windkit 2.0.0__tar.gz → 2.0.1__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 (117) hide show
  1. {windkit-2.0.0 → windkit-2.0.1}/CHANGELOG.md +22 -6
  2. {windkit-2.0.0 → windkit-2.0.1}/PKG-INFO +49 -27
  3. windkit-2.0.1/pyproject.toml +113 -0
  4. windkit-2.0.1/setup.cfg +4 -0
  5. {windkit-2.0.0 → windkit-2.0.1}/windkit/__init__.py +6 -1
  6. {windkit-2.0.0 → windkit-2.0.1}/windkit/_version.py +2 -2
  7. windkit-2.0.1/windkit/integrations/_earth_engine.py +337 -0
  8. {windkit-2.0.0 → windkit-2.0.1}/windkit/spatial/__init__.py +1 -0
  9. windkit-2.0.1/windkit/spatial/_conversions.py +42 -0
  10. {windkit-2.0.0 → windkit-2.0.1}/windkit/spatial/_struct.py +27 -0
  11. {windkit-2.0.0 → windkit-2.0.1}/windkit/topography/map_conversions.py +3 -0
  12. windkit-2.0.1/windkit/tutorial_data.py +276 -0
  13. {windkit-2.0.0 → windkit-2.0.1}/windkit/wind_climate/binned_wind_climate.py +1 -1
  14. {windkit-2.0.0 → windkit-2.0.1}/windkit/wind_farm/wind_turbines.py +143 -0
  15. {windkit-2.0.0 → windkit-2.0.1}/windkit.egg-info/SOURCES.txt +1 -2
  16. windkit-2.0.0/PERFORMANCE_CI_PLAN.md +0 -294
  17. windkit-2.0.0/pyproject.toml +0 -15
  18. windkit-2.0.0/setup.cfg +0 -66
  19. windkit-2.0.0/windkit/integrations/_earth_engine.py +0 -241
  20. windkit-2.0.0/windkit/tutorial_data.py +0 -188
  21. {windkit-2.0.0 → windkit-2.0.1}/LICENSE +0 -0
  22. {windkit-2.0.0 → windkit-2.0.1}/README.md +0 -0
  23. {windkit-2.0.0 → windkit-2.0.1}/windkit/_errors.py +0 -0
  24. {windkit-2.0.0 → windkit-2.0.1}/windkit/_rvea_xml.py +0 -0
  25. {windkit-2.0.0 → windkit-2.0.1}/windkit/_weibull_nb.py +0 -0
  26. {windkit-2.0.0 → windkit-2.0.1}/windkit/cfd.py +0 -0
  27. {windkit-2.0.0 → windkit-2.0.1}/windkit/config.py +0 -0
  28. {windkit-2.0.0 → windkit-2.0.1}/windkit/data/all_vars.json +0 -0
  29. {windkit-2.0.0 → windkit-2.0.1}/windkit/data/color_table_roughness.json +0 -0
  30. {windkit-2.0.0 → windkit-2.0.1}/windkit/data/landcover_tables/CGLS-LC100.json +0 -0
  31. {windkit-2.0.0 → windkit-2.0.1}/windkit/data/landcover_tables/CORINE.json +0 -0
  32. {windkit-2.0.0 → windkit-2.0.1}/windkit/data/landcover_tables/ESA_CCI.json +0 -0
  33. {windkit-2.0.0 → windkit-2.0.1}/windkit/data/landcover_tables/GWA4_micro.json +0 -0
  34. {windkit-2.0.0 → windkit-2.0.1}/windkit/data/landcover_tables/Globcover.json +0 -0
  35. {windkit-2.0.0 → windkit-2.0.1}/windkit/data/landcover_tables/MODIS.json +0 -0
  36. {windkit-2.0.0 → windkit-2.0.1}/windkit/data/landcover_tables/NEWA_micro_v2.json +0 -0
  37. {windkit-2.0.0 → windkit-2.0.1}/windkit/data/landcover_tables/WorldCover.json +0 -0
  38. {windkit-2.0.0 → windkit-2.0.1}/windkit/data/loss_tables/dtu_loss_table.csv +0 -0
  39. {windkit-2.0.0 → windkit-2.0.1}/windkit/data/map_file_header_to_epsg.json +0 -0
  40. {windkit-2.0.0 → windkit-2.0.1}/windkit/data/map_file_header_to_epsg_read.json +0 -0
  41. {windkit-2.0.0 → windkit-2.0.1}/windkit/data/uncertainty_tables/dtu_uncertainty_table.csv +0 -0
  42. {windkit-2.0.0 → windkit-2.0.1}/windkit/import_manager.py +0 -0
  43. {windkit-2.0.0 → windkit-2.0.1}/windkit/integrations/__init__.py +0 -0
  44. {windkit-2.0.0 → windkit-2.0.1}/windkit/integrations/_gwa_map_api.py +0 -0
  45. {windkit-2.0.0 → windkit-2.0.1}/windkit/integrations/_planetary_computer.py +0 -0
  46. {windkit-2.0.0 → windkit-2.0.1}/windkit/ltc/__init__.py +0 -0
  47. {windkit-2.0.0 → windkit-2.0.1}/windkit/ltc/_validation.py +0 -0
  48. {windkit-2.0.0 → windkit-2.0.1}/windkit/ltc/mcp.py +0 -0
  49. {windkit-2.0.0 → windkit-2.0.1}/windkit/ltc/regression.py +0 -0
  50. {windkit-2.0.0 → windkit-2.0.1}/windkit/ltc/scores.py +0 -0
  51. {windkit-2.0.0 → windkit-2.0.1}/windkit/plot/__init__.py +0 -0
  52. {windkit-2.0.0 → windkit-2.0.1}/windkit/plot/_colormaps.py +0 -0
  53. {windkit-2.0.0 → windkit-2.0.1}/windkit/plot/_helpers.py +0 -0
  54. {windkit-2.0.0 → windkit-2.0.1}/windkit/plot/color.py +0 -0
  55. {windkit-2.0.0 → windkit-2.0.1}/windkit/plot/elevation_map.py +0 -0
  56. {windkit-2.0.0 → windkit-2.0.1}/windkit/plot/histogram.py +0 -0
  57. {windkit-2.0.0 → windkit-2.0.1}/windkit/plot/landcover_map.py +0 -0
  58. {windkit-2.0.0 → windkit-2.0.1}/windkit/plot/operational_curves.py +0 -0
  59. {windkit-2.0.0 → windkit-2.0.1}/windkit/plot/raster_plot.py +0 -0
  60. {windkit-2.0.0 → windkit-2.0.1}/windkit/plot/roughness_rose.py +0 -0
  61. {windkit-2.0.0 → windkit-2.0.1}/windkit/plot/time_series.py +0 -0
  62. {windkit-2.0.0 → windkit-2.0.1}/windkit/plot/vertical_profile.py +0 -0
  63. {windkit-2.0.0 → windkit-2.0.1}/windkit/plot/wind_rose.py +0 -0
  64. {windkit-2.0.0 → windkit-2.0.1}/windkit/plot/wind_turbine.py +0 -0
  65. {windkit-2.0.0 → windkit-2.0.1}/windkit/spatial/README.md +0 -0
  66. {windkit-2.0.0 → windkit-2.0.1}/windkit/spatial/_bbox.py +0 -0
  67. {windkit-2.0.0 → windkit-2.0.1}/windkit/spatial/_crs.py +0 -0
  68. {windkit-2.0.0 → windkit-2.0.1}/windkit/spatial/_cuboid.py +0 -0
  69. {windkit-2.0.0 → windkit-2.0.1}/windkit/spatial/_dimensions.py +0 -0
  70. {windkit-2.0.0 → windkit-2.0.1}/windkit/spatial/_interpolation.py +0 -0
  71. {windkit-2.0.0 → windkit-2.0.1}/windkit/spatial/_latitude.py +0 -0
  72. {windkit-2.0.0 → windkit-2.0.1}/windkit/spatial/_metpy_natural_neighbors.py +0 -0
  73. {windkit-2.0.0 → windkit-2.0.1}/windkit/spatial/_point.py +0 -0
  74. {windkit-2.0.0 → windkit-2.0.1}/windkit/spatial/_raster.py +0 -0
  75. {windkit-2.0.0 → windkit-2.0.1}/windkit/spatial/_utm.py +0 -0
  76. {windkit-2.0.0 → windkit-2.0.1}/windkit/spatial/_vector.py +0 -0
  77. {windkit-2.0.0 → windkit-2.0.1}/windkit/spatial/_vertical.py +0 -0
  78. {windkit-2.0.0 → windkit-2.0.1}/windkit/spatial/decorators.py +0 -0
  79. {windkit-2.0.0 → windkit-2.0.1}/windkit/spatial/spatial.py +0 -0
  80. {windkit-2.0.0 → windkit-2.0.1}/windkit/topography/__init__.py +0 -0
  81. {windkit-2.0.0 → windkit-2.0.1}/windkit/topography/_vectormap_gml.py +0 -0
  82. {windkit-2.0.0 → windkit-2.0.1}/windkit/topography/_vectormap_helpers.py +0 -0
  83. {windkit-2.0.0 → windkit-2.0.1}/windkit/topography/get_map.py +0 -0
  84. {windkit-2.0.0 → windkit-2.0.1}/windkit/topography/landcover.py +0 -0
  85. {windkit-2.0.0 → windkit-2.0.1}/windkit/topography/map_conversion/__init__.py +0 -0
  86. {windkit-2.0.0 → windkit-2.0.1}/windkit/topography/map_conversion/_polygons_constraints.py +0 -0
  87. {windkit-2.0.0 → windkit-2.0.1}/windkit/topography/map_conversion/helper_functions.py +0 -0
  88. {windkit-2.0.0 → windkit-2.0.1}/windkit/topography/map_conversion/lines_to_poly.py +0 -0
  89. {windkit-2.0.0 → windkit-2.0.1}/windkit/topography/map_conversion/poly_to_lines.py +0 -0
  90. {windkit-2.0.0 → windkit-2.0.1}/windkit/topography/raster_map.py +0 -0
  91. {windkit-2.0.0 → windkit-2.0.1}/windkit/topography/topography.py +0 -0
  92. {windkit-2.0.0 → windkit-2.0.1}/windkit/topography/vector_map.py +0 -0
  93. {windkit-2.0.0 → windkit-2.0.1}/windkit/utils.py +0 -0
  94. {windkit-2.0.0 → windkit-2.0.1}/windkit/weibull.py +0 -0
  95. {windkit-2.0.0 → windkit-2.0.1}/windkit/weng_workspace.py +0 -0
  96. {windkit-2.0.0 → windkit-2.0.1}/windkit/wind.py +0 -0
  97. {windkit-2.0.0 → windkit-2.0.1}/windkit/wind_climate/__init__.py +0 -0
  98. {windkit-2.0.0 → windkit-2.0.1}/windkit/wind_climate/generalized_wind_climate.py +0 -0
  99. {windkit-2.0.0 → windkit-2.0.1}/windkit/wind_climate/geostrophic_wind_climate.py +0 -0
  100. {windkit-2.0.0 → windkit-2.0.1}/windkit/wind_climate/predicted_wind_climate.py +0 -0
  101. {windkit-2.0.0 → windkit-2.0.1}/windkit/wind_climate/time_series_wind_climate.py +0 -0
  102. {windkit-2.0.0 → windkit-2.0.1}/windkit/wind_climate/weibull_wind_climate.py +0 -0
  103. {windkit-2.0.0 → windkit-2.0.1}/windkit/wind_climate/wind_climate.py +0 -0
  104. {windkit-2.0.0 → windkit-2.0.1}/windkit/wind_data.py +0 -0
  105. {windkit-2.0.0 → windkit-2.0.1}/windkit/wind_farm/__init__.py +0 -0
  106. {windkit-2.0.0 → windkit-2.0.1}/windkit/wind_farm/loss.py +0 -0
  107. {windkit-2.0.0 → windkit-2.0.1}/windkit/wind_farm/power_curve.py +0 -0
  108. {windkit-2.0.0 → windkit-2.0.1}/windkit/wind_farm/uncertainty.py +0 -0
  109. {windkit-2.0.0 → windkit-2.0.1}/windkit/wind_farm/wind_turbine_generator.py +0 -0
  110. {windkit-2.0.0 → windkit-2.0.1}/windkit/workspace.py +0 -0
  111. {windkit-2.0.0 → windkit-2.0.1}/windkit/xarray_structures/_validate.py +0 -0
  112. {windkit-2.0.0 → windkit-2.0.1}/windkit/xarray_structures/data_structures.py +0 -0
  113. {windkit-2.0.0 → windkit-2.0.1}/windkit/xarray_structures/direction.py +0 -0
  114. {windkit-2.0.0 → windkit-2.0.1}/windkit/xarray_structures/empty.py +0 -0
  115. {windkit-2.0.0 → windkit-2.0.1}/windkit/xarray_structures/metadata.py +0 -0
  116. {windkit-2.0.0 → windkit-2.0.1}/windkit/xarray_structures/sector.py +0 -0
  117. {windkit-2.0.0 → windkit-2.0.1}/windkit/xarray_structures/wsbin.py +0 -0
@@ -2,14 +2,22 @@
2
2
 
3
3
  All major changes are listed here.
4
4
 
5
- ## 2.0.0 (2025-07-21)
5
+ ## [2.0.1 (2026-01-29)](https://docs.wasp.dk/windkit/v2.0.1/)
6
6
 
7
7
  ### Highlights
8
8
 
9
- ### Performance improvements
9
+ #### Performance improvements
10
10
  * Significantly reduced import time by lazy loading some packages and data. Should be approx 1/3 of previous import time.
11
11
 
12
- ### Sheer and veer functionality
12
+ #### Documentation
13
+ * Restructured user guide into focused, object-based documentation pages:
14
+ - Wind climates section with dedicated pages for TSWC, BWC, WWC, and GWC including I/O formats and usage examples
15
+ - Topography section with elevation, roughness, and landcover map documentation including format support tables
16
+ - Spatial section explaining point, stacked_point, raster, and cuboid structures with operation examples
17
+ - Wind farms section documenting wind turbine datasets and WTG objects
18
+ - New overview section with data structures, schemas, and tutorial data documentation
19
+
20
+ #### Sheer and veer functionality
13
21
 
14
22
  * Added new function `wk.shear_extrapolate` to extrapolate wind speeds to different heights using a power law shear profile.
15
23
  * Added new function `wk.shear_exponent` to calculate a shear exponent from wind speeds
@@ -23,16 +31,24 @@ All major changes are listed here.
23
31
 
24
32
  * new function `create_geowc`, which creates a dummy geostrophic wind climate
25
33
  * functions `mean_wind_speed`, `mean_power_density`, `ws_freq_gt_mean`, `ws_cdf` and `mean_ws_moment` are now also working when a geostrophic wind climate is passed.
26
- * bug fix to interpolation, which would create invalid spatial structures when a dimension was in exclude dims but not present in the target structure (e.g. when doing interpolation from `stacked_point` to `point`).
27
34
 
28
35
  ### New features
29
36
  * Added new function `wk.extrapolate_to_height` to extrapolate wind climate objects to different heights. Currently, only works for time-series wind climates (TSWC).
30
- * Fixed bug where a point dataset with several horizontal locations would be converted to a single horizontal location when using the function `to_stacked_point`
31
37
  * `wk.roughness_map_to_file` now allows to write to a old fashioned `.map` file for the special case where these is a single polygon and the external roughness length is not specified (None). For this case we assume that the external roughness length is the same as the roughness length inside the polygon.
32
38
  * `wk.roughness_map_to_file` now allows users to write to a `.map` as long all displacement heights in the map are zero.
39
+ * For `wv_count` objects (thus also geostrophic wind climates) if all of the bins have nan's, the result of function `mean_wind_speed`, `mean_power_density`, `ws_freq_gt_mean` and `mean_ws_moment` operating on such a function is also nan, whereas before it would be 0.
40
+ * Added new function `wk.load_tutorial_data` to download and load tutorial data directly into memory as windkit-compatible objects (xarray.Dataset and geopandas.GeoDataFrame).
41
+ * Changed `wk.get_tutorial_data` to return a `Path` to the extracted tutorial data folder, allowing users to load files manually using windkit readers or generic loaders. The function now includes detailed docstrings describing all available files for each dataset, including .nc, .omwc, .wtg, .map, and .csv.
42
+ * Added `wk.read_wind_turbines` and `wk.wind_turbines_to_file` functions to read and write wind turbine datasets in various formats. Both only support CSV for now. The CSV should follow the windkit wind turbine format specification.
43
+
44
+
45
+ ### Bug fixes
46
+ * Fixed pixel misalignment when downloading raster maps from Google Earth Engine. Downloads now use `crs_transform` instead of `scale` to preserve exact pixel alignment with source datasets (e.g., Globcover, SRTM, WorldCover). This fixes half-pixel offsets (~150m for Globcover) and ~12-15m offsets for elevation data.
33
47
  * Bugfix in `read_bwc`: it now opens `bwc` stored in netcdf correctly.
34
48
  but some of this functionality was only available in pywasp, so it has been moved there.
35
- * For `wv_count` objects (thus also geostrophic wind climates) if all of the bins have nan's, the result of function `mean_wind_speed`, `mean_power_density`, `ws_freq_gt_mean` and `mean_ws_moment` operating on such a function is also nan, whereas before it would be 0.
49
+ * Fixed bug where a point dataset with several horizontal locations would be converted to a single horizontal location when using the function `to_stacked_point`
50
+ * bug fix to interpolation, which would create invalid spatial structures when a dimension was in exclude dims but not present in the target structure (e.g. when doing interpolation from `stacked_point` to `point`).
51
+
36
52
 
37
53
  ### Changes
38
54
  * Changed default value of `use_bounds` to `True` in `wk.spatial.BBox.reproject` method.
@@ -1,50 +1,72 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: windkit
3
- Version: 2.0.0
3
+ Version: 2.0.1
4
4
  Summary: WindKit provides core functionalities for working with wind resource data
5
- Home-page: https://docs.wasp.dk/windkit/latest
6
5
  Author: DTU Wind Energy
7
- License: BSD-3-Clause
6
+ License-Expression: BSD-3-Clause
8
7
  Classifier: Programming Language :: Python :: 3
9
8
  Classifier: Operating System :: OS Independent
10
9
  Requires-Python: >=3.11
11
10
  Description-Content-Type: text/markdown
12
11
  License-File: LICENSE
12
+ Requires-Dist: geopandas>=1.0
13
+ Requires-Dist: h5netcdf
13
14
  Requires-Dist: lxml>=5.0
14
15
  Requires-Dist: netcdf4>=1.6
15
16
  Requires-Dist: numpy>=1.26
16
17
  Requires-Dist: packaging>=23
17
18
  Requires-Dist: pandas>=2.1
19
+ Requires-Dist: platformdirs>=4.3
20
+ Requires-Dist: pydantic>=2.7
21
+ Requires-Dist: pydantic-settings>=2.3
18
22
  Requires-Dist: pyproj>=3.6
23
+ Requires-Dist: pyyaml
24
+ Requires-Dist: rasterio>=1.3
25
+ Requires-Dist: requests
26
+ Requires-Dist: rioxarray>=0.17
19
27
  Requires-Dist: scipy>=1.12
20
- Requires-Dist: shapely>=2
28
+ Requires-Dist: shapely>=2.0
29
+ Requires-Dist: toml
21
30
  Requires-Dist: xarray>=2023.9
22
- Requires-Dist: rioxarray>=0.17
23
- Requires-Dist: rasterio>=1.3
24
- Requires-Dist: geopandas>=1.0
25
- Requires-Dist: h5netcdf>=1.3
26
- Requires-Dist: h5py>=3.9
27
- Requires-Dist: matplotlib>=3.8
28
- Requires-Dist: pydantic-settings>=2.3
29
- Requires-Dist: pydantic>=2.7
30
- Requires-Dist: platformdirs>=4.3.8
31
- Requires-Dist: plotly>=5
32
- Requires-Dist: kaleido>=0.2.1
33
- Requires-Dist: requests>=2.31
34
- Requires-Dist: toml>=0.10
35
31
  Provides-Extra: plots
36
- Requires-Dist: matplotlib>=3.6; extra == "plots"
37
- Requires-Dist: plotly>=5.12; extra == "plots"
38
- Requires-Dist: kaleido>=0.2.1; 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"
35
+ Provides-Extra: performance
36
+ Requires-Dist: numba>=0.57.0; extra == "performance"
39
37
  Provides-Extra: data-apis
40
- Requires-Dist: ee; extra == "data-apis"
41
- Requires-Dist: pystac_client; extra == "data-apis"
42
- Requires-Dist: planetary_computer; extra == "data-apis"
43
- Requires-Dist: fsspec; extra == "data-apis"
44
38
  Requires-Dist: adlfs; extra == "data-apis"
45
- Requires-Dist: zarr; extra == "data-apis"
46
- Provides-Extra: performance
47
- Requires-Dist: numba; extra == "performance"
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"
44
+ Provides-Extra: dev
45
+ Requires-Dist: dash; extra == "dev"
46
+ Requires-Dist: ipython; extra == "dev"
47
+ Requires-Dist: nbval; extra == "dev"
48
+ Requires-Dist: pre-commit; extra == "dev"
49
+ Requires-Dist: pytest; extra == "dev"
50
+ Requires-Dist: pytest-cov; extra == "dev"
51
+ Requires-Dist: pytest-xdist; extra == "dev"
52
+ Requires-Dist: windIO; extra == "dev"
53
+ Provides-Extra: doc
54
+ Requires-Dist: docutils; extra == "doc"
55
+ Requires-Dist: jupyter; extra == "doc"
56
+ Requires-Dist: jupyter-dash; extra == "doc"
57
+ Requires-Dist: jupyter-sphinx; extra == "doc"
58
+ Requires-Dist: myst-parser; extra == "doc"
59
+ Requires-Dist: nbsphinx; extra == "doc"
60
+ Requires-Dist: nbsphinx-link; extra == "doc"
61
+ Requires-Dist: pydata-sphinx-theme; extra == "doc"
62
+ Requires-Dist: recommonmark; extra == "doc"
63
+ Requires-Dist: sphinx; extra == "doc"
64
+ Requires-Dist: sphinx-argparse; extra == "doc"
65
+ Requires-Dist: sphinx-copybutton; extra == "doc"
66
+ Requires-Dist: sphinx-design; extra == "doc"
67
+ Requires-Dist: sphinx-gallery; extra == "doc"
68
+ Requires-Dist: sphinxcontrib-bibtex; extra == "doc"
69
+ Requires-Dist: sphinxext-opengraph; extra == "doc"
48
70
  Dynamic: license-file
49
71
 
50
72
  # WindKit
@@ -0,0 +1,113 @@
1
+ [build-system]
2
+ requires = ["setuptools>=45", "setuptools_scm[toml]>=6.2"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "windkit"
7
+ description = "WindKit provides core functionalities for working with wind resource data"
8
+ readme = "README.md"
9
+ license = "BSD-3-Clause"
10
+ authors = [
11
+ {name = "DTU Wind Energy"}
12
+ ]
13
+ classifiers = [
14
+ "Programming Language :: Python :: 3",
15
+ "Operating System :: OS Independent",
16
+ ]
17
+ dynamic = ["version"]
18
+ requires-python = ">=3.11"
19
+ dependencies = [
20
+ "geopandas>=1.0",
21
+ "h5netcdf",
22
+ "lxml>=5.0",
23
+ "netcdf4>=1.6",
24
+ "numpy>=1.26",
25
+ "packaging>=23",
26
+ "pandas>=2.1",
27
+ "platformdirs>=4.3",
28
+ "pydantic>=2.7",
29
+ "pydantic-settings>=2.3",
30
+ "pyproj>=3.6",
31
+ "pyyaml",
32
+ "rasterio>=1.3",
33
+ "requests",
34
+ "rioxarray>=0.17",
35
+ "scipy>=1.12",
36
+ "shapely>=2.0",
37
+ "toml",
38
+ "xarray>=2023.9",
39
+ ]
40
+
41
+ [project.optional-dependencies]
42
+ plots = [
43
+ "matplotlib>=3.8",
44
+ "plotly>=5.12.0",
45
+ "kaleido",
46
+ ]
47
+ performance = [
48
+ "numba>=0.57.0",
49
+ ]
50
+ data_apis = [
51
+ "adlfs",
52
+ "earthengine-api",
53
+ "fsspec",
54
+ "planetary-computer",
55
+ "pystac-client",
56
+ "zarr>2.15",
57
+ ]
58
+ dev = [
59
+ "dash",
60
+ "ipython",
61
+ "nbval",
62
+ "pre-commit",
63
+ "pytest",
64
+ "pytest-cov",
65
+ "pytest-xdist",
66
+ "windIO",
67
+ ]
68
+ doc = [
69
+ "docutils",
70
+ "jupyter",
71
+ "jupyter-dash",
72
+ "jupyter-sphinx",
73
+ "myst-parser",
74
+ "nbsphinx",
75
+ "nbsphinx-link",
76
+ "pydata-sphinx-theme",
77
+ "recommonmark",
78
+ "sphinx",
79
+ "sphinx-argparse",
80
+ "sphinx-copybutton",
81
+ "sphinx-design",
82
+ "sphinx-gallery",
83
+ "sphinxcontrib-bibtex",
84
+ "sphinxext-opengraph",
85
+ ]
86
+
87
+ [project.scripts]
88
+ split_combomap = "windkit.topography.vector_map:_split_combo"
89
+
90
+ [tool.setuptools_scm]
91
+ write_to = "windkit/_version.py"
92
+
93
+ [tool.setuptools]
94
+ include-package-data = true
95
+
96
+ [tool.setuptools.packages.find]
97
+ namespaces = true
98
+
99
+ [tool.setuptools.package-data]
100
+ "windkit.data" = ["*.json"]
101
+ "windkit.data.landcover_tables" = ["*.json"]
102
+ "windkit.data.plot" = ["*.json"]
103
+ "windkit.data.loss_tables" = ["*.csv"]
104
+ "windkit.data.uncertainty_tables" = ["*.csv"]
105
+
106
+ [tool.pytest.ini_options]
107
+ minversion = "6.0"
108
+ norecursedirs = "dist build .tox"
109
+ testpaths = ["test",]
110
+ markers = [
111
+ "slow: marks tests as slow (deselect with '-m \"not slow\"')",
112
+ "performance: marks tests as performance tests (run with '-m performance')",
113
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -105,6 +105,8 @@ __all__ = [
105
105
  "create_wind_turbines_from_dataframe",
106
106
  "create_wind_turbines_from_arrays",
107
107
  "wind_turbines_to_geodataframe",
108
+ "read_wind_turbines",
109
+ "wind_turbines_to_file",
108
110
  # workspace
109
111
  "Workspace",
110
112
  # weng_workspace
@@ -142,6 +144,7 @@ __all__ = [
142
144
  "total_uncertainty_factor",
143
145
  # tutorial_data
144
146
  "get_tutorial_data",
147
+ "load_tutorial_data",
145
148
  ]
146
149
 
147
150
  from windkit._version import version as __version__
@@ -238,6 +241,8 @@ from windkit.wind_farm.wind_turbines import (
238
241
  create_wind_turbines_from_dataframe,
239
242
  create_wind_turbines_from_arrays,
240
243
  wind_turbines_to_geodataframe,
244
+ read_wind_turbines,
245
+ wind_turbines_to_file,
241
246
  )
242
247
  from windkit.workspace import Workspace
243
248
  from windkit.weng_workspace import WengWorkspace
@@ -301,4 +306,4 @@ from windkit.xarray_structures.direction import (
301
306
  from windkit.xarray_structures.wsbin import (
302
307
  create_wsbin_coords,
303
308
  )
304
- from windkit.tutorial_data import get_tutorial_data
309
+ from windkit.tutorial_data import get_tutorial_data, load_tutorial_data
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '2.0.0'
32
- __version_tuple__ = version_tuple = (2, 0, 0)
31
+ __version__ = version = '2.0.1'
32
+ __version_tuple__ = version_tuple = (2, 0, 1)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -0,0 +1,337 @@
1
+ # (c) 2022 DTU Wind Energy
2
+ """
3
+ Module that downloads elevation and roughness maps
4
+
5
+ Currently driven by the google earth engine, which is a cloud interface service to access
6
+ landcover and elevation data, among other things.
7
+
8
+ Installation & Setup
9
+ --------------------
10
+
11
+ The earth engine is an optional install, you can install with conda::
12
+
13
+ conda install earthengine-api google-cloud-sdk
14
+
15
+ After installation you will have to do a one-time authentication
16
+ step from the command line: ``earthengine authenticate``
17
+
18
+ This will open a browser where you will have to allow google to use
19
+ you google account to retrieve data from the google servers. If you are on a machine
20
+ without the ability to use a browser (such as an HPC
21
+ cluster), you will have to use ``earthengine authenticate --quiet``, which requires you to
22
+ to manually copy the authentication code into the terminal.
23
+
24
+ In addition, you will have to `sign up
25
+ <https://signup.earthengine.google.com/#!/>`_ for the google earth engine and give
26
+ a reason why you want to use the program. Please pay particular attention to
27
+ their terms of service.
28
+
29
+ Automated Datasets
30
+ ------------------
31
+
32
+ Currently, the databases that have been added are the Copernicus Global Land Cover
33
+ (CGLS-LC100), Copernicus CORINE land Cover (CORINE), MODIS Global Land Cover MCD12Q1
34
+ (MODIS), Globcover and WorldCover
35
+ (https://developers.google.com/earth-engine/datasets/catalog/ESA_WorldCover_v100) as landcover databases and
36
+ NASA SRTM Digital Elevation 30m(SRTM), ALOS DSM: Global 30 (ALOS) and NASADEM
37
+ (https://developers.google.com/earth-engine/datasets/catalog/NASA_NASADEM_HGT_001?hl=en) as elevation databases.
38
+
39
+ The landcover databases have standard conversion tables that are included in
40
+ ``windkit/data/landcover_tables``.
41
+
42
+ Google Earth Engine provides lists of
43
+ `elevation
44
+ <https://developers.google.com/earth-engine/datasets/tags/elevation>`_ and
45
+ `land cover
46
+ <https://developers.google.com/earth-engine/datasets/tags/landcover>`_
47
+ data sources, which provide additional details about the various datasources used
48
+ in this library.
49
+ """
50
+
51
+ import logging
52
+ import math
53
+ import tempfile
54
+ import urllib.request
55
+ import warnings
56
+ from pathlib import Path
57
+
58
+ import rioxarray
59
+
60
+ from ..spatial import BBox, clip
61
+
62
+ CRS_GEOGRAPHIC = "EPSG:4326"
63
+
64
+ # Dataset configuration: maps datasource name to (url, band, is_collection)
65
+ _DATASET_CONFIG = {
66
+ "CGLS-LC100": (
67
+ "COPERNICUS/Landcover/100m/Proba-V/Global/2015",
68
+ "discrete_classification",
69
+ False,
70
+ ),
71
+ "CORINE": ("COPERNICUS/CORINE/V20/100m/2018", "landcover", False),
72
+ "MODIS": ("MODIS/006/MCD12Q1/2018_01_01", "LC_Type1", False),
73
+ "SRTM": ("USGS/SRTMGL1_003", "elevation", False),
74
+ "NASADEM": ("NASA/NASADEM_HGT/001", "elevation", False),
75
+ "ALOS": ("JAXA/ALOS/AW3D30/V2_2", "AVE_DSM", False),
76
+ "Globcover": ("ESA/GLOBCOVER_L4_200901_200912_V2_3", "landcover", False),
77
+ "WorldCover": ("ESA/WorldCover/v100", "Map", True),
78
+ }
79
+
80
+ ELEVATION_DATASETS = {"SRTM", "ALOS", "NASADEM"}
81
+ LIST_DATA_SOURCES = list(_DATASET_CONFIG.keys())
82
+
83
+ logger = logging.getLogger(__name__)
84
+
85
+ # Initialize Google Earth Engine
86
+ try:
87
+ import ee as earth_engine
88
+
89
+ try:
90
+ credentials_path = Path.home() / "earth_engine.json"
91
+ if credentials_path.exists():
92
+ service_account = "windkit@windkit.iam.gserviceaccount.com"
93
+ credentials = earth_engine.ServiceAccountCredentials(
94
+ service_account, str(credentials_path)
95
+ )
96
+ earth_engine.Initialize(credentials)
97
+ else:
98
+ earth_engine.Initialize()
99
+ except Exception:
100
+ warnings.warn(
101
+ "Could not initialize Google Earth Engine. Run 'earth_engine.Authenticate()' and try again."
102
+ )
103
+
104
+ except ImportError:
105
+ earth_engine = None
106
+
107
+
108
+ def _bbox_to_ee_geometry(bbox):
109
+ return earth_engine.Geometry(bbox.__geo_interface__, bbox.crs.to_string())
110
+
111
+
112
+ def _get_image(datasource):
113
+ """Get image from Google Earth Engine for the specified datasource.
114
+
115
+ Parameters
116
+ ----------
117
+ datasource : str
118
+ One of: 'CGLS-LC100', 'CORINE', 'MODIS', 'Globcover', 'WorldCover',
119
+ 'SRTM', 'ALOS', 'NASADEM'
120
+
121
+ Returns
122
+ -------
123
+ image : ee.Image
124
+ Google Earth Engine image for the specified band
125
+ """
126
+ if earth_engine is None:
127
+ raise ValueError(
128
+ "ee (earthengine-api) is required to get maps from Google Earth Engine"
129
+ )
130
+
131
+ if datasource not in _DATASET_CONFIG:
132
+ valid_sources = ", ".join(LIST_DATA_SOURCES)
133
+ raise ValueError(f"Please specify a valid data source from {valid_sources}")
134
+
135
+ url, band, is_collection = _DATASET_CONFIG[datasource]
136
+
137
+ if is_collection:
138
+ dataset = earth_engine.ImageCollection(url).first()
139
+ else:
140
+ dataset = earth_engine.Image(url)
141
+
142
+ return dataset.select(band)
143
+
144
+
145
+ def _get_ee_map(lat, lon, buffer_dist=20000, source="SRTM", vector=False):
146
+ """Extract map from a given lat, lon
147
+
148
+ Extract the smallest square which fits a cirle with radius buffer_dist
149
+ around the coordinates lat,lon.
150
+
151
+ Parameters
152
+ ----------
153
+ lat : float
154
+ Center latitude from which we extract a map
155
+ lon : float
156
+ Center longitude from which we extract a map
157
+ buffer_dist : int, optional
158
+ Distance in meters from the given (lat,lon) where a map is extracted, by default 20000
159
+ source : str {"CGLS-LC100", "CORINE", "MODIS", "Globcover", "WorldCover", "SRTM", "ALOS", "NASADEM"}, optional
160
+ Landcover or elevation datasource, by default "SRTM"
161
+ vector:
162
+ If true, return the map in vector format else return a raster map
163
+ """
164
+ if vector:
165
+ raise NotImplementedError("This feature is not yet available.")
166
+
167
+ bbox = BBox.utm_bbox_from_geographic_coordinate(lon, lat, buffer_dist)
168
+ ras = _get_raster_map_from_earth_engine(bbox, dataset=source)
169
+ return ras
170
+
171
+
172
+ def _compute_aligned_origin(bbox_min, bbox_max, scale, origin):
173
+ """Compute a new origin aligned to the native grid but near the bbox.
174
+
175
+ This prevents Earth Engine from computing pixels from a distant origin
176
+ (e.g., -180 degrees) to the bbox by finding the nearest grid-aligned
177
+ point just outside the bbox.
178
+
179
+ Parameters
180
+ ----------
181
+ bbox_min : float
182
+ Minimum bound of bbox in this dimension (west or south)
183
+ bbox_max : float
184
+ Maximum bound of bbox in this dimension (east or north)
185
+ scale : float
186
+ Pixel scale (positive or negative)
187
+ origin : float
188
+ Native grid origin
189
+
190
+ Returns
191
+ -------
192
+ float
193
+ New origin aligned to native grid, positioned just outside the bbox
194
+ """
195
+ # Choose reference bound based on scale direction
196
+ # Positive scale: origin at min side, negative scale: origin at max side
197
+ reference_bound = bbox_min if scale > 0 else bbox_max
198
+ n_pixels = math.floor((reference_bound - origin) / scale)
199
+ return origin + n_pixels * scale
200
+
201
+
202
+ def _create_aligned_transform(native_transform, bbox):
203
+ """Create an adjusted transform aligned to the native grid near the bbox.
204
+
205
+ Parameters
206
+ ----------
207
+ native_transform : list
208
+ Earth Engine transform: [xScale, xShear, xOrigin, yShear, yScale, yOrigin]
209
+ bbox : BBox
210
+ Bounding box for the download
211
+
212
+ Returns
213
+ -------
214
+ list or None
215
+ Adjusted transform, or None if transform cannot be computed
216
+ """
217
+ x_scale, x_shear, x_origin, y_shear, y_scale, y_origin = native_transform
218
+
219
+ if x_scale == 0 or y_scale == 0:
220
+ return None
221
+
222
+ west, south, east, north = bbox.bounds()
223
+
224
+ new_x_origin = _compute_aligned_origin(west, east, x_scale, x_origin)
225
+ new_y_origin = _compute_aligned_origin(south, north, y_scale, y_origin)
226
+
227
+ return [x_scale, x_shear, new_x_origin, y_shear, y_scale, new_y_origin]
228
+
229
+
230
+ def _get_download_url(ee_image, download_params, dataset):
231
+ """Get download URL, falling back to scale if crs_transform fails.
232
+
233
+ Some datasets (e.g., NASADEM) may not respect the adjusted transform origin
234
+ and fail with grid dimension errors.
235
+
236
+ Parameters
237
+ ----------
238
+ ee_image : ee.Image
239
+ Earth Engine image
240
+ download_params : dict
241
+ Download parameters (may be modified in place)
242
+ dataset : str
243
+ Dataset name for logging
244
+
245
+ Returns
246
+ -------
247
+ str
248
+ Download URL
249
+ """
250
+ try:
251
+ return ee_image.getDownloadURL(download_params)
252
+ except Exception as e:
253
+ if "crs_transform" in download_params and "grid dimensions" in str(e).lower():
254
+ logger.debug(
255
+ f"crs_transform failed for {dataset}, falling back to scale: {e}"
256
+ )
257
+ del download_params["crs_transform"]
258
+ download_params["scale"] = ee_image.projection().nominalScale().getInfo()
259
+ return ee_image.getDownloadURL(download_params)
260
+ raise
261
+
262
+
263
+ def _get_raster_map_from_earth_engine(bbox, dataset="NASADEM", band=None):
264
+ """
265
+ Get map from Google Earth Engine.
266
+
267
+ Parameters
268
+ ----------
269
+ bbox : windkit.spatial.BBox
270
+ Bounding box of the map to download.
271
+
272
+ dataset : str, optional
273
+ Dataset to retrieve, by default "NASADEM"
274
+
275
+ band : str, optional
276
+ Band to retrieve, by default None
277
+
278
+ Returns
279
+ -------
280
+ da : xarray.DataArray
281
+ DataArray with the map
282
+
283
+ Notes
284
+ -----
285
+ When the requested CRS matches the dataset's native CRS, the download uses
286
+ `crs_transform` to preserve exact pixel alignment with the source data.
287
+ When the CRS differs, `scale` is used which may result in slight pixel
288
+ misalignment (typically sub-pixel shifts).
289
+ """
290
+ if earth_engine is None:
291
+ raise ValueError(
292
+ "ee (earthengine-api) is required to get maps from Google Earth Engine"
293
+ )
294
+
295
+ if not isinstance(bbox, BBox):
296
+ raise ValueError("bbox must be a BBox object or a windkit.spatial.BBox object.")
297
+
298
+ ee_image = _get_image(dataset)
299
+ projection_info = ee_image.projection().getInfo()
300
+ native_crs = projection_info.get("crs")
301
+ native_transform = projection_info.get("transform")
302
+ requested_crs = f"EPSG:{bbox.crs.to_epsg()}"
303
+
304
+ download_params = {
305
+ "region": _bbox_to_ee_geometry(bbox.reproject(CRS_GEOGRAPHIC)),
306
+ "format": "GEO_TIFF",
307
+ "crs": requested_crs,
308
+ }
309
+
310
+ # Use crs_transform for precise alignment when CRS matches
311
+ crs_matches = native_crs == requested_crs and native_transform is not None
312
+ adjusted_transform = (
313
+ _create_aligned_transform(native_transform, bbox) if crs_matches else None
314
+ )
315
+
316
+ if adjusted_transform is not None:
317
+ download_params["crs_transform"] = adjusted_transform
318
+ else:
319
+ download_params["scale"] = ee_image.projection().nominalScale().getInfo()
320
+
321
+ download_url = _get_download_url(ee_image, download_params, dataset)
322
+
323
+ tmpfile = tempfile.NamedTemporaryFile(delete=False)
324
+ try:
325
+ logger.debug(f"Downloading file {tmpfile.name}")
326
+ urllib.request.urlretrieve(download_url, tmpfile.name)
327
+
328
+ da = rioxarray.open_rasterio(tmpfile.name)
329
+ da = da.rename({"spatial_ref": "crs", "x": "west_east", "y": "south_north"})
330
+ da = da.drop_vars("band", errors="i")
331
+ da.name = "elevation" if dataset in ELEVATION_DATASETS else "landcover"
332
+ da = clip(da, bbox)
333
+ da = da.sortby(["south_north", "west_east"])
334
+ return da
335
+ finally:
336
+ tmpfile.close()
337
+ Path(tmpfile.name).unlink()
@@ -97,6 +97,7 @@ from ._struct import (
97
97
  _is_vertical,
98
98
  )
99
99
  from .decorators import _stack_then_unstack
100
+ from ._conversions import _to_spatial_struct
100
101
  from .spatial import (
101
102
  add_projected_wrf_coordinates,
102
103
  equal_spatial_shape,