austaltools 2.9.2__tar.gz → 2.10.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 (105) hide show
  1. {austaltools-2.9.2 → austaltools-2.10.0}/.github/workflows/release.yml +1 -1
  2. {austaltools-2.9.2 → austaltools-2.10.0}/PKG-INFO +1 -1
  3. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_version.py +1 -1
  4. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/command_line.py +4 -0
  5. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/eap.py +5 -5
  6. austaltools-2.10.0/austaltools/volout.py +410 -0
  7. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools.egg-info/PKG-INFO +1 -1
  8. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools.egg-info/SOURCES.txt +5 -0
  9. {austaltools-2.9.2 → austaltools-2.10.0}/docs/api_commands.rst +14 -0
  10. {austaltools-2.9.2 → austaltools-2.10.0}/docs/api_internal.rst +7 -7
  11. austaltools-2.10.0/docs/austaltools.rst +50 -0
  12. {austaltools-2.9.2 → austaltools-2.10.0}/docs/conf.py +4 -1
  13. austaltools-2.10.0/docs/img/volout_basilika.png +0 -0
  14. austaltools-2.10.0/docs/img/windfield.png +0 -0
  15. austaltools-2.10.0/docs/img/windrose.png +0 -0
  16. austaltools-2.10.0/tests/test_volout.py +397 -0
  17. austaltools-2.9.2/docs/austaltools.rst +0 -27
  18. {austaltools-2.9.2 → austaltools-2.10.0}/.gitignore +0 -0
  19. {austaltools-2.9.2 → austaltools-2.10.0}/.gitlab-ci.yml +0 -0
  20. {austaltools-2.9.2 → austaltools-2.10.0}/CITATION.cff +0 -0
  21. {austaltools-2.9.2 → austaltools-2.10.0}/LICENSE.txt +0 -0
  22. {austaltools-2.9.2 → austaltools-2.10.0}/README.md +0 -0
  23. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/TODO.md +0 -0
  24. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/__init__.py +0 -0
  25. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_corine.py +0 -0
  26. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_datasets.py +0 -0
  27. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_dispersion.py +0 -0
  28. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_fetch_dwd_obs.py +0 -0
  29. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_geo.py +0 -0
  30. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_metadata.py +0 -0
  31. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_mock/__init__.py +0 -0
  32. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_mock/netCDF4.py +0 -0
  33. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_netcdf.py +0 -0
  34. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_plotting.py +0 -0
  35. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_storage.py +0 -0
  36. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_tools.py +0 -0
  37. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_windutil.py +0 -0
  38. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_wmo_metadata.py +0 -0
  39. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/austal_input.py +0 -0
  40. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/configure_austaltools.py +0 -0
  41. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/data/DGM10-HE.LICENSE.txt +0 -0
  42. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/data/GLO-30.LICENSE.txt +0 -0
  43. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/data/__init__.py +0 -0
  44. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/data/dataset_definitions.json +0 -0
  45. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/data/dwd_stationlist.json +0 -0
  46. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/data/scrape_places_names_HE.py +0 -0
  47. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/data/scrape_wmo_stationlist.py +0 -0
  48. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/data/wmo_stationlist.json +0 -0
  49. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/fill_timeseries.py +0 -0
  50. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/heating.py +0 -0
  51. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/import_buildings.py +0 -0
  52. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/input_terrain.py +0 -0
  53. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/input_weather.py +0 -0
  54. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/plot.py +0 -0
  55. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/simple.py +0 -0
  56. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/steepness.py +0 -0
  57. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/transform.py +0 -0
  58. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/windfield.py +0 -0
  59. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/windrose.py +0 -0
  60. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools.egg-info/dependency_links.txt +0 -0
  61. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools.egg-info/entry_points.txt +0 -0
  62. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools.egg-info/not-zip-safe +0 -0
  63. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools.egg-info/requires.txt +0 -0
  64. {austaltools-2.9.2 → austaltools-2.10.0}/austaltools.egg-info/top_level.txt +0 -0
  65. {austaltools-2.9.2 → austaltools-2.10.0}/ci_build_deb.sh +0 -0
  66. {austaltools-2.9.2 → austaltools-2.10.0}/docs/configure-austaltools.rst +0 -0
  67. {austaltools-2.9.2 → austaltools-2.10.0}/docs/fill-timeseries.rst +0 -0
  68. {austaltools-2.9.2 → austaltools-2.10.0}/docs/heating.rst +0 -0
  69. {austaltools-2.9.2 → austaltools-2.10.0}/docs/img/plot_contour_2sigma.png +0 -0
  70. {austaltools-2.9.2 → austaltools-2.10.0}/docs/img/plot_window.png +0 -0
  71. {austaltools-2.9.2 → austaltools-2.10.0}/docs/index.rst +0 -0
  72. {austaltools-2.9.2 → austaltools-2.10.0}/docs/install.rst +0 -0
  73. {austaltools-2.9.2 → austaltools-2.10.0}/docs/references.rst +0 -0
  74. {austaltools-2.9.2 → austaltools-2.10.0}/heating/partition_distance.py +0 -0
  75. {austaltools-2.9.2 → austaltools-2.10.0}/heating/plot_flux_wall_all_month_2003-07.py +0 -0
  76. {austaltools-2.9.2 → austaltools-2.10.0}/heating/plot_temp_room_room_month_2003-04.py +0 -0
  77. {austaltools-2.9.2 → austaltools-2.10.0}/heating/plot_temp_room_room_month_2003-07.py +0 -0
  78. {austaltools-2.9.2 → austaltools-2.10.0}/heating/plot_temp_wall_back_day_2003-04-01.py +0 -0
  79. {austaltools-2.9.2 → austaltools-2.10.0}/heating/plot_temp_wall_back_heatup_2003-01-01.py +0 -0
  80. {austaltools-2.9.2 → austaltools-2.10.0}/heating/plot_temp_wall_back_month_2003-04.py +0 -0
  81. {austaltools-2.9.2 → austaltools-2.10.0}/pyproject.toml +0 -0
  82. {austaltools-2.9.2 → austaltools-2.10.0}/setup.cfg +0 -0
  83. {austaltools-2.9.2 → austaltools-2.10.0}/setup.py +0 -0
  84. {austaltools-2.9.2 → austaltools-2.10.0}/tests/__init__.py +0 -0
  85. {austaltools-2.9.2 → austaltools-2.10.0}/tests/example.akterm +0 -0
  86. {austaltools-2.9.2 → austaltools-2.10.0}/tests/test.dmna +0 -0
  87. {austaltools-2.9.2 → austaltools-2.10.0}/tests/test__corine.py +0 -0
  88. {austaltools-2.9.2 → austaltools-2.10.0}/tests/test__datasets.py +0 -0
  89. {austaltools-2.9.2 → austaltools-2.10.0}/tests/test__dispersion.py +0 -0
  90. {austaltools-2.9.2 → austaltools-2.10.0}/tests/test__fetch_dwd_obs.py +0 -0
  91. {austaltools-2.9.2 → austaltools-2.10.0}/tests/test__geo.py +0 -0
  92. {austaltools-2.9.2 → austaltools-2.10.0}/tests/test__netcdf.py +0 -0
  93. {austaltools-2.9.2 → austaltools-2.10.0}/tests/test__plotting.py +0 -0
  94. {austaltools-2.9.2 → austaltools-2.10.0}/tests/test__storage.py +0 -0
  95. {austaltools-2.9.2 → austaltools-2.10.0}/tests/test__tools.py +0 -0
  96. {austaltools-2.9.2 → austaltools-2.10.0}/tests/test__windutil.py +0 -0
  97. {austaltools-2.9.2 → austaltools-2.10.0}/tests/test__wmo_metadata.py +0 -0
  98. {austaltools-2.9.2 → austaltools-2.10.0}/tests/test_austal_input.py +0 -0
  99. {austaltools-2.9.2 → austaltools-2.10.0}/tests/test_command_line.py +0 -0
  100. {austaltools-2.9.2 → austaltools-2.10.0}/tests/test_eap.py +0 -0
  101. {austaltools-2.9.2 → austaltools-2.10.0}/tests/test_fill_timeseries.py +0 -0
  102. {austaltools-2.9.2 → austaltools-2.10.0}/tests/test_import_buildings.py +0 -0
  103. {austaltools-2.9.2 → austaltools-2.10.0}/tests/test_input_terrain.py +0 -0
  104. {austaltools-2.9.2 → austaltools-2.10.0}/tests/test_input_weather.py +0 -0
  105. {austaltools-2.9.2 → austaltools-2.10.0}/tests/test_windrose.py +0 -0
@@ -3,7 +3,7 @@ name: Create Release
3
3
  on:
4
4
  push:
5
5
  tags:
6
- - 'v[0-9]+.[0-9]+.[0-9]+' # Only stable, not v2.9.0-a9
6
+ - 'v[0-9]+\.[0-9]+\.[0-9]+' # Escaped dots for literal matching
7
7
 
8
8
  jobs:
9
9
  release:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: austaltools
3
- Version: 2.9.2
3
+ Version: 2.10.0
4
4
  Summary: Tools for use with the Lagrangian atmospheric pollutant dispersion model AUSTAL (AUSbreitungsmodell nach TA-Luft).
5
5
  Author-email: Clemens Drüe <druee@uni-trier.de>
6
6
  License: EUPL-1.2
@@ -1,3 +1,3 @@
1
1
  """Version information generated by setuptools_scm"""
2
2
 
3
- __version__ = "2.9.2"
3
+ __version__ = "2.10.0"
@@ -18,6 +18,7 @@ from . import steepness
18
18
  from . import simple
19
19
  from . import transform
20
20
  from . import plot
21
+ from . import volout
21
22
  from . import windfield
22
23
  from . import windrose
23
24
 
@@ -74,6 +75,7 @@ def cli_parser():
74
75
  input_terrain,
75
76
  transform,
76
77
  input_weather,
78
+ volout,
77
79
  windfield,
78
80
  windrose,
79
81
  ]:
@@ -164,6 +166,8 @@ def main(args=None):
164
166
  input_terrain.main(args)
165
167
  elif args['command'] == 'transform':
166
168
  transform.main(args)
169
+ elif args['command'] == 'volout':
170
+ volout.main(args)
167
171
  elif args['command'] == 'weather':
168
172
  input_weather.main(args)
169
173
  elif args['command'] == 'windfield':
@@ -2,9 +2,9 @@
2
2
  # -*- coding: utf-8 -*-
3
3
  """
4
4
  This module implements the process described in VDI 3783 Part 16
5
- :cite:`VDI3783p16` to find a substitute anemometer position (EAP)
5
+ [VDI3783p16]_ to find a substitute anemometer position (EAP)
6
6
  when wind measurements provided as input to the dispersion model
7
- AUSTAL :cite:`AST31` are **not** taken by an anemometer inside the
7
+ AUSTAL [AST31]_ are **not** taken by an anemometer inside the
8
8
  AUSTAL model domain (i.e., on a nearby weather station or taken from
9
9
  a weather model).
10
10
 
@@ -168,7 +168,7 @@ def same_sense_rotation(val, ref):
168
168
  This function is used in the EAP algorithm to reject grid points
169
169
  where the wind does not rotate consistently with the reference
170
170
  profile as required by VDI 3783 Part 16, Section 6.1, criterion 2
171
- [vdi3783p16]_ .
171
+ [VDI3783p16]_ .
172
172
  """
173
173
 
174
174
  def angdiff(ang: np.ndarray) -> np.ndarray:
@@ -324,7 +324,7 @@ def calc_quality_measure(u_grid, v_grid, u_ref, v_ref,
324
324
  dimensions are incompatible with the wind library.
325
325
 
326
326
  .. note::
327
- The algorithm follows VDI 3783 Part 16, Section 6.1 [vdi3783p16]_ :
327
+ The algorithm follows VDI 3783 Part 16, Section 6.1 [VDI3783p16]_ :
328
328
 
329
329
  1. Exclude edge nodes (``nedge`` from each boundary).
330
330
  2. Reject points where wind doesn't rotate consistently or
@@ -1066,7 +1066,7 @@ def calc_vdi3783_8(levels: list, dirs: list, z0: float = None,
1066
1066
  :rtype: tuple(numpy.ndarray, numpy.ndarray)
1067
1067
 
1068
1068
  .. note::
1069
- The two-layer model from VDI 3783 Part 8 :cite:`VDI3783p8` consists of:
1069
+ The two-layer model from VDI 3783 Part 8 [VDI3783p8]_ consists of:
1070
1070
 
1071
1071
  **Lower layer** (:math:`z \\leq h_1`):
1072
1072
  Surface layer following Monin-Obukhov similarity with wind speed:
@@ -0,0 +1,410 @@
1
+ #!/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ This module ...
5
+ """
6
+ import logging
7
+ import os
8
+ import sys
9
+
10
+ import numpy as np
11
+ import pandas as pd
12
+
13
+ if os.environ.get('BUILDING_SPHINX', 'false') == 'false':
14
+ import matplotlib as mpl
15
+ import matplotlib.pyplot as plt
16
+ import matplotlib.colors as mcolors
17
+ from matplotlib.colors import LightSource
18
+ from mpl_toolkits.mplot3d.art3d import Poly3DCollection
19
+ logging.getLogger('matplotlib.font_manager').setLevel(logging.ERROR)
20
+
21
+ import readmet
22
+
23
+ from . import _dispersion
24
+ from . import _corine
25
+ from . import _plotting
26
+ from . import _tools
27
+ from ._metadata import __version__
28
+ from . import _windutil
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+
33
+ # -------------------------------------------------------------------------
34
+
35
+ def is_valid_color(c):
36
+ try:
37
+ mcolors.to_rgba(c)
38
+ return True
39
+ except ValueError:
40
+ return False
41
+
42
+ # -------------------------------------------------------------------------
43
+
44
+ def contrasting_shade(color, factor=0.4):
45
+ """
46
+ Return a darker or lighter shade of the same hue that
47
+ contrasts with the input.
48
+
49
+ If the color is light (luminance > 0.5), returns a darker shade.
50
+ If the color is dark, returns a lighter shade.
51
+
52
+ :param color: any matplotlib color specification
53
+ :param factor: how far to shift the lightness (0..1)
54
+ :returns: RGBA tuple
55
+ """
56
+ r, g, b, a = mcolors.to_rgba(color)
57
+ # perceived luminance
58
+ luminance = 0.299 * r + 0.587 * g + 0.114 * b
59
+ if luminance > 0.5:
60
+ r2, g2, b2 = (max(0.0, r * (1 - factor)),
61
+ max(0.0, g * (1 - factor)),
62
+ max(0.0, b * (1 - factor)))
63
+ else:
64
+ r2 = r + (1 - r) * factor
65
+ g2 = g + (1 - g) * factor
66
+ b2 = b + (1 - b) * factor
67
+ return (r2, g2, b2, a)
68
+
69
+ # -------------------------------------------------------------------------
70
+
71
+ def plot_voxels(grid: np.ndarray,
72
+ xmin: float, ymin: float, delt: float, hh: np.ndarray|list,
73
+ color: tuple[str] | None = None,
74
+ camera: tuple[float] | None = None,
75
+ zoom: float | None = None,
76
+ clip: str | None = None):
77
+ """
78
+ Plot isometric view of a 3D binary grid.
79
+
80
+ :param grid: 3D ndarray of 0s and 1s, shape (ni, nj, nk)
81
+ :type grid: np.ndarray
82
+ :param xmin: x origin
83
+ :type xmin: float
84
+ :param ymin: y origin
85
+ :type ymin: float
86
+ :param delt: grid spacing in x and y
87
+ :type delt: float
88
+ :param hh: 1D array of z-levels, length nk
89
+ :type hh: np.ndarray | list
90
+ :param camera: plot view from azimuth
91
+ (geodetic, clockwise from north)
92
+ and elevation (upward) angle
93
+ :type camera: tuple[float]
94
+ :param zoom: zoom focal length (1 = 90° viewing angle) or None
95
+ for isometric plot
96
+ :type zoom: float | None
97
+ :param clip:
98
+ zoom to
99
+ - `out`: show full grid
100
+ - 'center': keep center position, clip axes (symetrically) to max building extent
101
+ - `in`: show only space filled with buildings
102
+ :type clip: str | None
103
+ """
104
+ if clip is None:
105
+ clip = 'no'
106
+ if camera is None:
107
+ camera = (45, 40)
108
+ if color is None:
109
+ color = ('tan', 'chocolate')
110
+
111
+ hh = np.asarray(hh, dtype=float)
112
+ ni, nj, nk = grid.shape
113
+ filled = np.argwhere(grid == 1)
114
+
115
+ if len(filled) == 0:
116
+ logger.warning('Grid is entirely empty — nothing to plot.')
117
+ return
118
+
119
+ if isinstance(color, str):
120
+ color = color.strip('()').replace(',', ' ').split()
121
+ if not 1 <= len(color) <= 2:
122
+ raise ValueError(f"One or two colors must be given, "
123
+ f"found {len(color)}")
124
+ rgb = list() # ensure mutable
125
+ for i, c in enumerate(color):
126
+ if is_valid_color(c):
127
+ rgb.append(mcolors.to_rgba(c))
128
+ else:
129
+ raise ValueError(f"Invalid color: {c}")
130
+ if len(color) < 2:
131
+ rgb.append(contrasting_shade(color[0]))
132
+
133
+ fig = plt.figure(figsize=(10, 8))
134
+ ax = fig.add_subplot(111, projection='3d')
135
+
136
+ # Build coordinate arrays for ax.voxels()
137
+ # x, y are uniform; z follows hh (possibly irregular)
138
+ x = xmin + np.arange(ni + 1) * delt
139
+ y = ymin + np.arange(nj + 1) * delt
140
+
141
+ # z edges: check if hh already has nk+1 entries (includes top edge)
142
+ # or nk entries (lower edges only, need to append one more)
143
+ if len(hh) == nk + 1:
144
+ z = hh # already cell edges
145
+ elif len(hh) == nk:
146
+ dz_last = hh[1] - hh[0] if nk > 1 else delt
147
+ z = np.append(hh, hh[-1] + dz_last) # append top edge
148
+ else:
149
+ raise ValueError(f"hh length {len(hh)} incompatible with nk={nk} "
150
+ f"(expected {nk} or {nk + 1})")
151
+
152
+ # ax.voxels needs coordinate meshes of shape (ni+1, nj+1, nk+1)
153
+ X, Y, Z = np.meshgrid(x, y, z, indexing='ij')
154
+
155
+
156
+ light_azi = (camera[0] + 135) % 360 # geodetic, clockwise from north
157
+ light_ele = 45
158
+ logger.debug("light: azi = %s , ele = %s" %
159
+ (light_azi, light_ele))
160
+ ls = LightSource(light_azi, light_ele) # azdeg, altdeg
161
+
162
+ ax.voxels(X, Y, Z, grid.astype(bool),
163
+ facecolors=rgb[0],
164
+ edgecolors=rgb[1],
165
+ shade=True, # we handle shading ourselves
166
+ lightsource=ls,
167
+ linewidth=0.75)
168
+
169
+ ax.view_init(elev=camera[1],
170
+ azim=(90 - camera[0]) % 360) # math, anticlockw. fr. east
171
+ if zoom:
172
+ ax.set_proj_type('persp', focal_length=float(zoom))
173
+ else:
174
+ ax.set_proj_type('ortho')
175
+ ax.set_xlabel('X')
176
+ ax.set_ylabel('Y')
177
+ ax.set_zlabel('Z')
178
+ # ax.set_title('Isometric Grid View')
179
+
180
+ # --- zoom ---
181
+ i_min, j_min, k_min = filled.min(axis=0)
182
+ i_max, j_max, k_max = filled.max(axis=0)
183
+
184
+ bx_min, bx_max = x[i_min], x[i_max + 1]
185
+ by_min, by_max = y[j_min], y[j_max + 1]
186
+ bz_min, bz_max = z[k_min], z[k_max + 1]
187
+
188
+ if clip == 'fit':
189
+ xmin_val, xmax_val = bx_min, bx_max
190
+ ymin_val, ymax_val = by_min, by_max
191
+ zmin_val, zmax_val = bz_min, bz_max
192
+ elif clip == 'center':
193
+ cx, cy = xmin + ni * delt / 2.0, ymin + nj * delt / 2.0
194
+ half_x = max(cx - bx_min, bx_max - cx)
195
+ half_y = max(cy - by_min, by_max - cy)
196
+ xmin_val, xmax_val = cx - half_x, cx + half_x
197
+ ymin_val, ymax_val = cy - half_y, cy + half_y
198
+ zmin_val, zmax_val = float(z[0]), bz_max
199
+ else: # 'no'
200
+ xmin_val, xmax_val = float(x[0]), float(x[-1])
201
+ ymin_val, ymax_val = float(y[0]), float(y[-1])
202
+ zmin_val, zmax_val = float(z[0]), float(z[-1])
203
+
204
+ ax.set_xlim(xmin_val, xmax_val)
205
+ ax.set_ylim(ymin_val, ymax_val)
206
+ ax.set_zlim(zmin_val, zmax_val)
207
+
208
+ x_range = xmax_val - xmin_val
209
+ y_range = ymax_val - ymin_val
210
+ z_range = zmax_val - zmin_val if zmax_val != zmin_val else 1.0
211
+ try:
212
+ ax.set_box_aspect((x_range, y_range, z_range))
213
+ except AttributeError:
214
+ pass
215
+
216
+ plt.tight_layout()
217
+
218
+ # -------------------------------------------------------------------------
219
+
220
+ def main(args):
221
+ """
222
+ This is the main working function
223
+
224
+ :param args: the command line arguments as dictionary
225
+ :type args: dict
226
+ """
227
+ logger.debug(format(args))
228
+
229
+ working_dir = args.get('working_dir', '.')
230
+
231
+ # determine output
232
+ plotfile = _plotting.consolidate_plotname(args['plot'],'volout.png')
233
+
234
+ grid_no = int(args['grid'])
235
+ volfile = os.path.join(working_dir, f'volout{grid_no:02d}.dmna')
236
+ volume = readmet.dmna.DataFile(volfile)
237
+ xmin = float(volume.header['xmin'])
238
+ ymin = float(volume.header['ymin'])
239
+ delt = float(volume.header['delt'])
240
+ hh = [float(x) for x in volume.header['hh'].split()]
241
+ grid = volume.data['']
242
+
243
+ angle = args.get('angle', 'SW')
244
+ if isinstance(angle, str):
245
+ if angle.upper() in ['N', 'E', 'S', 'W']:
246
+ if angle.upper() == 'N':
247
+ azi_from = 0
248
+ elif angle.upper() == 'E':
249
+ azi_from = 90
250
+ elif angle.upper() == 'S':
251
+ azi_from = 180
252
+ else:
253
+ azi_from = 270
254
+ ele_from = 0
255
+ zoom = None
256
+ elif angle.upper() in ['NE', 'SW', 'SE', 'NW']:
257
+ if angle.upper() == 'NE':
258
+ azi_from = 45
259
+ elif angle.upper() == 'SE':
260
+ azi_from = 135
261
+ elif angle.upper() == 'SW':
262
+ azi_from = 225
263
+ else:
264
+ azi_from = 315
265
+ ele_from = 40
266
+ zoom = None
267
+ else:
268
+ # remove parentheses if a litteral tuple is given
269
+ angle = angle.strip("()")
270
+ # string representing a number or tuple of numbers
271
+ # e.g. "225", "225,40", "225,40,0.5"
272
+ try:
273
+ parts = [float(v)
274
+ for v in angle.replace(',', ' ').split()]
275
+ except ValueError:
276
+ raise ValueError(f"Unrecognised angle value: {angle!r}")
277
+ if len(parts) == 1:
278
+ azi_from = parts[0]
279
+ ele_from = 40
280
+ zoom = 1
281
+ elif len(parts) == 2:
282
+ azi_from = parts[0]
283
+ ele_from = parts[1]
284
+ zoom = 1
285
+ elif len(parts) == 3:
286
+ azi_from = parts[0]
287
+ ele_from = parts[1]
288
+ zoom = parts[2]
289
+ else:
290
+ raise ValueError(f"Angle string must have 1–3 values, "
291
+ f"got: {angle!r}")
292
+ elif isinstance(angle, (int, float)):
293
+ azi_from = angle
294
+ ele_from = 40
295
+ zoom = 1
296
+ elif isinstance(angle, (list, tuple)):
297
+ if len(angle) == 2:
298
+ azi_from = angle[0]
299
+ ele_from = angle[1]
300
+ zoom = 1
301
+ elif len(angle) == 3:
302
+ azi_from = angle[0]
303
+ ele_from = angle[1]
304
+ zoom = angle[2]
305
+ else:
306
+ raise ValueError(f"Angle must be string or float or tuple, "
307
+ f"not {type(angle)}")
308
+
309
+ camera = ( float(azi_from) , float(ele_from))
310
+ logger.debug("camera: azi = %s, ele = %s" %
311
+ (camera[0], camera[1]))
312
+ clip = args.get('clip', None)
313
+ logger.debug("clip = %s" % clip)
314
+ logger.debug("zoom = %s" % zoom)
315
+ color = args.get('color', None)
316
+ logger.debug("color = %s" % color)
317
+ plot_voxels(grid, xmin, ymin, delt, hh,
318
+ camera=camera,
319
+ clip=clip,
320
+ color=color,
321
+ zoom=zoom)
322
+
323
+
324
+ if plotfile == "__show__":
325
+ logger.info('showing plot')
326
+ plt.show()
327
+ else:
328
+ if os.path.sep in plotfile:
329
+ outname = plotfile
330
+ else:
331
+ outname = os.path.join(args["working_dir"], plotfile)
332
+ if not outname.endswith('.png'):
333
+ outname = outname + '.png'
334
+ logger.info('writing plot: %s' % outname)
335
+ plt.savefig(outname, dpi=180)
336
+
337
+ # ----------------------------------------------------
338
+
339
+ def add_options(subparsers):
340
+
341
+ pars_wrs = subparsers.add_parser(
342
+ name='volout',
343
+ help='Plot building volumes',
344
+ formatter_class=_tools.SmartFormatter,
345
+ )
346
+ pars_wrs.add_argument('-k', '--kind',
347
+ dest='style',
348
+ choices=['default'],
349
+ default='default',
350
+ help='style of volume plot [%(default)s])]')
351
+ pars_wrs.add_argument('-p', '--plot',
352
+ metavar="FILE",
353
+ nargs='?',
354
+ const='__default__',
355
+ help='save plot to a file. If `FILE` is "-" ' +
356
+ 'the plot is shown on screen. If `FILE` is ' +
357
+ 'missing, the file name defaults to ' +
358
+ 'the data file name with extension `png`'
359
+ )
360
+ pars_wrs_vol = pars_wrs.add_mutually_exclusive_group()
361
+ pars_wrs_vol.add_argument('-g', '--grid',
362
+ dest='grid',
363
+ default=1,
364
+ help='Number of the grid for which to plot '
365
+ 'the building volumes [%(default)s])].')
366
+ pars_wrs_vol.add_argument('-f', '--file',
367
+ dest = 'file',
368
+ default = None,
369
+ help = 'Name of the file to read '
370
+ '[%(default)s])].')
371
+ pars_wrs.add_argument('-c', '--clip',
372
+ dest = 'clip',
373
+ default='fit',
374
+ choices=['fit', 'center', 'no'],
375
+ help=r'Zoom level for plotting: \n'
376
+ r' - "no": view full grid\n'
377
+ r' - "center": zoom to grid center\n'
378
+ r' - "fit": zoom closest to buildings\n'
379
+ r'Defaults to [%(default)s])].')
380
+ pars_wrs.add_argument('-a', '--angle',
381
+ dest = 'angle',
382
+ default = 'SW',
383
+ help=r"viewing angle:\n"
384
+ r" - `N`, `E`, `S`, `W`: side view"
385
+ r" from north, east, south, west\n"
386
+ r" - `NW`, `SW`, `SE`, `NE`: isometric"
387
+ r" view from ... \n"
388
+ r" - 0...360: Perspective view from "
389
+ r" direction (geographic degrees,"
390
+ r" clockwise from north)\n"
391
+ r" - AZI,ELE[,FL]: comma-sepatrated tuple"
392
+ r" of view-from direction and elevation, "
393
+ r" and optionally focal length "
394
+ r" (focal length of 1.0 corresponds to"
395
+ r" 90° field of view, defaults to 0.5 if"
396
+ r" absent)\n"
397
+ r"Default is [%(default)s])].")
398
+ pars_wrs.add_argument_group('advanced options')
399
+ pars_wrs.add_argument('--color',
400
+ dest='color',
401
+ default='sienna',
402
+ help="color of the buidlings as\n"
403
+ " - color name or html color code"
404
+ " (a darker shade is used"
405
+ " for the edges\n"
406
+ " - two comma-separated color names or"
407
+ " html color codes"
408
+ " (used for sides and egdes)\n"
409
+ "Html color codes in the form #rrggbb. "
410
+ "Defaults to [%(default)s])].")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: austaltools
3
- Version: 2.9.2
3
+ Version: 2.10.0
4
4
  Summary: Tools for use with the Lagrangian atmospheric pollutant dispersion model AUSTAL (AUSbreitungsmodell nach TA-Luft).
5
5
  Author-email: Clemens Drüe <druee@uni-trier.de>
6
6
  License: EUPL-1.2
@@ -35,6 +35,7 @@ austaltools/plot.py
35
35
  austaltools/simple.py
36
36
  austaltools/steepness.py
37
37
  austaltools/transform.py
38
+ austaltools/volout.py
38
39
  austaltools/windfield.py
39
40
  austaltools/windrose.py
40
41
  austaltools.egg-info/PKG-INFO
@@ -66,6 +67,9 @@ docs/install.rst
66
67
  docs/references.rst
67
68
  docs/img/plot_contour_2sigma.png
68
69
  docs/img/plot_window.png
70
+ docs/img/volout_basilika.png
71
+ docs/img/windfield.png
72
+ docs/img/windrose.png
69
73
  heating/partition_distance.py
70
74
  heating/plot_flux_wall_all_month_2003-07.py
71
75
  heating/plot_temp_room_room_month_2003-04.py
@@ -94,4 +98,5 @@ tests/test_fill_timeseries.py
94
98
  tests/test_import_buildings.py
95
99
  tests/test_input_terrain.py
96
100
  tests/test_input_weather.py
101
+ tests/test_volout.py
97
102
  tests/test_windrose.py
@@ -68,9 +68,23 @@ transform
68
68
  :undoc-members:
69
69
  :member-order: groupwise
70
70
 
71
+ volout
72
+ ---------
73
+ .. automodule:: austaltools.volout
74
+ :members:
75
+ :undoc-members:
76
+ :member-order: groupwise
77
+
71
78
  windfield
72
79
  ---------
73
80
  .. automodule:: austaltools.windfield
74
81
  :members:
75
82
  :undoc-members:
76
83
  :member-order: groupwise
84
+
85
+ windrose
86
+ ---------
87
+ .. automodule:: austaltools.windrose
88
+ :members:
89
+ :undoc-members:
90
+ :member-order: groupwise
@@ -46,6 +46,13 @@ _plotting
46
46
  :undoc-members:
47
47
  :member-order: groupwise
48
48
 
49
+ _metadata
50
+ ---------
51
+ .. automodule:: austaltools._metadata
52
+ :members:
53
+ :undoc-members:
54
+ :member-order: groupwise
55
+
49
56
  _netcdf
50
57
  -----------
51
58
  .. automodule:: austaltools._netcdf
@@ -67,13 +74,6 @@ _tools
67
74
  :undoc-members:
68
75
  :member-order: groupwise
69
76
 
70
- _version
71
- --------
72
- .. automodule:: austaltools._version
73
- :members:
74
- :undoc-members:
75
- :member-order: groupwise
76
-
77
77
  _wmo_metadata
78
78
  -------------
79
79
  .. automodule:: austaltools._wmo_metadata
@@ -0,0 +1,50 @@
1
+ :orphan:
2
+
3
+ -----------
4
+ austaltools
5
+ -----------
6
+
7
+ .. |plotcontour| image:: img/plot_contour_2sigma.png
8
+ :width: 45%
9
+ :align: bottom
10
+
11
+ .. |plotvolout| image:: img/volout_basilika.png
12
+ :width: 45%
13
+ :align: bottom
14
+
15
+ .. |plotwindrose| image:: img/windrose.png
16
+ :width: 45%
17
+ :align: bottom
18
+
19
+ .. |plotwindfield| image:: img/windfield.png
20
+ :width: 45%
21
+ :align: bottom
22
+
23
+
24
+ .. argparse::
25
+ :module: austaltools.command_line
26
+ :func: cli_parser
27
+ :prog: austaltools
28
+
29
+ fill-timeseries (ft)
30
+ Detailed userguide see: :doc:`fill-timeseries`
31
+
32
+ heating
33
+ For a description fo the heating description file ``
34
+ see: :doc:`heating`
35
+
36
+ plot
37
+ A simple plot for a quick overview (click to enlarge)
38
+ |plotcontour|
39
+
40
+ volout
41
+ Example plot of a buildings volume plot (click to enlarge)
42
+ |plotvolout|
43
+
44
+ windfield
45
+ Example plot of a windfield (click to enlarge)
46
+ |plotwindfield|
47
+
48
+ windrose
49
+ Example plot of a windrose classified by wind speed quantiles (click to enlarge)
50
+ |plotwindrose|
@@ -38,7 +38,10 @@ try:
38
38
  except FileNotFoundError:
39
39
  __title__ = __author__ = __email__ = __description__ = 'Unknown'
40
40
 
41
- from austaltools._version import __version__
41
+ try:
42
+ from austaltools._version import __version__
43
+ except ImportError:
44
+ __version__ = 'local'
42
45
  from austaltools._metadata import __copyright__
43
46
 
44
47
 
Binary file