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.
- {austaltools-2.9.2 → austaltools-2.10.0}/.github/workflows/release.yml +1 -1
- {austaltools-2.9.2 → austaltools-2.10.0}/PKG-INFO +1 -1
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_version.py +1 -1
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/command_line.py +4 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/eap.py +5 -5
- austaltools-2.10.0/austaltools/volout.py +410 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools.egg-info/PKG-INFO +1 -1
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools.egg-info/SOURCES.txt +5 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/docs/api_commands.rst +14 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/docs/api_internal.rst +7 -7
- austaltools-2.10.0/docs/austaltools.rst +50 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/docs/conf.py +4 -1
- austaltools-2.10.0/docs/img/volout_basilika.png +0 -0
- austaltools-2.10.0/docs/img/windfield.png +0 -0
- austaltools-2.10.0/docs/img/windrose.png +0 -0
- austaltools-2.10.0/tests/test_volout.py +397 -0
- austaltools-2.9.2/docs/austaltools.rst +0 -27
- {austaltools-2.9.2 → austaltools-2.10.0}/.gitignore +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/.gitlab-ci.yml +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/CITATION.cff +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/LICENSE.txt +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/README.md +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/TODO.md +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/__init__.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_corine.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_datasets.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_dispersion.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_fetch_dwd_obs.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_geo.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_metadata.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_mock/__init__.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_mock/netCDF4.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_netcdf.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_plotting.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_storage.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_tools.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_windutil.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/_wmo_metadata.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/austal_input.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/configure_austaltools.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/data/DGM10-HE.LICENSE.txt +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/data/GLO-30.LICENSE.txt +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/data/__init__.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/data/dataset_definitions.json +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/data/dwd_stationlist.json +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/data/scrape_places_names_HE.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/data/scrape_wmo_stationlist.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/data/wmo_stationlist.json +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/fill_timeseries.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/heating.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/import_buildings.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/input_terrain.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/input_weather.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/plot.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/simple.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/steepness.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/transform.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/windfield.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools/windrose.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools.egg-info/dependency_links.txt +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools.egg-info/entry_points.txt +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools.egg-info/not-zip-safe +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools.egg-info/requires.txt +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/austaltools.egg-info/top_level.txt +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/ci_build_deb.sh +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/docs/configure-austaltools.rst +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/docs/fill-timeseries.rst +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/docs/heating.rst +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/docs/img/plot_contour_2sigma.png +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/docs/img/plot_window.png +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/docs/index.rst +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/docs/install.rst +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/docs/references.rst +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/heating/partition_distance.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/heating/plot_flux_wall_all_month_2003-07.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/heating/plot_temp_room_room_month_2003-04.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/heating/plot_temp_room_room_month_2003-07.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/heating/plot_temp_wall_back_day_2003-04-01.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/heating/plot_temp_wall_back_heatup_2003-01-01.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/heating/plot_temp_wall_back_month_2003-04.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/pyproject.toml +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/setup.cfg +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/setup.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/tests/__init__.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/tests/example.akterm +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/tests/test.dmna +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/tests/test__corine.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/tests/test__datasets.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/tests/test__dispersion.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/tests/test__fetch_dwd_obs.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/tests/test__geo.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/tests/test__netcdf.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/tests/test__plotting.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/tests/test__storage.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/tests/test__tools.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/tests/test__windutil.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/tests/test__wmo_metadata.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/tests/test_austal_input.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/tests/test_command_line.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/tests/test_eap.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/tests/test_fill_timeseries.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/tests/test_import_buildings.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/tests/test_input_terrain.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/tests/test_input_weather.py +0 -0
- {austaltools-2.9.2 → austaltools-2.10.0}/tests/test_windrose.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: austaltools
|
|
3
|
-
Version: 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
|
|
@@ -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
|
-
|
|
5
|
+
[VDI3783p16]_ to find a substitute anemometer position (EAP)
|
|
6
6
|
when wind measurements provided as input to the dispersion model
|
|
7
|
-
AUSTAL
|
|
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
|
-
[
|
|
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 [
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
Binary file
|
|
Binary file
|