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/configuration.py CHANGED
@@ -2,8 +2,9 @@
2
2
  Common configuration module
3
3
  """
4
4
 
5
- import os
6
5
  import importlib.resources
6
+ import os
7
+
7
8
  import pandas as pd
8
9
 
9
10
 
pyBADA/conversions.py CHANGED
@@ -2,158 +2,131 @@
2
2
  Common unit conversions module
3
3
  """
4
4
 
5
- from math import pi
6
5
  from datetime import datetime
6
+ from math import pi
7
7
  from time import mktime
8
8
 
9
+ import numpy as np
10
+ import xarray as xr
11
+ import pandas as pd
12
+
13
+ from pyBADA import utils
14
+
15
+
16
+ def _unit_converter(factor):
17
+ """
18
+ Returns a function that multiplies its input by `factor`,
19
+ vectorized through utils._extract / utils._wrap.
20
+ """
21
+ def converter(val):
22
+ # pull out the raw array
23
+ arr = utils._extract(val)
24
+ # do the multiplication
25
+ core = arr * factor
26
+ # pass both the result and the original to _wrap
27
+ return utils._wrap(core, original=val)
28
+ return converter
29
+
30
+ # Linear unit conversions (factor-based)
31
+ _factor_map = {
32
+ "ft2m": 0.3048,
33
+ "nm2m": 1852.0,
34
+ "h2s": 3600.0,
35
+ "kt2ms": 0.514444,
36
+ "lb2kg": 0.453592,
37
+ "deg2rad": pi / 180.0,
38
+ "rad2deg": 180.0 / pi,
39
+ "m2ft": 1 / 0.3048,
40
+ "m2nm": 1 / 1852.0,
41
+ "s2h": 1 / 3600.0,
42
+ "ms2kt": 1 / 0.514444,
43
+ "kg2lb": 1 / 0.453592,
44
+ "hp2W": 745.699872,
45
+ }
9
46
 
10
- def ft2m(val):
11
- """
12
- This function converts from ft to m s
13
-
14
- :param val: value in ft
15
- :returns: vaue in m
16
- """
17
- return round(float(val) * 0.3048, 10)
18
-
19
-
20
- def nm2m(val):
21
- """
22
- This function converts from nautical miles to m
23
-
24
- :param val: value in nautical miles
25
- :returns: vaue in m
26
- """
27
- return val * 1852.0
28
-
29
-
30
- def h2s(val):
31
- """
32
- This function converts from hours to m seconds
33
-
34
- :param val: value in hours
35
- :returns: vaue in seconds
36
- """
37
- return val * 3600.0
38
-
39
-
40
- def kt2ms(val):
41
- """
42
- This function converts from kt to m s^-1
43
-
44
- :param val: value in kt
45
- :returns: vaue in m s^-1
46
- """
47
-
48
- if val is None:
49
- return None
50
- else:
51
- return round(float(val) * 0.514444, 10)
52
-
53
-
54
- def lb2kg(val):
55
- """This function converts from lb to kg.
56
-
57
- :param val: value in lb
58
- :returns: vaue in kg
59
- """
60
- return val * 0.453592
61
-
62
-
63
- def deg2rad(val):
64
- """This function converts from decimal degrees to radians.
65
-
66
- :param val: value in decimal degrees
67
- :returns: vaue in radians
68
- """
69
- return val * pi / 180.0
70
-
71
-
72
- def m2ft(val):
73
- """This function converts from meters to feets.
74
-
75
- :param val: value in meters
76
- :returns: value in feets
77
- """
78
-
79
- return round(float(val) / 0.3048, 10)
80
-
81
-
82
- def m2nm(val):
83
- """This function converts from meters to nautical miles.
84
-
85
- :param val: value in meters
86
- :returns: value in nautical miles
87
- """
88
- return val / 1852.0
89
-
90
-
91
- def s2h(val):
92
- """This function converts from seconds to hours.
93
-
94
- :param val: value in seconds
95
- :returns: value in hours
96
- """
97
- return val / 3600.0
98
-
99
-
100
- def ms2kt(val):
101
- """This function converts from m s^-1 to kt.
102
-
103
- :param val: value in m s^-1
104
- :returns: value in kt
105
- """
106
-
107
- if val is None:
108
- return None
109
- else:
110
- return round(float(val) / 0.514444, 10)
111
-
112
-
113
- def kg2lb(val):
114
- """This function converts from kg to lb.
115
-
116
- :param val: value in kg
117
- :returns: value in lb
118
- """
119
- return val / 0.453592
120
-
121
-
122
- def rad2deg(val):
123
- """This function converts from radians to decimal degrees.
124
-
125
- :param val: value in radians
126
- :returns: value in decimal degrees
127
- """
128
- return val / pi * 180.0
129
-
130
-
131
- def hp2W(val):
132
- """This function converts from horsepower to watts.
133
-
134
- :param val: value in horsepower
135
- :returns: value in watts
136
- """
137
- return val * 745.699872
47
+ # Dynamically create each converter in the module namespace
48
+ for _name, _factor in _factor_map.items():
49
+ globals()[_name] = _unit_converter(_factor)
138
50
 
139
51
 
140
52
  def date2posix(val):
141
- """This function converts a date format to posix.
142
-
143
- :param val: date in %Y-%m-%d %H:%M:%S format
144
- :returns: posix time referenced to 01-01-1970 [s]
145
53
  """
146
- return mktime(datetime.strptime(val, "%Y-%m-%d %H:%M:%S").timetuple())
54
+ Convert date(s) to POSIX timestamp in seconds since 1970-01-01, vectorized for
55
+ numpy arrays, pandas Series/DataFrame, and xarray.DataArray.
56
+
57
+ :param val: Input date(s). Can be string, datetime, numpy array of strings/datetimes,
58
+ pandas Series/DataFrame of datetime-like, or xarray.DataArray of datetime64.
59
+ :type val: str or datetime or array-like or pandas Series/DataFrame or xarray.DataArray
60
+ :returns: POSIX timestamp(s) in seconds, matching input type.
61
+ """
62
+ # xarray DataArray of datetime64
63
+ if isinstance(val, xr.DataArray):
64
+ out = val.astype('datetime64[s]').astype(int)
65
+ out.attrs.update(units='s', long_name='POSIX timestamp')
66
+ return out
67
+
68
+ if isinstance(val, (pd.Series, pd.DataFrame)):
69
+ ts = pd.to_datetime(val)
70
+ arr = ts.values.astype('datetime64[s]').astype(int)
71
+ if isinstance(val, pd.Series):
72
+ return pd.Series(arr, index=val.index, name=val.name)
73
+ return pd.DataFrame(arr, index=val.index, columns=val.columns)
74
+
75
+ if isinstance(val, str):
76
+ dt = datetime.strptime(val, "%Y-%m-%d %H:%M:%S")
77
+ return mktime(dt.timetuple())
78
+
79
+ try:
80
+ if isinstance(val, datetime):
81
+ return mktime(val.timetuple())
82
+ except NameError:
83
+ pass
84
+
85
+ arr = np.asarray(val)
86
+ try:
87
+ dt64 = arr.astype('datetime64[s]')
88
+ return dt64.astype(int)
89
+ except Exception:
90
+ flat = arr.ravel()
91
+ result = []
92
+ for v in flat:
93
+ if isinstance(v, str):
94
+ dt = datetime.strptime(v, "%Y-%m-%d %H:%M:%S")
95
+ result.append(mktime(dt.timetuple()))
96
+ else:
97
+ result.append(mktime(v.timetuple()))
98
+ out = np.array(result)
99
+ return out.reshape(arr.shape)
147
100
 
148
101
 
149
102
  def unix2date(val):
150
- """This function converts posix to date format.
151
-
152
- :param val: time referenced to 01-01-1970 [s]
153
- :returns: date in %Y-%m-%d %H:%M:%S format
154
103
  """
155
- return datetime.fromtimestamp(int(val)).strftime("%Y-%m-%d %H:%M:%S")
156
-
104
+ Convert POSIX timestamp(s) in seconds to date string(s) in "%Y-%m-%d %H:%M:%S" format, vectorized for
105
+ numpy arrays, pandas Series/DataFrame, and xarray.DataArray.
106
+
107
+ :param val: Input POSIX timestamp(s). Float, numpy array, pandas Series/DataFrame of floats,
108
+ or xarray.DataArray of ints.
109
+ :type val: float or array-like or pandas Series/DataFrame or xarray.DataArray
110
+ :returns: Date string(s) in "%Y-%m-%d %H:%M:%S", matching input type.
111
+ """
112
+ if isinstance(val, xr.DataArray):
113
+ dt64 = val.astype('datetime64[s]')
114
+ return dt64.dt.strftime("%Y-%m-%d %H:%M:%S")
115
+
116
+ if isinstance(val, (pd.Series, pd.DataFrame)):
117
+ ts = pd.to_datetime(val, unit='s')
118
+ return ts.dt.strftime("%Y-%m-%d %H:%M:%S")
119
+
120
+ arr = np.asarray(val, dtype=float)
121
+ flat = arr.ravel()
122
+ result = []
123
+ for v in flat:
124
+ dt = datetime.fromtimestamp(int(v))
125
+ result.append(dt.strftime("%Y-%m-%d %H:%M:%S"))
126
+ res_arr = np.array(result)
127
+ if res_arr.ndim == 0:
128
+ return res_arr.item()
129
+ return res_arr.reshape(arr.shape)
157
130
 
158
131
  convertFrom = {
159
132
  "unix": unix2date,
@@ -2,8 +2,9 @@
2
2
  Generic flight trajectory module
3
3
  """
4
4
 
5
- import os
6
5
  import datetime
6
+ import os
7
+
7
8
  import pandas as pd
8
9
  import simplekml
9
10
 
pyBADA/geodesic.py CHANGED
@@ -3,23 +3,129 @@ Geodesic calculation module
3
3
  """
4
4
 
5
5
  from math import (
6
- tan,
7
- atan2,
8
- sin,
9
6
  asin,
7
+ atan,
8
+ atan2,
10
9
  cos,
11
- radians,
12
10
  degrees,
13
- sqrt,
14
- pi,
15
11
  log,
16
12
  log2,
13
+ pi,
14
+ radians,
15
+ sin,
16
+ sqrt,
17
+ tan,
17
18
  )
18
- from pyBADA.aircraft import Airplane as airplane
19
+
19
20
  from pyBADA import constants as const
21
+ from pyBADA import conversions as conv
22
+ from pyBADA.aircraft import Airplane as airplane
23
+
24
+ class GeodesicCommon:
25
+ @classmethod
26
+ def requiredSlope(cls, waypoint_init, waypoint_final):
27
+ """
28
+ Calculate the climb/descent slope and horizontal distance between
29
+ two WGS84 waypoints with pressure altitude.
30
+
31
+ :param waypoint_init: dict with keys 'latitude', 'longitude', 'altitude' (ft)
32
+ :param waypoint_final: dict with keys 'latitude', 'longitude', 'altitude' (ft)
33
+ :type waypoint_init: dict[str, float]
34
+ :type waypoint_final: dict[str, float]
35
+ :return: (slope_degrees, distance_meters)
36
+ :rtype: (float, float)
37
+ """
38
+
39
+ dist = cls.distance(waypoint_init['latitude'], waypoint_init['longitude'], waypoint_final['latitude'], waypoint_final['longitude'])
40
+ if dist == 0:
41
+ raise ValueError("Waypoints must be distinct (distance = 0)")
42
+
43
+ delta_h = conv.ft2m(waypoint_final['altitude'] - waypoint_init['altitude'])
44
+ slope = degrees(atan(delta_h / dist))
45
+ return slope, dist
46
+
47
+ @staticmethod
48
+ def finalAltitudeApplyingSlopeForDistance(
49
+ altitude: float,
50
+ slope: float,
51
+ distance: float
52
+ ) -> float:
53
+ """
54
+ Calculate the final pressure altitude after applying a constant
55
+ climb/descent slope over a horizontal distance.
56
+
57
+ :param delta_h_ft: Initial pressure altitude in feet
58
+ :param slope: Flight‐path angle in degrees
59
+ (positive for climb, negative for descent)
60
+ :param distance: Horizontal distance to travel in nautical miles
61
+ :type altitude: float
62
+ :type slope: float
63
+ :type distance: float
64
+ :return: Final pressure altitude in feet
65
+ :rtype: float
66
+ """
67
+
68
+ horizontal_m = conv.nm2m(distance)
69
+ delta_h_ft = conv.m2ft(tan(radians(slope)) * horizontal_m)
70
+ return altitude + delta_h_ft
71
+
72
+ @classmethod
73
+ def destinationPointApplyingSlopeForDistance(
74
+ cls,
75
+ waypoint_init: dict,
76
+ slope: float,
77
+ distance: float,
78
+ bearing: float
79
+ ) -> dict:
80
+ """
81
+ Calculate the destination waypoint after traveling a horizontal
82
+ distance from an initial WGS84 waypoint on a given bearing and
83
+ applying a constant climb/descent slope.
84
+
85
+ :param waypoint_init: Initial waypoint, as a dict containing:
86
+ - 'latitude': Latitude in decimal degrees
87
+ - 'longitude': Longitude in decimal degrees
88
+ - 'altitude': Pressure altitude in feet
89
+ :param slope: Flight‐path angle in degrees
90
+ (positive for climb, negative for descent)
91
+ :param distance: Horizontal distance to travel from the initial
92
+ point in nautical miles
93
+ :param bearing: Initial bearing (direction) in degrees from
94
+ true north
95
+ :type waypoint_init: dict[str, float]
96
+ :type slope: float
97
+ :type distance: float
98
+ :type bearing: float
99
+ :return: Destination waypoint with keys:
100
+ - 'latitude': Destination latitude in decimal degrees
101
+ - 'longitude': Destination longitude in decimal degrees
102
+ - 'altitude': Final pressure altitude in feet
103
+ :rtype: dict[str, float]
104
+ """
105
+
106
+ horizontal_dist_m = conv.nm2m(distance)
107
+
108
+ dest_lat, dest_lon = cls.destinationPoint(
109
+ waypoint_init['latitude'],
110
+ waypoint_init['longitude'],
111
+ horizontal_dist_m,
112
+ bearing
113
+ )
114
+
115
+ final_alt_ft = cls.finalAltitudeApplyingSlopeForDistance(
116
+ waypoint_init['altitude'],
117
+ slope,
118
+ distance
119
+ )
120
+
121
+ return {
122
+ 'latitude': dest_lat,
123
+ 'longitude': dest_lon,
124
+ 'altitude': final_alt_ft
125
+ }
20
126
 
21
127
 
22
- class Haversine:
128
+ class Haversine(GeodesicCommon):
23
129
  """This class implements the geodesic calculations on sherical earth
24
130
  (ignoring ellipsoidal effects).
25
131
 
@@ -146,7 +252,7 @@ class Haversine:
146
252
  return bearing
147
253
 
148
254
 
149
- class Vincenty:
255
+ class Vincenty(GeodesicCommon):
150
256
  """This class implements the vincenty calculations of geodesics on the
151
257
  ellipsoid-model earth.
152
258
 
@@ -492,7 +598,7 @@ class Vincenty:
492
598
  return (dest[0], dest[1])
493
599
 
494
600
 
495
- class RhumbLine:
601
+ class RhumbLine(GeodesicCommon):
496
602
  """This class implements the rhumb line (loxodrome) calculations of
497
603
  geodesics on the ellipsoid-model earth.
498
604
 
pyBADA/magnetic.py CHANGED
@@ -2,8 +2,9 @@
2
2
  Magnetic declination module
3
3
  """
4
4
 
5
- import json
6
5
  import bisect
6
+ import json
7
+
7
8
  from pyBADA import configuration
8
9
 
9
10
 
@@ -2,28 +2,29 @@
2
2
  Basic calculations for the Trajectory Prediction (TP) using BADA
3
3
  """
4
4
 
5
- from pyBADA import atmosphere as atm
6
5
  from math import exp
7
6
 
7
+ from pyBADA import atmosphere as atm
8
+
8
9
 
9
- def cruiseFuelConsumption(AC, altitude, M, deltaTemp):
10
+ def cruiseFuelConsumption(AC, altitude, M, DeltaTemp):
10
11
  """
11
12
  Calculate the cruise fuel consumption for an aircraft during cruise flight using BADA.
12
13
 
13
14
  :param AC: Aircraft object (instance of Bada3Aircraft, Bada4Aircraft, or BadaHAircraft).
14
15
  :param altitude: Altitude in meters.
15
16
  :param M: Mach number at cruising altitude.
16
- :param deltaTemp: Temperature deviation from standard atmosphere.
17
+ :param DeltaTemp: Temperature deviation from standard atmosphere.
17
18
  :type AC: object
18
19
  :type altitude: float
19
20
  :type M: float
20
- :type deltaTemp: float
21
+ :type DeltaTemp: float
21
22
  :return: Fuel flow in kg/s.
22
23
  :rtype: float
23
24
  """
24
25
 
25
26
  [theta, delta, sigma] = atm.atmosphereProperties(
26
- h=altitude, DeltaTemp=deltaTemp
27
+ h=altitude, DeltaTemp=DeltaTemp
27
28
  )
28
29
  TAS = atm.mach2Tas(Mach=M, theta=theta)
29
30
 
@@ -62,7 +63,7 @@ def cruiseFuelConsumption(AC, altitude, M, deltaTemp):
62
63
  CT = AC.CT(Thrust=THR, delta=delta)
63
64
 
64
65
  fuelFlow = AC.ff(
65
- CT=CT, delta=delta, theta=theta, M=M, deltaTemp=deltaTemp
66
+ CT=CT, delta=delta, theta=theta, M=M, DeltaTemp=DeltaTemp
66
67
  ) # [kg/s]
67
68
 
68
69
  elif AC.BADAFamily.BADAH:
@@ -131,7 +132,7 @@ def getInitialMass(
131
132
  payload=60,
132
133
  fuelReserve=3600,
133
134
  flightPlanInitialMass=None,
134
- deltaTemp=0,
135
+ DeltaTemp=0,
135
136
  ):
136
137
  """Calculates the estimated initial aircraft mass assumig cruise phase,
137
138
  combining flight plan data, aircraft envelope constraints, and an
@@ -147,7 +148,7 @@ def getInitialMass(
147
148
  or 1 hour).
148
149
  :param flightPlanInitialMass: Optional initial mass from a flight plan, in
149
150
  kg.
150
- :param deltaTemp: Temperature deviation from standard atmosphere.
151
+ :param DeltaTemp: Temperature deviation from standard atmosphere.
151
152
  :type AC: object
152
153
  :type distance: float
153
154
  :type altitude: float
@@ -155,7 +156,7 @@ def getInitialMass(
155
156
  :type payload: float, optional
156
157
  :type fuelReserve: float, optional
157
158
  :type flightPlanInitialMass: float, optional
158
- :type deltaTemp: float, optional
159
+ :type DeltaTemp: float, optional
159
160
  :return: Estimated initial aircraft mass in kg.
160
161
  :rtype: float
161
162
  """
@@ -166,7 +167,7 @@ def getInitialMass(
166
167
  else:
167
168
  # in case of no wind, the ground speed is the same as true airspeed
168
169
  [theta, delta, sigma] = atm.atmosphereProperties(
169
- h=altitude, DeltaTemp=deltaTemp
170
+ h=altitude, DeltaTemp=DeltaTemp
170
171
  )
171
172
  TAS = atm.mach2Tas(Mach=M, theta=theta)
172
173
  GS = TAS
@@ -179,7 +180,7 @@ def getInitialMass(
179
180
  initialMass = AC.MREF
180
181
  else:
181
182
  cruiseFuelFlow = cruiseFuelConsumption(
182
- AC=AC, altitude=altitude, M=M, deltaTemp=deltaTemp
183
+ AC=AC, altitude=altitude, M=M, DeltaTemp=DeltaTemp
183
184
  )
184
185
  initialMass = breguetLeducInitialMass(
185
186
  AC=AC,
@@ -196,7 +197,7 @@ def getInitialMass(
196
197
  initialMass = AC.MREF
197
198
  else:
198
199
  cruiseFuelFlow = cruiseFuelConsumption(
199
- AC=AC, altitude=altitude, M=M, deltaTemp=deltaTemp
200
+ AC=AC, altitude=altitude, M=M, DeltaTemp=DeltaTemp
200
201
  )
201
202
  initialMass = breguetLeducInitialMass(
202
203
  AC=AC,