pyBADA 0.1.5__py3-none-any.whl → 0.1.6__py3-none-any.whl
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.
- pyBADA/TCL.py +10 -17
- pyBADA/aircraft.py +2 -2
- pyBADA/atmosphere.py +403 -269
- pyBADA/bada3.py +58 -71
- pyBADA/bada4.py +116 -130
- pyBADA/badaH.py +20 -33
- pyBADA/configuration.py +2 -1
- pyBADA/conversions.py +113 -140
- pyBADA/flightTrajectory.py +2 -1
- pyBADA/geodesic.py +116 -10
- pyBADA/magnetic.py +2 -1
- pyBADA/trajectoryPrediction.py +13 -12
- pyBADA/utils.py +204 -0
- {pybada-0.1.5.dist-info → pybada-0.1.6.dist-info}/METADATA +14 -12
- {pybada-0.1.5.dist-info → pybada-0.1.6.dist-info}/RECORD +18 -17
- {pybada-0.1.5.dist-info → pybada-0.1.6.dist-info}/WHEEL +0 -0
- {pybada-0.1.5.dist-info → pybada-0.1.6.dist-info}/licenses/AUTHORS +0 -0
- {pybada-0.1.5.dist-info → pybada-0.1.6.dist-info}/licenses/LICENCE.txt +0 -0
pyBADA/utils.py
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import xarray as xr
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import numbers
|
|
5
|
+
from decimal import Decimal, ROUND_HALF_UP
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _round_scalar(x, dec):
|
|
9
|
+
"""Round a single scalar value using half-up rounding."""
|
|
10
|
+
quant = Decimal('1.' + '0'*dec)
|
|
11
|
+
d = Decimal(str(x))
|
|
12
|
+
return float(d.quantize(quant, rounding=ROUND_HALF_UP))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def proper_round(num, dec=0):
|
|
16
|
+
"""Forced half-up rounding, vectorized over numpy arrays, pandas, and xarray.
|
|
17
|
+
|
|
18
|
+
:param num: Input scalar, array-like, pandas Series/DataFrame, or xarray DataArray
|
|
19
|
+
:param dec: Number of decimal places
|
|
20
|
+
:returns: Rounded values with half-up rule; preserves infinities.
|
|
21
|
+
"""
|
|
22
|
+
# Scalar helper handling infinities
|
|
23
|
+
def _f(v):
|
|
24
|
+
# Preserve infinities
|
|
25
|
+
try:
|
|
26
|
+
if np.isinf(v):
|
|
27
|
+
return v
|
|
28
|
+
except Exception:
|
|
29
|
+
pass
|
|
30
|
+
return _round_scalar(v, dec)
|
|
31
|
+
|
|
32
|
+
# xarray DataArray
|
|
33
|
+
if isinstance(num, xr.DataArray):
|
|
34
|
+
arr = num.data
|
|
35
|
+
rounded = np.vectorize(_f)(arr)
|
|
36
|
+
return xr.DataArray(rounded, coords=num.coords, dims=num.dims)
|
|
37
|
+
|
|
38
|
+
# pandas Series
|
|
39
|
+
if isinstance(num, pd.Series):
|
|
40
|
+
return num.map(_f)
|
|
41
|
+
|
|
42
|
+
# pandas DataFrame
|
|
43
|
+
if isinstance(num, pd.DataFrame):
|
|
44
|
+
return num.applymap(_f)
|
|
45
|
+
|
|
46
|
+
# numpy array
|
|
47
|
+
if isinstance(num, np.ndarray):
|
|
48
|
+
return np.vectorize(_f)(num)
|
|
49
|
+
|
|
50
|
+
# scalar fallback
|
|
51
|
+
return _f(num)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def to_numpy(x):
|
|
55
|
+
"""
|
|
56
|
+
Convert xarray.DataArray, pandas.Series/DataFrame, or any array-like
|
|
57
|
+
to a NumPy array of floats.
|
|
58
|
+
"""
|
|
59
|
+
if isinstance(x, xr.DataArray):
|
|
60
|
+
return x.values
|
|
61
|
+
if isinstance(x, (pd.Series, pd.DataFrame)):
|
|
62
|
+
return x.to_numpy(copy=False)
|
|
63
|
+
return np.asarray(x, dtype=float)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _extract(x):
|
|
67
|
+
if isinstance(x, numbers.Real) and not isinstance(x, (np.generic, np.ndarray)):
|
|
68
|
+
return float(x)
|
|
69
|
+
if isinstance(x, xr.DataArray):
|
|
70
|
+
return x.data
|
|
71
|
+
if isinstance(x, (pd.Series, pd.DataFrame)):
|
|
72
|
+
return x.values
|
|
73
|
+
return np.asarray(x, dtype=float)
|
|
74
|
+
|
|
75
|
+
def _broadcast(*arrays):
|
|
76
|
+
"""
|
|
77
|
+
Broadcast any number of array-like inputs to a common shape.
|
|
78
|
+
- If *all* inputs are Python scalars, just return them as a tuple.
|
|
79
|
+
- If exactly two inputs, and one is 1-D while the other is N-D (N>1),
|
|
80
|
+
then align the 1-D array to whichever axis of the N-D array has the same length.
|
|
81
|
+
- Otherwise prepend leading singleton dims to match trailing-dims broadcasting.
|
|
82
|
+
"""
|
|
83
|
+
# 1) Scalar passthrough
|
|
84
|
+
if all(isinstance(a, numbers.Real) and not isinstance(a, (np.generic, np.ndarray))
|
|
85
|
+
for a in arrays):
|
|
86
|
+
return tuple(arrays)
|
|
87
|
+
|
|
88
|
+
# Convert everything up front
|
|
89
|
+
arrs = [np.asarray(a) for a in arrays]
|
|
90
|
+
|
|
91
|
+
# Special‐case exactly two inputs, one 1-D and one N-D
|
|
92
|
+
if len(arrs) == 2:
|
|
93
|
+
a0, a1 = arrs
|
|
94
|
+
# identify which is 1-D and which is higher-D
|
|
95
|
+
if a0.ndim == 1 and a1.ndim > 1:
|
|
96
|
+
arrs[0] = _align_1d_to_nd(a0, a1)
|
|
97
|
+
return np.broadcast_arrays(arrs[0], a1)
|
|
98
|
+
if a1.ndim == 1 and a0.ndim > 1:
|
|
99
|
+
arrs[1] = _align_1d_to_nd(a1, a0)
|
|
100
|
+
return np.broadcast_arrays(a0, arrs[1])
|
|
101
|
+
|
|
102
|
+
# Fallback: pad all inputs with leading singleton dims
|
|
103
|
+
max_ndim = max(a.ndim for a in arrs)
|
|
104
|
+
padded = [
|
|
105
|
+
a.reshape((1,) * (max_ndim - a.ndim) + a.shape)
|
|
106
|
+
for a in arrs
|
|
107
|
+
]
|
|
108
|
+
try:
|
|
109
|
+
return np.broadcast_arrays(*padded)
|
|
110
|
+
except ValueError:
|
|
111
|
+
shapes = [a.shape for a in arrs]
|
|
112
|
+
raise ValueError(f"Cannot broadcast input shapes {shapes}")
|
|
113
|
+
|
|
114
|
+
def _align_1d_to_nd(one_d, nd):
|
|
115
|
+
"""
|
|
116
|
+
Take one_d of shape (N,) and an array nd of shape (...),
|
|
117
|
+
find axis i where nd.shape[i] == N, and reshape one_d to
|
|
118
|
+
(1,1,...,N,...,1) so it lines up on that axis.
|
|
119
|
+
"""
|
|
120
|
+
N = one_d.shape[0]
|
|
121
|
+
for i, dim in enumerate(nd.shape):
|
|
122
|
+
if dim == N:
|
|
123
|
+
# build a shape of length nd.ndim: all 1s except axis i holds N
|
|
124
|
+
new_shape = tuple(1 if j != i else N for j in range(nd.ndim))
|
|
125
|
+
return one_d.reshape(new_shape)
|
|
126
|
+
# no matching dimension found
|
|
127
|
+
raise ValueError(f"Cannot align 1D array of length {N} with ND shape {nd.shape}")
|
|
128
|
+
|
|
129
|
+
# def _broadcast(*arrays):
|
|
130
|
+
# """
|
|
131
|
+
# Broadcast any number of array-like inputs to a common shape.
|
|
132
|
+
# Accepts Python scalars, numpy arrays, pandas Series/DataFrame, xarray DataArray (via utils._extract), and returns numpy arrays broadcasted or scalars.
|
|
133
|
+
# """
|
|
134
|
+
|
|
135
|
+
# If all inputs are Python real scalars, return them unchanged
|
|
136
|
+
# if all(isinstance(a, numbers.Real) and not isinstance(a, (np.generic, np.ndarray)) for a in arrays):
|
|
137
|
+
# return tuple(arrays)
|
|
138
|
+
|
|
139
|
+
# Convert inputs to numpy arrays and broadcast
|
|
140
|
+
# arrs = [np.asarray(a) for a in arrays]
|
|
141
|
+
# try:
|
|
142
|
+
# return np.broadcast_arrays(*arrs)
|
|
143
|
+
# except ValueError:
|
|
144
|
+
# shapes = [a.shape for a in arrs]
|
|
145
|
+
# raise ValueError(f"Cannot broadcast input shapes {shapes}")
|
|
146
|
+
|
|
147
|
+
def _wrap(core, original):
|
|
148
|
+
# 1) Plain Python floats
|
|
149
|
+
if isinstance(original, numbers.Real) and not isinstance(original, (np.generic, np.ndarray)):
|
|
150
|
+
# core might be a 0-d array or numpy scalar
|
|
151
|
+
return float(np.asarray(core).item())
|
|
152
|
+
|
|
153
|
+
# xarray
|
|
154
|
+
if isinstance(original, xr.DataArray):
|
|
155
|
+
return xr.DataArray(
|
|
156
|
+
core,
|
|
157
|
+
coords=original.coords,
|
|
158
|
+
dims=original.dims,
|
|
159
|
+
name=original.name,
|
|
160
|
+
attrs=original.attrs
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# pandas Series
|
|
164
|
+
if isinstance(original, pd.Series):
|
|
165
|
+
return pd.Series(core, index=original.index, name=original.name)
|
|
166
|
+
|
|
167
|
+
# pandas DataFrame
|
|
168
|
+
if isinstance(original, pd.DataFrame):
|
|
169
|
+
return pd.DataFrame(core, index=original.index, columns=original.columns)
|
|
170
|
+
|
|
171
|
+
# fallback: NumPy arrays/scalars
|
|
172
|
+
return core
|
|
173
|
+
|
|
174
|
+
def _vectorized_wrapper(core_func, *args):
|
|
175
|
+
"""Generic vectorized wrapper for functions with N inputs."""
|
|
176
|
+
# Extract raw arrays or scalars
|
|
177
|
+
arrs = [_extract(a) for a in args]
|
|
178
|
+
core = core_func(*arrs)
|
|
179
|
+
|
|
180
|
+
# If *all* inputs were plain Python real numbers, return a Python float
|
|
181
|
+
first = args[0]
|
|
182
|
+
if isinstance(first, numbers.Real) and not isinstance(first, (np.generic, np.ndarray)):
|
|
183
|
+
return float(np.asarray(core).item())
|
|
184
|
+
|
|
185
|
+
# xarray: if every arg was a DataArray, wrap back to DataArray
|
|
186
|
+
if isinstance(first, xr.DataArray) and all(isinstance(a, xr.DataArray) for a in args):
|
|
187
|
+
return xr.DataArray(core, coords=first.coords, dims=first.dims)
|
|
188
|
+
|
|
189
|
+
# pandas Series
|
|
190
|
+
if isinstance(first, pd.Series) and all(isinstance(a, pd.Series) for a in args):
|
|
191
|
+
return pd.Series(core, index=first.index, name=first.name)
|
|
192
|
+
|
|
193
|
+
# pandas DataFrame
|
|
194
|
+
if isinstance(first, pd.DataFrame) and all(isinstance(a, pd.DataFrame) for a in args):
|
|
195
|
+
return pd.DataFrame(core, index=first.index, columns=first.columns)
|
|
196
|
+
|
|
197
|
+
# fallback: NumPy array or scalar (leave as-is)
|
|
198
|
+
return core
|
|
199
|
+
|
|
200
|
+
def checkArgument(argument, **kwargs):
|
|
201
|
+
if kwargs.get(argument) is not None:
|
|
202
|
+
return kwargs.get(argument)
|
|
203
|
+
else:
|
|
204
|
+
raise TypeError("Missing " + argument + " argument")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyBADA
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6
|
|
4
4
|
Summary: Aircraft performance modelling, trajectory prediction and optimisation, and visualisation with EUROCONTROL's BADA.
|
|
5
5
|
Project-URL: Homepage, https://github.com/eurocontrol-bada/pybada
|
|
6
6
|
Author-email: Henrich Glaser-Opitz <henrich.glaser-opitz@eurocontrol.int>, Antonio Vivace <antonio.vivace@eurocontrol.int>
|
|
@@ -10,17 +10,18 @@ License-File: LICENCE.txt
|
|
|
10
10
|
Classifier: License :: OSI Approved :: European Union Public Licence 1.2 (EUPL 1.2)
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Requires-Python: >=3.12
|
|
13
|
-
Requires-Dist: numpy>=2.2
|
|
14
|
-
Requires-Dist: pandas>=2.
|
|
15
|
-
Requires-Dist: scipy>=1.
|
|
13
|
+
Requires-Dist: numpy>=2.3.2
|
|
14
|
+
Requires-Dist: pandas>=2.3.1
|
|
15
|
+
Requires-Dist: scipy>=1.16.1
|
|
16
16
|
Requires-Dist: simplekml>=1.3.6
|
|
17
|
-
Requires-Dist:
|
|
17
|
+
Requires-Dist: xarray>=2025.7.1
|
|
18
|
+
Requires-Dist: xlsxwriter>=3.2.5
|
|
18
19
|
Provides-Extra: dev
|
|
19
20
|
Requires-Dist: build; extra == 'dev'
|
|
20
|
-
Requires-Dist: folium==0.
|
|
21
|
-
Requires-Dist: matplotlib==3.10.
|
|
22
|
-
Requires-Dist: pre-commit==4.
|
|
23
|
-
Requires-Dist: pytest==8.
|
|
21
|
+
Requires-Dist: folium==0.20.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: matplotlib==3.10.5; extra == 'dev'
|
|
23
|
+
Requires-Dist: pre-commit==4.2.0; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest==8.4.1; extra == 'dev'
|
|
24
25
|
Requires-Dist: readthedocs-sphinx-search>=0.3.2; extra == 'dev'
|
|
25
26
|
Requires-Dist: ruff==0.11.5; extra == 'dev'
|
|
26
27
|
Requires-Dist: sphinx-gallery==0.19.0; extra == 'dev'
|
|
@@ -33,10 +34,10 @@ Description-Content-Type: text/markdown
|
|
|
33
34
|
|
|
34
35
|
[](https://github.com/astral-sh/ruff)
|
|
35
36
|
<a href="https://github.com/eurocontrol-bada/pybada/blob/main/LICENCE.txt"><img alt="License: EUPL" src="https://img.shields.io/badge/license-EUPL-3785D1.svg"></a>
|
|
36
|
-
<a href="https://pypi.org/project/pyBADA"><img alt="Released on PyPi" src="https://img.shields.io/pypi/v/pyBADA.svg"></a>
|
|
37
|
-

|
|
37
|
+
<a href="https://pypi.org/project/pyBADA"><img alt="Released on PyPi" src="https://img.shields.io/pypi/v/pyBADA.svg"></a> <img alt="PyPI - Downloads" src="https://img.shields.io/pypi/dw/pybada"> 
|
|
38
38
|
<a href="https://github.com/eurocontrol-bada/pybada"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
|
|
39
39
|
[](https://github.com/eurocontrol-bada/pybada/actions/workflows/pytest.yml)
|
|
40
|
+

|
|
40
41
|
|
|
41
42
|
This package provides aircraft performance modelling, trajectory prediction and optimisation, and visualisation with [BADA](https://www.eurocontrol.int/model/bada) in Python.
|
|
42
43
|
|
|
@@ -89,4 +90,5 @@ You won't receive support for it, but you can pass the flag `--ignore-requires-p
|
|
|
89
90
|
|
|
90
91
|
BADA and pyBADA are developed and maintained by [EUROCONTROL](https://www.eurocontrol.int/model/bada).
|
|
91
92
|
|
|
92
|
-
This project is released under the European Union Public License v1.2 - see the [
|
|
93
|
+
This project is released under the European Union Public License v1.2 - see the [LICENCE](https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12) file for details.
|
|
94
|
+
See the [Amendment to the EUPL](./AMENDMENT_TO_EUPL_license.md) for additional terms.
|
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
pyBADA/TCL.py,sha256=
|
|
1
|
+
pyBADA/TCL.py,sha256=0X6giDdZi-T5pr1l0x5zBoVkS5MjrxYVWZq-O6UAxsA,373536
|
|
2
2
|
pyBADA/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
pyBADA/aircraft.py,sha256=
|
|
4
|
-
pyBADA/atmosphere.py,sha256=
|
|
5
|
-
pyBADA/bada3.py,sha256=
|
|
6
|
-
pyBADA/bada4.py,sha256=
|
|
7
|
-
pyBADA/badaH.py,sha256=
|
|
8
|
-
pyBADA/configuration.py,sha256=
|
|
3
|
+
pyBADA/aircraft.py,sha256=VBgavT6CO05W941ts3Rm93SC-wUZcx8QbEey1QPxiZ0,13009
|
|
4
|
+
pyBADA/atmosphere.py,sha256=6aYd6bwXcN_HcoHk77-dMMFkoNTT7XUPiCVpRiurUtY,19589
|
|
5
|
+
pyBADA/bada3.py,sha256=r342J8qcoO7Otp2CzYdItRABVcjVWyl142XJ1-inu9I,186825
|
|
6
|
+
pyBADA/bada4.py,sha256=O2ruf7I2oIFp3NeNcabLriFci48TFTJ5RKNfhyXEpPg,223355
|
|
7
|
+
pyBADA/badaH.py,sha256=9PQWky2tE876rXrK9VlPKnQOJlCvi8bpqquOVZQeDDY,153049
|
|
8
|
+
pyBADA/configuration.py,sha256=j4PxLEByx5hP-JDV7FegqgzS0Tk_6rVBrs8DJC42o-Y,6506
|
|
9
9
|
pyBADA/constants.py,sha256=zJhiLcG49Ab2i-c_mHNCmfLpecmQozvIOjX71aVB5KE,952
|
|
10
|
-
pyBADA/conversions.py,sha256=
|
|
11
|
-
pyBADA/flightTrajectory.py,sha256=
|
|
12
|
-
pyBADA/geodesic.py,sha256=
|
|
13
|
-
pyBADA/magnetic.py,sha256=
|
|
14
|
-
pyBADA/trajectoryPrediction.py,sha256=
|
|
10
|
+
pyBADA/conversions.py,sha256=pfOPAgpQmHPOLUPnOnUTAo2lxj5XE0dZ_dWTzYbsy7M,4358
|
|
11
|
+
pyBADA/flightTrajectory.py,sha256=g8J18o17lUmcF7cd4bHvBUn9el6ywn1628biDF2LAsQ,38484
|
|
12
|
+
pyBADA/geodesic.py,sha256=sC1GTqDkrdFNhzTr8xn4tdEqbdoIijvRmKSsL1ObyOw,34626
|
|
13
|
+
pyBADA/magnetic.py,sha256=svmwNEwfuiDIQChQxmfqNd9tyNHjnAK1lYsTYy_RYio,4997
|
|
14
|
+
pyBADA/trajectoryPrediction.py,sha256=8mkwRoXcmUyw1UlRbg-gRLE5BmSlubI3h_c8aPpKS6A,6946
|
|
15
|
+
pyBADA/utils.py,sha256=ULW1n_Rm30kGrBGG97-yd8V1SJQw8Y6NdEVIEq_xVhc,7029
|
|
15
16
|
pyBADA/data/magneticDeclinationGridData.json,sha256=ffmLcxdDwFwJqV4WPyMM5SWt60tMj8xsLh5IHYkpO3U,5390918
|
|
16
17
|
pyBADA/aircraft/BADA3/DUMMY/BADA.GPF,sha256=1Am9yU8v9nnIpvJt-XhJRz4APOkRzfNPujKAy3UCYg0,9831
|
|
17
18
|
pyBADA/aircraft/BADA3/DUMMY/BZJT__.APF,sha256=wGyXK-ro1S6YhC4wp2nMQKi0JGYLvTunNjO6x_YbhW4,2523
|
|
@@ -90,8 +91,8 @@ pyBADA/aircraft/BADAH/DUMMY/DUMH/DUMH_ISA.PTF,sha256=aEthZF1xztp6QSHYEwU5tH4HF8x
|
|
|
90
91
|
pyBADA/aircraft/BADAH/DUMMY/DUMH/LRC.OPT,sha256=7WecIu4kfw5nM_ADih06Hb8pCoxLVsEdHHJTqQrx4hg,10123
|
|
91
92
|
pyBADA/aircraft/BADAH/DUMMY/DUMH/MEC.OPT,sha256=yKczjH6lZqTplmcV79tZLvwXmHM2F9bYoB2gIM8hBpg,10123
|
|
92
93
|
pyBADA/aircraft/BADAH/DUMMY/DUMH/MRC.OPT,sha256=fTGqt0P9xgt9Q4sKPlL0CZi9aj73prAPlXj1dpWHSOk,10123
|
|
93
|
-
pybada-0.1.
|
|
94
|
-
pybada-0.1.
|
|
95
|
-
pybada-0.1.
|
|
96
|
-
pybada-0.1.
|
|
97
|
-
pybada-0.1.
|
|
94
|
+
pybada-0.1.6.dist-info/METADATA,sha256=aG80WMHd3JzEGGPQNBXGeNZnkXhCmcRAIIFQyACxn60,3853
|
|
95
|
+
pybada-0.1.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
96
|
+
pybada-0.1.6.dist-info/licenses/AUTHORS,sha256=iCKpU7CHp2sB4u5hkS2WyMJHLzL0gfMm-bobd7QDmzE,108
|
|
97
|
+
pybada-0.1.6.dist-info/licenses/LICENCE.txt,sha256=RpvAZSjULHvoTR_esTlucJ08-zdQydnoqQLbqOh9Ub8,13826
|
|
98
|
+
pybada-0.1.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|