|
3 | 3 | from __future__ import annotations |
4 | 4 |
|
5 | 5 | import typing |
6 | | -from typing import Optional, Union |
| 6 | +from typing import Optional, Union, Literal |
7 | 7 | import warnings |
8 | 8 |
|
9 | 9 | import numpy as np |
@@ -161,9 +161,9 @@ def resample(df: pd.DataFrame, sample_rate: Optional[float] = None) -> pd.DataFr |
161 | 161 | index=resampled_time.astype(df.index.dtype), |
162 | 162 | columns=df.columns, |
163 | 163 | ) |
164 | | - |
| 164 | + |
165 | 165 | resampled_df.index.name = df.index.name |
166 | | - |
| 166 | + |
167 | 167 | return resampled_df |
168 | 168 |
|
169 | 169 |
|
@@ -343,3 +343,119 @@ def convert_units( |
343 | 343 | for i, c in enumerate(df.columns): |
344 | 344 | converted_df[c] = vals[:, i] |
345 | 345 | return converted_df |
| 346 | + |
| 347 | + |
| 348 | +def to_altitude(df: pd.DataFrame, |
| 349 | + base_press: Optional[float] = 101325, |
| 350 | + base_temp: Optional[float] = 15, |
| 351 | + temp_col_index: Optional[int] = None, |
| 352 | + press_col_index: Optional[int] = None, |
| 353 | + units: Literal['m', 'ft'] = 'm') -> pd.DataFrame: |
| 354 | + """ |
| 355 | + Converts pressure (Pascals) to altitude (feet or meters) up to 50km. |
| 356 | +
|
| 357 | + :param df: pandas DataFrame of one of the temperature/pressure channels. |
| 358 | + A pressure column should be present, if not, raise an error |
| 359 | + :param base_press: P_b; reference pressure (pressure at sea level) in |
| 360 | + Pascals (Pa). If set to None, use the first pressure measurement |
| 361 | + included in the dataframe (df) |
| 362 | + :param base_temp: T_b reference temperature (temperature at sea level) in |
| 363 | + Celsius (C). If set to None and a Temperature column exists in the |
| 364 | + dataframe (df), use the first listed temperature value. If set to None |
| 365 | + and no temperature column exists, error out |
| 366 | + :param temp_col_index: the index (starting at 0) in the dataframe (df) of |
| 367 | + the temperature column to pull data from. |
| 368 | + :param press_col_index: the index (starting at 0) in the dataframe (df) of |
| 369 | + the pressure column to pull data from |
| 370 | + :param units: determines if altitude is represented in meters ('m') or feet |
| 371 | + ('ft') in both the input and output dataframes |
| 372 | + :returns: a pandas DataFrame with the same Index values as the |
| 373 | + input, and an added column of “Altitude (m)” or “Altitude (ft)” data |
| 374 | + """ |
| 375 | + # Column Name Placeholders |
| 376 | + temp_col = None |
| 377 | + press_col = None |
| 378 | + |
| 379 | + # Conversion Constants |
| 380 | + C_K = 273.15 # Celsius to Kelvin Constant (C + 273.15 = K) |
| 381 | + ft_m = 0.3048 # Feet to Meters Constant (ft * 0.3048 = m) & (m / 0.3048 = ft) |
| 382 | + |
| 383 | + # Constants |
| 384 | + h_b = 0 # Reference Height of Sea Level (m) |
| 385 | + h_sb = 11000 # Height at the Base of the Stratosphere (meters) |
| 386 | + h_st = 50000 # Height at the Top of the Stratosphere (meters) |
| 387 | + L_b = -0.0065 # Standard Temperature Lapse Rate [K/m] |
| 388 | + g_0 = 9.80665 # Gravitational Acceleration Constant [m/s^2] |
| 389 | + R = 8.31432 # Universal Gas Constant [N*m/mol*K] |
| 390 | + M = 0.0289644 # Molar Mass of Earth's Air [kg/mol] |
| 391 | + top_stratosphere_pressure = 100 # Air Pressure at Stratopause (1mb)=[100 Pa] |
| 392 | + |
| 393 | + # Finding pressure column |
| 394 | + if press_col_index is None: |
| 395 | + for col in df.columns: |
| 396 | + if "press" in col.lower(): |
| 397 | + press_col = col |
| 398 | + press_col_index = df.columns.get_loc(press_col) |
| 399 | + break |
| 400 | + if press_col is None: |
| 401 | + raise ValueError("Pressure column not found.") |
| 402 | + else: |
| 403 | + press_col = df.columns[press_col_index] |
| 404 | + |
| 405 | + # Checking for base temp and converting to K |
| 406 | + if base_temp == None: |
| 407 | + if temp_col_index is None: |
| 408 | + for col in df.columns: |
| 409 | + if "temp" in col.lower(): |
| 410 | + temp_col = col |
| 411 | + temp_col_index = df.columns.get_loc(temp_col) |
| 412 | + break |
| 413 | + if temp_col is None: |
| 414 | + raise ValueError('Temperature column not found.') |
| 415 | + T_b = df.iloc[0, temp_col_index] + C_K # Standard Temperature in Kelvin |
| 416 | + T_bS = T_b -71.5 # Temperature at start of Stratosphere |
| 417 | + else: |
| 418 | + T_b = base_temp + C_K # Standard Temperature in Kelvin |
| 419 | + T_bS = T_b -71.5 # Temperature at start of Stratosphere |
| 420 | + |
| 421 | + # Checking for base pressure |
| 422 | + if base_press == None: |
| 423 | + # Set to first pressure recording in the dataframe |
| 424 | + P_b = df.iloc[0, press_col_index] # Static Pressure in Pascals |
| 425 | + else: |
| 426 | + P_b = base_press # Static Pressure in Pascals |
| 427 | + |
| 428 | + # Pressure at base of Stratosphere |
| 429 | + base_stratosphere_pressure = (P_b * (1 + (L_b / T_b) * (h_sb - h_b)) ** |
| 430 | + ((-g_0 * M) / (R * L_b))) |
| 431 | + |
| 432 | + # List of Altitudes to be added to Dataframe |
| 433 | + altitude_column = [] |
| 434 | + |
| 435 | + # Calculate Altitude for the DataFrame |
| 436 | + for index, row in df.iterrows(): |
| 437 | + P = row[press_col] |
| 438 | + if P > base_stratosphere_pressure: |
| 439 | + h = h_b + (T_b / L_b) * (((P / P_b) ** ((-R * L_b) / (g_0 * M))) - 1) |
| 440 | + altitude_column.append(h) |
| 441 | + elif top_stratosphere_pressure < P < base_stratosphere_pressure: |
| 442 | + h = h_sb + ((R * T_bS * np.log(P / base_stratosphere_pressure)) / |
| 443 | + (-g_0 * M)) |
| 444 | + altitude_column.append(h) |
| 445 | + else: |
| 446 | + raise ValueError("Altitudes above stratosphere not supported.") |
| 447 | + |
| 448 | + # Convert Altitude back to feet if specified |
| 449 | + if units == 'ft': |
| 450 | + altitude_column = [x / ft_m for x in altitude_column] |
| 451 | + # Add "Altitude (ft)" Column to Copy of Original Dataframe |
| 452 | + alt_df = df.copy() |
| 453 | + alt_df["Altitude (ft)"] = altitude_column |
| 454 | + |
| 455 | + # Add "Altitude (m)" Column to Copy of Original Dataframe |
| 456 | + if units == 'm': |
| 457 | + alt_df = df.copy() |
| 458 | + alt_df["Altitude (m)"] = altitude_column |
| 459 | + |
| 460 | + # Return DataFrame with New Altitude Column |
| 461 | + return alt_df |
0 commit comments