pycontrails 0.59.0__cp314-cp314-macosx_10_15_x86_64.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.
Potentially problematic release.
This version of pycontrails might be problematic. Click here for more details.
- pycontrails/__init__.py +70 -0
- pycontrails/_version.py +34 -0
- pycontrails/core/__init__.py +30 -0
- pycontrails/core/aircraft_performance.py +679 -0
- pycontrails/core/airports.py +228 -0
- pycontrails/core/cache.py +889 -0
- pycontrails/core/coordinates.py +174 -0
- pycontrails/core/fleet.py +483 -0
- pycontrails/core/flight.py +2185 -0
- pycontrails/core/flightplan.py +228 -0
- pycontrails/core/fuel.py +140 -0
- pycontrails/core/interpolation.py +702 -0
- pycontrails/core/met.py +2936 -0
- pycontrails/core/met_var.py +387 -0
- pycontrails/core/models.py +1321 -0
- pycontrails/core/polygon.py +549 -0
- pycontrails/core/rgi_cython.cpython-314-darwin.so +0 -0
- pycontrails/core/vector.py +2249 -0
- pycontrails/datalib/__init__.py +12 -0
- pycontrails/datalib/_met_utils/metsource.py +746 -0
- pycontrails/datalib/ecmwf/__init__.py +73 -0
- pycontrails/datalib/ecmwf/arco_era5.py +345 -0
- pycontrails/datalib/ecmwf/common.py +114 -0
- pycontrails/datalib/ecmwf/era5.py +554 -0
- pycontrails/datalib/ecmwf/era5_model_level.py +490 -0
- pycontrails/datalib/ecmwf/hres.py +804 -0
- pycontrails/datalib/ecmwf/hres_model_level.py +466 -0
- pycontrails/datalib/ecmwf/ifs.py +287 -0
- pycontrails/datalib/ecmwf/model_levels.py +435 -0
- pycontrails/datalib/ecmwf/static/model_level_dataframe_v20240418.csv +139 -0
- pycontrails/datalib/ecmwf/variables.py +268 -0
- pycontrails/datalib/geo_utils.py +261 -0
- pycontrails/datalib/gfs/__init__.py +28 -0
- pycontrails/datalib/gfs/gfs.py +656 -0
- pycontrails/datalib/gfs/variables.py +104 -0
- pycontrails/datalib/goes.py +764 -0
- pycontrails/datalib/gruan.py +343 -0
- pycontrails/datalib/himawari/__init__.py +27 -0
- pycontrails/datalib/himawari/header_struct.py +266 -0
- pycontrails/datalib/himawari/himawari.py +671 -0
- pycontrails/datalib/landsat.py +589 -0
- pycontrails/datalib/leo_utils/__init__.py +5 -0
- pycontrails/datalib/leo_utils/correction.py +266 -0
- pycontrails/datalib/leo_utils/landsat_metadata.py +300 -0
- pycontrails/datalib/leo_utils/search.py +250 -0
- pycontrails/datalib/leo_utils/sentinel_metadata.py +748 -0
- pycontrails/datalib/leo_utils/static/bq_roi_query.sql +6 -0
- pycontrails/datalib/leo_utils/vis.py +59 -0
- pycontrails/datalib/sentinel.py +650 -0
- pycontrails/datalib/spire/__init__.py +5 -0
- pycontrails/datalib/spire/exceptions.py +62 -0
- pycontrails/datalib/spire/spire.py +604 -0
- pycontrails/ext/bada.py +42 -0
- pycontrails/ext/cirium.py +14 -0
- pycontrails/ext/empirical_grid.py +140 -0
- pycontrails/ext/synthetic_flight.py +431 -0
- pycontrails/models/__init__.py +1 -0
- pycontrails/models/accf.py +425 -0
- pycontrails/models/apcemm/__init__.py +8 -0
- pycontrails/models/apcemm/apcemm.py +983 -0
- pycontrails/models/apcemm/inputs.py +226 -0
- pycontrails/models/apcemm/static/apcemm_yaml_template.yaml +183 -0
- pycontrails/models/apcemm/utils.py +437 -0
- pycontrails/models/cocip/__init__.py +29 -0
- pycontrails/models/cocip/cocip.py +2742 -0
- pycontrails/models/cocip/cocip_params.py +305 -0
- pycontrails/models/cocip/cocip_uncertainty.py +291 -0
- pycontrails/models/cocip/contrail_properties.py +1530 -0
- pycontrails/models/cocip/output_formats.py +2270 -0
- pycontrails/models/cocip/radiative_forcing.py +1260 -0
- pycontrails/models/cocip/radiative_heating.py +520 -0
- pycontrails/models/cocip/unterstrasser_wake_vortex.py +508 -0
- pycontrails/models/cocip/wake_vortex.py +396 -0
- pycontrails/models/cocip/wind_shear.py +120 -0
- pycontrails/models/cocipgrid/__init__.py +9 -0
- pycontrails/models/cocipgrid/cocip_grid.py +2552 -0
- pycontrails/models/cocipgrid/cocip_grid_params.py +138 -0
- pycontrails/models/dry_advection.py +602 -0
- pycontrails/models/emissions/__init__.py +21 -0
- pycontrails/models/emissions/black_carbon.py +599 -0
- pycontrails/models/emissions/emissions.py +1353 -0
- pycontrails/models/emissions/ffm2.py +336 -0
- pycontrails/models/emissions/static/default-engine-uids.csv +239 -0
- pycontrails/models/emissions/static/edb-gaseous-v29b-engines.csv +596 -0
- pycontrails/models/emissions/static/edb-nvpm-v29b-engines.csv +215 -0
- pycontrails/models/extended_k15.py +1327 -0
- pycontrails/models/humidity_scaling/__init__.py +37 -0
- pycontrails/models/humidity_scaling/humidity_scaling.py +1075 -0
- pycontrails/models/humidity_scaling/quantiles/era5-model-level-quantiles.pq +0 -0
- pycontrails/models/humidity_scaling/quantiles/era5-pressure-level-quantiles.pq +0 -0
- pycontrails/models/issr.py +210 -0
- pycontrails/models/pcc.py +326 -0
- pycontrails/models/pcr.py +154 -0
- pycontrails/models/ps_model/__init__.py +18 -0
- pycontrails/models/ps_model/ps_aircraft_params.py +381 -0
- pycontrails/models/ps_model/ps_grid.py +701 -0
- pycontrails/models/ps_model/ps_model.py +1000 -0
- pycontrails/models/ps_model/ps_operational_limits.py +525 -0
- pycontrails/models/ps_model/static/ps-aircraft-params-20250328.csv +69 -0
- pycontrails/models/ps_model/static/ps-synonym-list-20250328.csv +104 -0
- pycontrails/models/sac.py +442 -0
- pycontrails/models/tau_cirrus.py +183 -0
- pycontrails/physics/__init__.py +1 -0
- pycontrails/physics/constants.py +117 -0
- pycontrails/physics/geo.py +1138 -0
- pycontrails/physics/jet.py +968 -0
- pycontrails/physics/static/iata-cargo-load-factors-20250221.csv +74 -0
- pycontrails/physics/static/iata-passenger-load-factors-20250221.csv +74 -0
- pycontrails/physics/thermo.py +551 -0
- pycontrails/physics/units.py +472 -0
- pycontrails/py.typed +0 -0
- pycontrails/utils/__init__.py +1 -0
- pycontrails/utils/dependencies.py +66 -0
- pycontrails/utils/iteration.py +13 -0
- pycontrails/utils/json.py +187 -0
- pycontrails/utils/temp.py +50 -0
- pycontrails/utils/types.py +163 -0
- pycontrails-0.59.0.dist-info/METADATA +179 -0
- pycontrails-0.59.0.dist-info/RECORD +123 -0
- pycontrails-0.59.0.dist-info/WHEEL +6 -0
- pycontrails-0.59.0.dist-info/licenses/LICENSE +178 -0
- pycontrails-0.59.0.dist-info/licenses/NOTICE +43 -0
- pycontrails-0.59.0.dist-info/top_level.txt +3 -0
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
"""Unit conversion support."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import numpy.typing as npt
|
|
7
|
+
|
|
8
|
+
from pycontrails.physics import constants
|
|
9
|
+
from pycontrails.utils.types import ArrayScalarLike, support_arraylike
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def pl_to_ft(pl: ArrayScalarLike) -> ArrayScalarLike:
|
|
13
|
+
r"""Convert from pressure level (hPa) to altitude (ft).
|
|
14
|
+
|
|
15
|
+
Assumes the ICAO standard atmosphere.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
pl : ArrayScalarLike
|
|
20
|
+
pressure level, [:math:`hPa`], [:math:`mbar`]
|
|
21
|
+
|
|
22
|
+
Returns
|
|
23
|
+
-------
|
|
24
|
+
ArrayScalarLike
|
|
25
|
+
altitude, [:math:`ft`]
|
|
26
|
+
|
|
27
|
+
See Also
|
|
28
|
+
--------
|
|
29
|
+
pl_to_m
|
|
30
|
+
ft_to_pl
|
|
31
|
+
m_to_T_isa
|
|
32
|
+
"""
|
|
33
|
+
return m_to_ft(pl_to_m(pl))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def ft_to_pl(h: ArrayScalarLike) -> ArrayScalarLike:
|
|
37
|
+
r"""Convert from altitude (ft) to pressure level (hPa).
|
|
38
|
+
|
|
39
|
+
Assumes the ICAO standard atmosphere.
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
h : ArrayScalarLike
|
|
44
|
+
altitude, [:math:`ft`]
|
|
45
|
+
|
|
46
|
+
Returns
|
|
47
|
+
-------
|
|
48
|
+
ArrayScalarLike
|
|
49
|
+
pressure level, [:math:`hPa`], [:math:`mbar`]
|
|
50
|
+
|
|
51
|
+
See Also
|
|
52
|
+
--------
|
|
53
|
+
m_to_pl
|
|
54
|
+
pl_to_ft
|
|
55
|
+
m_to_T_isa
|
|
56
|
+
"""
|
|
57
|
+
return m_to_pl(ft_to_m(h))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def kelvin_to_celsius(kelvin: ArrayScalarLike) -> ArrayScalarLike:
|
|
61
|
+
"""Convert temperature from Kelvin to Celsius.
|
|
62
|
+
|
|
63
|
+
Parameters
|
|
64
|
+
----------
|
|
65
|
+
kelvin : ArrayScalarLike
|
|
66
|
+
temperature [:math:`K`]
|
|
67
|
+
|
|
68
|
+
Returns
|
|
69
|
+
-------
|
|
70
|
+
ArrayScalarLike
|
|
71
|
+
temperature [:math:`C`]
|
|
72
|
+
"""
|
|
73
|
+
return kelvin + constants.absolute_zero
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def m_to_T_isa(h: ArrayScalarLike) -> ArrayScalarLike:
|
|
77
|
+
"""Calculate the ambient temperature (K) for a given altitude (m).
|
|
78
|
+
|
|
79
|
+
Assumes the ICAO standard atmosphere.
|
|
80
|
+
|
|
81
|
+
Parameters
|
|
82
|
+
----------
|
|
83
|
+
h : ArrayScalarLike
|
|
84
|
+
altitude, [:math:`m`]
|
|
85
|
+
|
|
86
|
+
Returns
|
|
87
|
+
-------
|
|
88
|
+
ArrayScalarLike
|
|
89
|
+
ICAO standard atmosphere ambient temperature, [:math:`K`]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
References
|
|
93
|
+
----------
|
|
94
|
+
- :cite:`wikipediacontributorsInternationalStandardAtmosphere2023`
|
|
95
|
+
|
|
96
|
+
Notes
|
|
97
|
+
-----
|
|
98
|
+
See https://en.wikipedia.org/wiki/International_Standard_Atmosphere
|
|
99
|
+
|
|
100
|
+
See Also
|
|
101
|
+
--------
|
|
102
|
+
m_to_pl
|
|
103
|
+
ft_to_pl
|
|
104
|
+
"""
|
|
105
|
+
h_min = np.minimum(h, constants.h_tropopause)
|
|
106
|
+
return constants.T_msl + h_min * constants.T_lapse_rate # type: ignore[return-value]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _low_altitude_m_to_pl(h: npt.NDArray[np.floating]) -> npt.NDArray[np.floating]:
|
|
110
|
+
T_isa = m_to_T_isa(h)
|
|
111
|
+
power_term = -constants.g / (constants.T_lapse_rate * constants.R_d)
|
|
112
|
+
return (constants.p_surface * (T_isa / constants.T_msl) ** power_term) / 100.0
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _high_altitude_m_to_pl(h: npt.NDArray[np.floating]) -> npt.NDArray[np.floating]:
|
|
116
|
+
T_tropopause_isa = m_to_T_isa(np.asarray(constants.h_tropopause))
|
|
117
|
+
power_term = -constants.g / (constants.T_lapse_rate * constants.R_d)
|
|
118
|
+
p_tropopause_isa = constants.p_surface * (T_tropopause_isa / constants.T_msl) ** power_term
|
|
119
|
+
inside_exp = (-constants.g / (constants.R_d * T_tropopause_isa)) * (h - constants.h_tropopause)
|
|
120
|
+
return p_tropopause_isa * np.exp(inside_exp) / 100.0
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@support_arraylike
|
|
124
|
+
def m_to_pl(h: npt.NDArray[np.floating]) -> npt.NDArray[np.floating]:
|
|
125
|
+
r"""Convert from altitude (m) to pressure level (hPa).
|
|
126
|
+
|
|
127
|
+
Parameters
|
|
128
|
+
----------
|
|
129
|
+
h : npt.NDArray[np.floating]
|
|
130
|
+
altitude, [:math:`m`]
|
|
131
|
+
|
|
132
|
+
Returns
|
|
133
|
+
-------
|
|
134
|
+
npt.NDArray[np.floating]
|
|
135
|
+
pressure level, [:math:`hPa`], [:math:`mbar`]
|
|
136
|
+
|
|
137
|
+
References
|
|
138
|
+
----------
|
|
139
|
+
- :cite:`wikipediacontributorsBarometricFormula2023`
|
|
140
|
+
|
|
141
|
+
Notes
|
|
142
|
+
-----
|
|
143
|
+
See https://en.wikipedia.org/wiki/Barometric_formula
|
|
144
|
+
|
|
145
|
+
See Also
|
|
146
|
+
--------
|
|
147
|
+
m_to_T_isa
|
|
148
|
+
ft_to_pl
|
|
149
|
+
"""
|
|
150
|
+
condlist = [h < constants.h_tropopause, h >= constants.h_tropopause]
|
|
151
|
+
funclist = [_low_altitude_m_to_pl, _high_altitude_m_to_pl, np.nan] # nan passed through
|
|
152
|
+
return np.piecewise(h, condlist, funclist)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _low_altitude_pl_to_m(pl: npt.NDArray[np.floating]) -> npt.NDArray[np.floating]:
|
|
156
|
+
base = 100.0 * pl / constants.p_surface
|
|
157
|
+
exponent = -constants.T_lapse_rate * constants.R_d / constants.g
|
|
158
|
+
T_isa = constants.T_msl * base**exponent
|
|
159
|
+
return (T_isa - constants.T_msl) / constants.T_lapse_rate
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _high_altitude_pl_to_m(pl: npt.NDArray[np.floating]) -> npt.NDArray[np.floating]:
|
|
163
|
+
T_tropopause_isa = m_to_T_isa(np.asarray(constants.h_tropopause))
|
|
164
|
+
power_term = -constants.g / (constants.T_lapse_rate * constants.R_d)
|
|
165
|
+
p_tropopause_isa = constants.p_surface * (T_tropopause_isa / constants.T_msl) ** power_term
|
|
166
|
+
inside_exp = np.log(pl * 100.0 / p_tropopause_isa)
|
|
167
|
+
return inside_exp / (-constants.g / (constants.R_d * T_tropopause_isa)) + constants.h_tropopause
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@support_arraylike
|
|
171
|
+
def pl_to_m(pl: npt.NDArray[np.floating]) -> npt.NDArray[np.floating]:
|
|
172
|
+
r"""Convert from pressure level (hPa) to altitude (m).
|
|
173
|
+
|
|
174
|
+
Function is slightly different from the classical formula:
|
|
175
|
+
``constants.T_msl / 0.0065) * (1 - (pl_pa / constants.p_surface) ** (1 / 5.255)``
|
|
176
|
+
in order to provide a mathematical inverse to :func:`m_to_pl`.
|
|
177
|
+
|
|
178
|
+
For low altitudes (below the tropopause), this implementation closely agrees to classical
|
|
179
|
+
formula.
|
|
180
|
+
|
|
181
|
+
Parameters
|
|
182
|
+
----------
|
|
183
|
+
pl : ArrayLike
|
|
184
|
+
pressure level, [:math:`hPa`], [:math:`mbar`]
|
|
185
|
+
|
|
186
|
+
Returns
|
|
187
|
+
-------
|
|
188
|
+
ArrayLike
|
|
189
|
+
altitude, [:math:`m`]
|
|
190
|
+
|
|
191
|
+
References
|
|
192
|
+
----------
|
|
193
|
+
- :cite:`wikipediacontributorsBarometricFormula2023`
|
|
194
|
+
|
|
195
|
+
Notes
|
|
196
|
+
-----
|
|
197
|
+
See https://en.wikipedia.org/wiki/Barometric_formula
|
|
198
|
+
|
|
199
|
+
See Also
|
|
200
|
+
--------
|
|
201
|
+
pl_to_ft
|
|
202
|
+
m_to_pl
|
|
203
|
+
m_to_T_isa
|
|
204
|
+
"""
|
|
205
|
+
pl_tropopause = m_to_pl(constants.h_tropopause)
|
|
206
|
+
condlist = [pl < pl_tropopause, pl >= pl_tropopause]
|
|
207
|
+
funclist = [_high_altitude_pl_to_m, _low_altitude_pl_to_m, np.nan] # nan passed through
|
|
208
|
+
return np.piecewise(pl, condlist, funclist)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def degrees_to_radians(degrees: ArrayScalarLike) -> ArrayScalarLike:
|
|
212
|
+
r"""Convert from degrees to radians.
|
|
213
|
+
|
|
214
|
+
Parameters
|
|
215
|
+
----------
|
|
216
|
+
degrees : ArrayScalarLike
|
|
217
|
+
Degrees values, [:math:`\deg`]
|
|
218
|
+
|
|
219
|
+
Returns
|
|
220
|
+
-------
|
|
221
|
+
ArrayScalarLike
|
|
222
|
+
Radians values
|
|
223
|
+
"""
|
|
224
|
+
return degrees * (np.pi / 180.0)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def radians_to_degrees(radians: ArrayScalarLike) -> ArrayScalarLike:
|
|
228
|
+
r"""Convert from radians to degrees.
|
|
229
|
+
|
|
230
|
+
Parameters
|
|
231
|
+
----------
|
|
232
|
+
radians : ArrayScalarLike
|
|
233
|
+
degrees values, [:math:`\rad`]
|
|
234
|
+
|
|
235
|
+
Returns
|
|
236
|
+
-------
|
|
237
|
+
ArrayScalarLike
|
|
238
|
+
Radian values
|
|
239
|
+
"""
|
|
240
|
+
return radians * (180.0 / np.pi)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def ft_to_m(ft: ArrayScalarLike) -> ArrayScalarLike:
|
|
244
|
+
"""Convert length from feet to meter.
|
|
245
|
+
|
|
246
|
+
Parameters
|
|
247
|
+
----------
|
|
248
|
+
ft : ArrayScalarLike
|
|
249
|
+
length, [:math:`ft`]
|
|
250
|
+
|
|
251
|
+
Returns
|
|
252
|
+
-------
|
|
253
|
+
ArrayScalarLike
|
|
254
|
+
length, [:math:`m`]
|
|
255
|
+
"""
|
|
256
|
+
return ft * 0.3048
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def m_to_ft(m: ArrayScalarLike) -> ArrayScalarLike:
|
|
260
|
+
"""Convert length from meters to feet.
|
|
261
|
+
|
|
262
|
+
Parameters
|
|
263
|
+
----------
|
|
264
|
+
m : ArrayScalarLike
|
|
265
|
+
length, [:math:`m`]
|
|
266
|
+
|
|
267
|
+
Returns
|
|
268
|
+
-------
|
|
269
|
+
ArrayScalarLike
|
|
270
|
+
length, [:math:`ft`]
|
|
271
|
+
"""
|
|
272
|
+
return m / 0.3048
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def m_per_s_to_knots(m_per_s: ArrayScalarLike) -> ArrayScalarLike:
|
|
276
|
+
r"""Convert speed from meters per second (m/s) to knots.
|
|
277
|
+
|
|
278
|
+
Parameters
|
|
279
|
+
----------
|
|
280
|
+
m_per_s : ArrayScalarLike
|
|
281
|
+
Speed, [:math:`m \ s^{-1}`]
|
|
282
|
+
|
|
283
|
+
Returns
|
|
284
|
+
-------
|
|
285
|
+
ArrayScalarLike
|
|
286
|
+
Speed, [:math:`knots`]
|
|
287
|
+
"""
|
|
288
|
+
return m_per_s / 0.514444
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def knots_to_m_per_s(knots: ArrayScalarLike) -> ArrayScalarLike:
|
|
292
|
+
r"""Convert speed from knots to meters per second (m/s).
|
|
293
|
+
|
|
294
|
+
Parameters
|
|
295
|
+
----------
|
|
296
|
+
knots : ArrayScalarLike
|
|
297
|
+
Speed, [:math:`knots`]
|
|
298
|
+
|
|
299
|
+
Returns
|
|
300
|
+
-------
|
|
301
|
+
ArrayScalarLike
|
|
302
|
+
Speed, [:math:`m \ s^{-1}`]
|
|
303
|
+
"""
|
|
304
|
+
return knots * 0.514444
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def longitude_distance_to_m(
|
|
308
|
+
distance_degrees: ArrayScalarLike, latitude_mean: ArrayScalarLike
|
|
309
|
+
) -> ArrayScalarLike:
|
|
310
|
+
r"""
|
|
311
|
+
Convert longitude degrees distance between two points to cartesian distances in meters.
|
|
312
|
+
|
|
313
|
+
Parameters
|
|
314
|
+
----------
|
|
315
|
+
distance_degrees : ArrayScalarLike
|
|
316
|
+
longitude distance, [:math:`\deg`]
|
|
317
|
+
latitude_mean : ArrayScalarLike, optional
|
|
318
|
+
mean latitude between ``longitude_1`` and ``longitude_2``, [:math:`\deg`]
|
|
319
|
+
|
|
320
|
+
Returns
|
|
321
|
+
-------
|
|
322
|
+
ArrayScalarLike
|
|
323
|
+
cartesian distance along the longitude axis, [:math:`m`]
|
|
324
|
+
"""
|
|
325
|
+
latitude_mean_rad = degrees_to_radians(latitude_mean)
|
|
326
|
+
return (distance_degrees / 180.0) * np.pi * constants.radius_earth * np.cos(latitude_mean_rad)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def latitude_distance_to_m(distance_degrees: ArrayScalarLike) -> ArrayScalarLike:
|
|
330
|
+
r"""
|
|
331
|
+
Convert latitude degrees distance between two points to cartesian distances in meters.
|
|
332
|
+
|
|
333
|
+
Parameters
|
|
334
|
+
----------
|
|
335
|
+
distance_degrees : ArrayScalarLike
|
|
336
|
+
latitude distance, [:math:`\deg`]
|
|
337
|
+
|
|
338
|
+
Returns
|
|
339
|
+
-------
|
|
340
|
+
ArrayScalarLike
|
|
341
|
+
Cartesian distance along the latitude axis, [:math:`m`]
|
|
342
|
+
"""
|
|
343
|
+
return (distance_degrees / 180.0) * np.pi * constants.radius_earth
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def m_to_longitude_distance(
|
|
347
|
+
distance_m: ArrayScalarLike, latitude_mean: ArrayScalarLike
|
|
348
|
+
) -> ArrayScalarLike:
|
|
349
|
+
r"""
|
|
350
|
+
Convert cartesian distance (meters) to differences in longitude degrees.
|
|
351
|
+
|
|
352
|
+
Small angle approximation for ``distance_m`` << :attr:`constants.radius_earth`
|
|
353
|
+
|
|
354
|
+
Parameters
|
|
355
|
+
----------
|
|
356
|
+
distance_m : ArrayScalarLike
|
|
357
|
+
cartesian distance along longitude axis, [:math:`m`]
|
|
358
|
+
latitude_mean : ArrayScalarLike
|
|
359
|
+
mean latitude between ``longitude_1`` and ``longitude_2``, [:math:`\deg`]
|
|
360
|
+
|
|
361
|
+
Returns
|
|
362
|
+
-------
|
|
363
|
+
ArrayScalarLike
|
|
364
|
+
longitude distance, [:math:`\deg`]
|
|
365
|
+
"""
|
|
366
|
+
return radians_to_degrees(
|
|
367
|
+
distance_m / (constants.radius_earth * np.cos(degrees_to_radians(latitude_mean)))
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def m_to_latitude_distance(distance_m: ArrayScalarLike) -> ArrayScalarLike:
|
|
372
|
+
r"""
|
|
373
|
+
Convert cartesian distance (meters) to differences in latitude degrees.
|
|
374
|
+
|
|
375
|
+
Small angle approximation for ``distance_m`` << :attr:`constants.radius_earth`
|
|
376
|
+
|
|
377
|
+
Parameters
|
|
378
|
+
----------
|
|
379
|
+
distance_m : ArrayScalarLike
|
|
380
|
+
cartesian distance along latitude axis, [:math:`m`]
|
|
381
|
+
|
|
382
|
+
Returns
|
|
383
|
+
-------
|
|
384
|
+
ArrayScalarLike
|
|
385
|
+
latitude distance, [:math:`\deg`]
|
|
386
|
+
"""
|
|
387
|
+
return radians_to_degrees(distance_m / constants.radius_earth)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def tas_to_mach_number(true_airspeed: ArrayScalarLike, T: ArrayScalarLike) -> ArrayScalarLike:
|
|
391
|
+
r"""Calculate Mach number from true airspeed at a specified ambient temperature.
|
|
392
|
+
|
|
393
|
+
Parameters
|
|
394
|
+
----------
|
|
395
|
+
true_airspeed : ArrayScalarLike
|
|
396
|
+
True airspeed, [:math:`m \ s^{-1}`]
|
|
397
|
+
T : ArrayScalarLike
|
|
398
|
+
Ambient temperature, [:math:`K`]
|
|
399
|
+
|
|
400
|
+
Returns
|
|
401
|
+
-------
|
|
402
|
+
ArrayScalarLike
|
|
403
|
+
Mach number, [:math: `Ma`]
|
|
404
|
+
|
|
405
|
+
References
|
|
406
|
+
----------
|
|
407
|
+
- :cite:`cumpstyJetPropulsion2015`
|
|
408
|
+
"""
|
|
409
|
+
return true_airspeed / np.sqrt((constants.kappa * constants.R_d) * T)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def mach_number_to_tas(
|
|
413
|
+
mach_number: float | npt.NDArray[np.floating], T: float | npt.NDArray[np.floating]
|
|
414
|
+
) -> float | npt.NDArray[np.floating]:
|
|
415
|
+
r"""Calculate true airspeed from the Mach number at a specified ambient temperature.
|
|
416
|
+
|
|
417
|
+
Parameters
|
|
418
|
+
----------
|
|
419
|
+
mach_number : float | npt.NDArray[np.floating]
|
|
420
|
+
Mach number, [:math: `Ma`]
|
|
421
|
+
T : npt.NDArray[np.floating]
|
|
422
|
+
Ambient temperature, [:math:`K`]
|
|
423
|
+
|
|
424
|
+
Returns
|
|
425
|
+
-------
|
|
426
|
+
npt.NDArray[np.floating]
|
|
427
|
+
True airspeed, [:math:`m \ s^{-1}`]
|
|
428
|
+
|
|
429
|
+
References
|
|
430
|
+
----------
|
|
431
|
+
- :cite:`cumpstyJetPropulsion2015`
|
|
432
|
+
"""
|
|
433
|
+
return mach_number * np.sqrt((constants.kappa * constants.R_d) * T)
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def lbs_to_kg(lbs: ArrayScalarLike) -> ArrayScalarLike:
|
|
437
|
+
r"""Convert mass from pounds (lbs) to kilograms (kg).
|
|
438
|
+
|
|
439
|
+
Parameters
|
|
440
|
+
----------
|
|
441
|
+
lbs : ArrayScalarLike
|
|
442
|
+
mass, pounds [:math:`lbs`]
|
|
443
|
+
|
|
444
|
+
Returns
|
|
445
|
+
-------
|
|
446
|
+
ArrayScalarLike
|
|
447
|
+
mass, kilograms [:math:`kg`]
|
|
448
|
+
"""
|
|
449
|
+
return lbs * 0.45359
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def dt_to_seconds(
|
|
453
|
+
dt: npt.NDArray[np.timedelta64] | np.timedelta64,
|
|
454
|
+
dtype: npt.DTypeLike = np.float64,
|
|
455
|
+
) -> npt.NDArray[np.floating]:
|
|
456
|
+
"""Convert a time delta to seconds as a float with specified ``dtype`` precision.
|
|
457
|
+
|
|
458
|
+
Parameters
|
|
459
|
+
----------
|
|
460
|
+
dt : np.ndarray
|
|
461
|
+
Time delta for each waypoint
|
|
462
|
+
dtype : np.dtype
|
|
463
|
+
Data type of the output array
|
|
464
|
+
|
|
465
|
+
Returns
|
|
466
|
+
-------
|
|
467
|
+
np.ndarray
|
|
468
|
+
Time delta in seconds as a float
|
|
469
|
+
"""
|
|
470
|
+
out = np.empty(dt.shape, dtype=dtype)
|
|
471
|
+
np.divide(dt, np.timedelta64(1, "s"), out=out)
|
|
472
|
+
return out
|
pycontrails/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Misc re-usable utilities."""
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Raise ``ImportError`` when dependencies are not met."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import NoReturn
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def raise_module_not_found_error(
|
|
9
|
+
name: str,
|
|
10
|
+
package_name: str,
|
|
11
|
+
module_not_found_error: ImportError,
|
|
12
|
+
pycontrails_optional_package: str | None = None,
|
|
13
|
+
extra: str | None = None,
|
|
14
|
+
) -> NoReturn:
|
|
15
|
+
"""Raise ``ImportError`` with a helpful message.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
name : str
|
|
20
|
+
The name describing the context of the ``ImportError``. For example,
|
|
21
|
+
if the module is required for a specific function, the name could be
|
|
22
|
+
"my_function function". If the module is required for a specific method,
|
|
23
|
+
the name could be "MyClass.my_method method". If the module is required
|
|
24
|
+
for an entire ``pycontrails`` module, the name could be "my_module module".
|
|
25
|
+
package_name : str
|
|
26
|
+
The name of the package that is required. This should be the full name of
|
|
27
|
+
the python package, which may be different from the name of the module
|
|
28
|
+
that is actually imported. For example, if ``import sklearn`` triggers
|
|
29
|
+
the ``ImportError``, the ``package_name`` should be "scikit-learn".
|
|
30
|
+
module_not_found_error : ImportError
|
|
31
|
+
The ``ImportError`` that was raised. This is passed to the
|
|
32
|
+
``from`` clause of the ``raise`` statement below. The subclass of the
|
|
33
|
+
``ImportError`` is preserved (e.g., ``ModuleNotFoundError`` or
|
|
34
|
+
``ImportError``).
|
|
35
|
+
pycontrails_optional_package : str, optional
|
|
36
|
+
The name of the optional ``pycontrails`` package that can be used to
|
|
37
|
+
install the required package. See the ``pyproject.toml`` file.
|
|
38
|
+
extra : str, optional
|
|
39
|
+
Any extra information that should be included in the error message.
|
|
40
|
+
This is appended to the end of the error message.
|
|
41
|
+
"""
|
|
42
|
+
# Put the function or method or module name in quotes if the full name
|
|
43
|
+
# contains a space.
|
|
44
|
+
try:
|
|
45
|
+
n1, n2 = name.split(" ")
|
|
46
|
+
except ValueError:
|
|
47
|
+
if "'" not in name:
|
|
48
|
+
name = f"'{name}'"
|
|
49
|
+
else:
|
|
50
|
+
if "'" not in n1:
|
|
51
|
+
n1 = f"'{n1}'"
|
|
52
|
+
name = f"{n1} {n2}"
|
|
53
|
+
|
|
54
|
+
msg = (
|
|
55
|
+
f"The {name} requires the '{package_name}' package. "
|
|
56
|
+
f"This can be installed with 'pip install {package_name}'"
|
|
57
|
+
)
|
|
58
|
+
if pycontrails_optional_package:
|
|
59
|
+
msg = f"{msg} or 'pip install pycontrails[{pycontrails_optional_package}]'."
|
|
60
|
+
else:
|
|
61
|
+
msg = f"{msg}."
|
|
62
|
+
|
|
63
|
+
if extra:
|
|
64
|
+
msg = f"{msg} {extra}"
|
|
65
|
+
|
|
66
|
+
raise type(module_not_found_error)(msg) from module_not_found_error
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Utilites for iterating of sequences."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterator
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def chunk_list(lst: list[Any], n: int) -> Iterator[list[Any]]:
|
|
10
|
+
"""Yield successive n-sized chunks from list."""
|
|
11
|
+
|
|
12
|
+
for i in range(0, len(lst), n):
|
|
13
|
+
yield lst[i : i + n]
|