pycontrails 0.58.0__cp314-cp314-win_amd64.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.

Files changed (122) hide show
  1. pycontrails/__init__.py +70 -0
  2. pycontrails/_version.py +34 -0
  3. pycontrails/core/__init__.py +30 -0
  4. pycontrails/core/aircraft_performance.py +679 -0
  5. pycontrails/core/airports.py +228 -0
  6. pycontrails/core/cache.py +889 -0
  7. pycontrails/core/coordinates.py +174 -0
  8. pycontrails/core/fleet.py +483 -0
  9. pycontrails/core/flight.py +2185 -0
  10. pycontrails/core/flightplan.py +228 -0
  11. pycontrails/core/fuel.py +140 -0
  12. pycontrails/core/interpolation.py +702 -0
  13. pycontrails/core/met.py +2931 -0
  14. pycontrails/core/met_var.py +387 -0
  15. pycontrails/core/models.py +1321 -0
  16. pycontrails/core/polygon.py +549 -0
  17. pycontrails/core/rgi_cython.cp314-win_amd64.pyd +0 -0
  18. pycontrails/core/vector.py +2249 -0
  19. pycontrails/datalib/__init__.py +12 -0
  20. pycontrails/datalib/_met_utils/metsource.py +746 -0
  21. pycontrails/datalib/ecmwf/__init__.py +73 -0
  22. pycontrails/datalib/ecmwf/arco_era5.py +345 -0
  23. pycontrails/datalib/ecmwf/common.py +114 -0
  24. pycontrails/datalib/ecmwf/era5.py +554 -0
  25. pycontrails/datalib/ecmwf/era5_model_level.py +490 -0
  26. pycontrails/datalib/ecmwf/hres.py +804 -0
  27. pycontrails/datalib/ecmwf/hres_model_level.py +466 -0
  28. pycontrails/datalib/ecmwf/ifs.py +287 -0
  29. pycontrails/datalib/ecmwf/model_levels.py +435 -0
  30. pycontrails/datalib/ecmwf/static/model_level_dataframe_v20240418.csv +139 -0
  31. pycontrails/datalib/ecmwf/variables.py +268 -0
  32. pycontrails/datalib/geo_utils.py +261 -0
  33. pycontrails/datalib/gfs/__init__.py +28 -0
  34. pycontrails/datalib/gfs/gfs.py +656 -0
  35. pycontrails/datalib/gfs/variables.py +104 -0
  36. pycontrails/datalib/goes.py +757 -0
  37. pycontrails/datalib/himawari/__init__.py +27 -0
  38. pycontrails/datalib/himawari/header_struct.py +266 -0
  39. pycontrails/datalib/himawari/himawari.py +667 -0
  40. pycontrails/datalib/landsat.py +589 -0
  41. pycontrails/datalib/leo_utils/__init__.py +5 -0
  42. pycontrails/datalib/leo_utils/correction.py +266 -0
  43. pycontrails/datalib/leo_utils/landsat_metadata.py +300 -0
  44. pycontrails/datalib/leo_utils/search.py +250 -0
  45. pycontrails/datalib/leo_utils/sentinel_metadata.py +748 -0
  46. pycontrails/datalib/leo_utils/static/bq_roi_query.sql +6 -0
  47. pycontrails/datalib/leo_utils/vis.py +59 -0
  48. pycontrails/datalib/sentinel.py +650 -0
  49. pycontrails/datalib/spire/__init__.py +5 -0
  50. pycontrails/datalib/spire/exceptions.py +62 -0
  51. pycontrails/datalib/spire/spire.py +604 -0
  52. pycontrails/ext/bada.py +42 -0
  53. pycontrails/ext/cirium.py +14 -0
  54. pycontrails/ext/empirical_grid.py +140 -0
  55. pycontrails/ext/synthetic_flight.py +431 -0
  56. pycontrails/models/__init__.py +1 -0
  57. pycontrails/models/accf.py +425 -0
  58. pycontrails/models/apcemm/__init__.py +8 -0
  59. pycontrails/models/apcemm/apcemm.py +983 -0
  60. pycontrails/models/apcemm/inputs.py +226 -0
  61. pycontrails/models/apcemm/static/apcemm_yaml_template.yaml +183 -0
  62. pycontrails/models/apcemm/utils.py +437 -0
  63. pycontrails/models/cocip/__init__.py +29 -0
  64. pycontrails/models/cocip/cocip.py +2742 -0
  65. pycontrails/models/cocip/cocip_params.py +305 -0
  66. pycontrails/models/cocip/cocip_uncertainty.py +291 -0
  67. pycontrails/models/cocip/contrail_properties.py +1530 -0
  68. pycontrails/models/cocip/output_formats.py +2270 -0
  69. pycontrails/models/cocip/radiative_forcing.py +1260 -0
  70. pycontrails/models/cocip/radiative_heating.py +520 -0
  71. pycontrails/models/cocip/unterstrasser_wake_vortex.py +508 -0
  72. pycontrails/models/cocip/wake_vortex.py +396 -0
  73. pycontrails/models/cocip/wind_shear.py +120 -0
  74. pycontrails/models/cocipgrid/__init__.py +9 -0
  75. pycontrails/models/cocipgrid/cocip_grid.py +2552 -0
  76. pycontrails/models/cocipgrid/cocip_grid_params.py +138 -0
  77. pycontrails/models/dry_advection.py +602 -0
  78. pycontrails/models/emissions/__init__.py +21 -0
  79. pycontrails/models/emissions/black_carbon.py +599 -0
  80. pycontrails/models/emissions/emissions.py +1353 -0
  81. pycontrails/models/emissions/ffm2.py +336 -0
  82. pycontrails/models/emissions/static/default-engine-uids.csv +239 -0
  83. pycontrails/models/emissions/static/edb-gaseous-v29b-engines.csv +596 -0
  84. pycontrails/models/emissions/static/edb-nvpm-v29b-engines.csv +215 -0
  85. pycontrails/models/extended_k15.py +1327 -0
  86. pycontrails/models/humidity_scaling/__init__.py +37 -0
  87. pycontrails/models/humidity_scaling/humidity_scaling.py +1075 -0
  88. pycontrails/models/humidity_scaling/quantiles/era5-model-level-quantiles.pq +0 -0
  89. pycontrails/models/humidity_scaling/quantiles/era5-pressure-level-quantiles.pq +0 -0
  90. pycontrails/models/issr.py +210 -0
  91. pycontrails/models/pcc.py +326 -0
  92. pycontrails/models/pcr.py +154 -0
  93. pycontrails/models/ps_model/__init__.py +18 -0
  94. pycontrails/models/ps_model/ps_aircraft_params.py +381 -0
  95. pycontrails/models/ps_model/ps_grid.py +701 -0
  96. pycontrails/models/ps_model/ps_model.py +1000 -0
  97. pycontrails/models/ps_model/ps_operational_limits.py +525 -0
  98. pycontrails/models/ps_model/static/ps-aircraft-params-20250328.csv +69 -0
  99. pycontrails/models/ps_model/static/ps-synonym-list-20250328.csv +104 -0
  100. pycontrails/models/sac.py +442 -0
  101. pycontrails/models/tau_cirrus.py +183 -0
  102. pycontrails/physics/__init__.py +1 -0
  103. pycontrails/physics/constants.py +117 -0
  104. pycontrails/physics/geo.py +1138 -0
  105. pycontrails/physics/jet.py +968 -0
  106. pycontrails/physics/static/iata-cargo-load-factors-20250221.csv +74 -0
  107. pycontrails/physics/static/iata-passenger-load-factors-20250221.csv +74 -0
  108. pycontrails/physics/thermo.py +551 -0
  109. pycontrails/physics/units.py +472 -0
  110. pycontrails/py.typed +0 -0
  111. pycontrails/utils/__init__.py +1 -0
  112. pycontrails/utils/dependencies.py +66 -0
  113. pycontrails/utils/iteration.py +13 -0
  114. pycontrails/utils/json.py +187 -0
  115. pycontrails/utils/temp.py +50 -0
  116. pycontrails/utils/types.py +163 -0
  117. pycontrails-0.58.0.dist-info/METADATA +180 -0
  118. pycontrails-0.58.0.dist-info/RECORD +122 -0
  119. pycontrails-0.58.0.dist-info/WHEEL +5 -0
  120. pycontrails-0.58.0.dist-info/licenses/LICENSE +178 -0
  121. pycontrails-0.58.0.dist-info/licenses/NOTICE +43 -0
  122. pycontrails-0.58.0.dist-info/top_level.txt +3 -0
@@ -0,0 +1,228 @@
1
+ """ATC Flight Plan Parser."""
2
+
3
+ import re
4
+ from typing import Any
5
+
6
+
7
+ def to_atc_plan(plan: dict[str, Any]) -> str:
8
+ """Write dictionary from :func:`parse_atc_plan` as ATC flight plan string.
9
+
10
+ Parameters
11
+ ----------
12
+ plan: dict[str, Any]
13
+ Dictionary representation of ATC flight plan returned from :func:`parse_atc_plan`.
14
+
15
+ Returns
16
+ -------
17
+ str
18
+ ATC flight plan string conforming to ICAO Doc 4444-ATM/501
19
+
20
+ See Also
21
+ --------
22
+ :func:`parse_atc_plan`
23
+ """
24
+ ret = f"(FPL-{plan['callsign']}-{plan['flight_rules']}"
25
+ ret += f"{plan['type_of_flight']}\n"
26
+ ret += "-"
27
+ if "number_aircraft" in plan and plan["number_aircraft"] <= 10:
28
+ ret += plan["number_aircraft"]
29
+ ret += f"{plan['type_of_aircraft']}/{plan['wake_category']}-"
30
+ ret += f"{plan['equipment']}/{plan['transponder']}\n"
31
+ ret += f"-{plan['departure_icao']}{plan['time']}\n"
32
+ ret += f"-{plan['speed_type']}{plan['speed']}{plan['level_type']}"
33
+ ret += f"{plan['level']} {plan['route']}\n"
34
+ if "destination_icao" in plan and "duration" in plan:
35
+ ret += f"-{plan['destination_icao']}{plan['duration']}"
36
+ if "alt_icao" in plan:
37
+ ret += f" {plan['alt_icao']}"
38
+ if "second_alt_icao" in plan:
39
+ ret += f" {plan['second_alt_icao']}"
40
+ ret += "\n"
41
+ ret += f"-{plan['other_info']}"
42
+ if "supplementary_info" in plan:
43
+ ret += "\n-"
44
+ ret += " ".join([f"{i[0]}/{i[1]}" for i in plan["supplementary_info"].items()])
45
+
46
+ ret += ")"
47
+
48
+ if ret[-1] == "\n":
49
+ ret = ret[:-1]
50
+
51
+ return ret
52
+
53
+
54
+ def parse_atc_plan(atc_plan: str) -> dict[str, str]:
55
+ """Parse an ATC flight plan string into a dictionary.
56
+
57
+ The route string is not converted to lat/lon in this process.
58
+
59
+ Parameters
60
+ ----------
61
+ atc_plan : str
62
+ An ATC flight plan string conforming to ICAO Doc 4444-ATM/501 (Appendix 2)
63
+
64
+ Returns
65
+ -------
66
+ dict[str, str]
67
+ A dictionary consisting of parsed components of the ATC flight plan.
68
+ A full ATC plan will contain the keys:
69
+
70
+ - ``callsign``: ICAO flight callsign
71
+ - ``flight_rules``: Flight rules ("I", "V", "Y", "Z")
72
+ - ``type_of_flight``: Type of flight ("S", "N", "G", "M", "X")
73
+ - ``number_aircraft``: The number of aircraft, if more than one
74
+ - ``type_of_aircraft``: ICAO aircraft type
75
+ - ``wake_category``: Wake turbulence category
76
+ - ``equipment``: Radiocommunication, navigation and approach aid equipment and capabilities
77
+ - ``transponder``: Surveillance equipment and capabilities
78
+ - ``departure_icao``: ICAO departure airport
79
+ - ``time``: Estimated off-block (departure) time (UTC)
80
+ - ``speed_type``: Speed units ("K": km / hr, "N": knots)
81
+ - ``speed``: Cruise true airspeed in ``speed_type`` units
82
+ - ``level_type``: Level units ("F", "S", "A", "M")
83
+ - ``level``: Cruise level
84
+ - ``route``: Route string
85
+ - ``destination_icao``: ICAO destination airport
86
+ - ``duration``: The total estimated elapsed time for the flight plan
87
+ - ``alt_icao``: ICAO alternate destination airport
88
+ - ``second_alt_icao``: ICAO second alternate destination airport
89
+ - ``other_info``: Other information
90
+ - ``supplementary_info``: Supplementary information
91
+
92
+ References
93
+ ----------
94
+ - https://applications.icao.int/tools/ATMiKIT/story_content/external_files/story_content/external_files/DOC%204444_PANS%20ATM_en.pdf
95
+
96
+ See Also
97
+ --------
98
+ :func:`to_atc_plan`
99
+ """
100
+ atc_plan = atc_plan.replace("\r", " ")
101
+ atc_plan = atc_plan.replace("\n", " ")
102
+ atc_plan = atc_plan.upper()
103
+ atc_plan = atc_plan.strip()
104
+
105
+ if len(atc_plan) == 0:
106
+ raise ValueError("Empty or invalid flight plan")
107
+
108
+ atc_plan = atc_plan.replace("(FPL", "")
109
+ atc_plan = atc_plan.replace(")", "")
110
+ atc_plan = atc_plan.replace("--", "-")
111
+
112
+ basic = atc_plan.split("-")
113
+
114
+ flightplan: dict[str, Any] = {}
115
+
116
+ # Callsign
117
+ if len(basic) > 1:
118
+ flightplan["callsign"] = basic[1]
119
+
120
+ # Flight Rules
121
+ if len(basic) > 2:
122
+ flightplan["flight_rules"] = basic[2][0]
123
+ flightplan["type_of_flight"] = basic[2][1]
124
+
125
+ # Aircraft
126
+ if len(basic) > 3:
127
+ aircraft = basic[3].split("/")
128
+ matches = re.match(r"(\d{1})(\S{3,4})", aircraft[0])
129
+ groups = matches.groups() if matches else ()
130
+
131
+ if matches and len(groups) > 2:
132
+ flightplan["number_aircraft"] = groups[1]
133
+ flightplan["type_of_aircraft"] = groups[2]
134
+ else:
135
+ flightplan["type_of_aircraft"] = aircraft[0]
136
+
137
+ if len(aircraft) > 1:
138
+ flightplan["wake_category"] = aircraft[1]
139
+
140
+ # Equipment
141
+ if len(basic) > 4:
142
+ equip = basic[4].split("/")
143
+ flightplan["equipment"] = equip[0]
144
+ if len(equip) > 1:
145
+ flightplan["transponder"] = equip[1]
146
+
147
+ # Dep. airport info
148
+ if len(basic) > 5:
149
+ matches = re.match(r"(\D*)(\d*)", basic[5])
150
+ groups = matches.groups() if matches else ()
151
+
152
+ if groups:
153
+ flightplan["departure_icao"] = groups[0]
154
+ if len(groups) > 1:
155
+ flightplan["time"] = groups[1]
156
+
157
+ # Speed and route info
158
+ if len(basic) > 6:
159
+ matches = re.match(r"(\D*)(\d*)(\D*)(\d*)", basic[6])
160
+ groups = matches.groups() if matches else ()
161
+
162
+ # match speed and level
163
+ if groups:
164
+ flightplan["speed_type"] = groups[0]
165
+ if len(groups) > 1:
166
+ flightplan["speed"] = groups[1]
167
+ if len(groups) > 2:
168
+ flightplan["level_type"] = groups[2]
169
+ if len(groups) > 3:
170
+ flightplan["level"] = groups[3]
171
+
172
+ flightplan["route"] = basic[6][len("".join(groups)) :].strip()
173
+ else:
174
+ flightplan["route"] = basic[6].strip()
175
+
176
+ # Dest. airport info
177
+ if len(basic) > 7:
178
+ matches = re.match(r"(\D{4})(\d{4})", basic[7])
179
+ groups = matches.groups() if matches else ()
180
+
181
+ if groups:
182
+ flightplan["destination_icao"] = groups[0]
183
+ if len(groups) > 1:
184
+ flightplan["duration"] = groups[1]
185
+
186
+ matches = re.match(r"(\D{4})(\d{4})(\s{1})(\D{4})", basic[7])
187
+ groups = matches.groups() if matches else ()
188
+
189
+ if len(groups) > 3:
190
+ flightplan["alt_icao"] = groups[3]
191
+
192
+ matches = re.match(r"(\D{4})(\d{4})(\s{1})(\D{4})(\s{1})(\D{4})", basic[7])
193
+ groups = matches.groups() if matches else ()
194
+
195
+ if len(groups) > 5:
196
+ flightplan["second_alt_icao"] = groups[5]
197
+
198
+ # Other info
199
+ if len(basic) > 8:
200
+ info = basic[8]
201
+ idx = info.find("DOF")
202
+ if idx != -1:
203
+ flightplan["departure_date"] = info[idx + 4 : idx + 10]
204
+
205
+ flightplan["other_info"] = info.strip()
206
+
207
+ # Supl. Info
208
+ if len(basic) > 9:
209
+ sup_match = re.findall(r"(\D{1}[\/]{1})", basic[9])
210
+ if sup_match:
211
+ suplInfo = {}
212
+ for i in range(len(sup_match) - 1):
213
+ this_key = sup_match[i]
214
+ this_idx = basic[9].find(this_key)
215
+
216
+ next_key = sup_match[i + 1]
217
+ next_idx = basic[9].find(next_key)
218
+
219
+ val = basic[9][this_idx + 2 : next_idx - 1]
220
+ suplInfo[this_key[0]] = val
221
+
222
+ last_key = sup_match[-1]
223
+ last_idx = basic[9].find(last_key)
224
+ suplInfo[last_key[0]] = basic[9][last_idx + 2 :]
225
+
226
+ flightplan["supplementary_info"] = suplInfo
227
+
228
+ return flightplan
@@ -0,0 +1,140 @@
1
+ """Fuel data support."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import dataclasses
6
+
7
+ import numpy as np
8
+
9
+
10
+ @dataclasses.dataclass(frozen=True)
11
+ class Fuel:
12
+ """Base class for the physical parameters of the fuel."""
13
+
14
+ #: Fuel Name
15
+ fuel_name: str
16
+
17
+ #: Lower calorific value (LCV) of fuel, :math:`[J \ kg_{fuel}^{-1}]`
18
+ q_fuel: float
19
+
20
+ #: Percentage of hydrogen mass content in the fuel
21
+ hydrogen_content: float
22
+
23
+ #: CO2 emissions index for fuel, :math:`[kg_{CO_{2}} \ kg_{fuel}^{-1}]`
24
+ ei_co2: float
25
+
26
+ #: Water vapour emissions index for fuel, :math:`[kg_{H_{2}O} \ kg_{fuel}^{-1}]`
27
+ ei_h2o: float
28
+
29
+ #: Sulphur oxide, SO2-S gas, emissions index for fuel, :math:`[kg_{SO_{2}} \ kg_{fuel}^{-1}]`
30
+ ei_so2: float
31
+
32
+ #: Sulphates, S(VI)-S particle, emissions index for fuel, :math:`[kg_{S} \ kg_{fuel}^{-1}]`
33
+ ei_sulphates: float
34
+
35
+ #: Organic carbon emissions index for fuel, :math:`[kg_{OC} \ kg_{fuel}^{-1}]`
36
+ ei_oc: float
37
+
38
+
39
+ @dataclasses.dataclass(frozen=True)
40
+ class JetA(Fuel):
41
+ """Jet A-1 Fuel.
42
+
43
+ References
44
+ ----------
45
+ - :cite:`celikel2001forecasting`
46
+ - :cite:`leeContributionGlobalAviation2021`
47
+ - :cite:`stettlerAirQualityPublic2011`
48
+ - :cite:`wilkersonAnalysisEmissionData2010`
49
+ """
50
+
51
+ fuel_name: str = "Jet A-1"
52
+ q_fuel: float = 43.13e6
53
+ hydrogen_content: float = 13.8
54
+ ei_co2: float = 3.159
55
+ ei_h2o: float = 1.23
56
+
57
+ #: Sulphur oxide, SO2-S gas, emissions index for fuel, :math:`[kg_{SO_{2}} \ kg_{fuel}^{-1}]`
58
+ #: - The EI SO2 is proportional to the fuel sulphur content
59
+ #: - Celikel (2001): EI_SO2 = 0.84 g/kg-fuel for 450 ppm fuel
60
+ #: - Lee et al. (2021): EI_SO2 = 1.2 g/kg-fuel for 600 ppm fuel
61
+ ei_so2: float = 0.0012
62
+
63
+ #: Sulphates, S(VI)-S particle, emissions index for fuel, :math:`[kg_{S} \ kg_{fuel}^{-1}]`
64
+ #: - The SOx-S is partitioned into 98% SO2-S gas and 2% S(VI)-S particle
65
+ #: - References: Wilkerson et al. (2010) & Stettler et al. (2011)
66
+ ei_sulphates: float = ei_so2 / 0.98 * 0.02
67
+
68
+ #: Organic carbon emissions index for fuel, :math:`[kg_{OC} \ kg_{fuel}^{-1}]`
69
+ #: - High uncertainty
70
+ #: - Wilkerson et al. (2010): EI_OC = 15 mg/kg-fuel
71
+ #: - Stettler et al. (2011): EI_OC = 20 [1, 40] mg/kg-fuel
72
+ ei_oc: float = 20 * 1e-6
73
+
74
+
75
+ class SAFBlend(Fuel):
76
+ """Jet A-1 / Sustainable Aviation Fuel Blend.
77
+
78
+ SAF only changes the CO2 lifecycle emissions, not the CO2 emissions emitted at the
79
+ aircraft exhaust. We assume that the EI OC stays the same as Jet A-1 fuel due to lack
80
+ of data.
81
+
82
+ Parameters
83
+ ----------
84
+ pct_blend : float
85
+ Sustainable aviation fuel percentage blend ratio by volume, %. Expected
86
+ to be in the interval ``[0, 100]``.
87
+
88
+ References
89
+ ----------
90
+ - :cite:`teohTargetedUseSustainable2022`
91
+ - :cite:`schrippAircraftEngineParticulate2022`
92
+ """
93
+
94
+ def __init__(self, pct_blend: float) -> None:
95
+ if pct_blend < 0.0 or pct_blend > 100.0:
96
+ raise ValueError("pct_blend only accepts a value of between 0 and 100.")
97
+
98
+ self.pct_blend = pct_blend
99
+
100
+ fuel_name = "Jet A-1 / Sustainable Aviation Fuel Blend"
101
+
102
+ # We take the default values for Jet-A and modify them for a custom blend
103
+ base_fuel = JetA()
104
+ q_fuel = base_fuel.q_fuel + (10700.0 * self.pct_blend)
105
+ hydrogen_content = base_fuel.hydrogen_content + 0.015 * self.pct_blend
106
+ ei_co2 = base_fuel.ei_co2
107
+ ei_h2o = base_fuel.ei_h2o * (hydrogen_content / base_fuel.hydrogen_content)
108
+ ei_so2 = base_fuel.ei_so2 * (1.0 - self.pct_blend / 100.0)
109
+ ei_sulphates = ei_so2 / 0.98 * 0.02
110
+ ei_oc = base_fuel.ei_oc
111
+
112
+ super().__init__(
113
+ fuel_name=fuel_name,
114
+ q_fuel=q_fuel,
115
+ hydrogen_content=hydrogen_content,
116
+ ei_co2=ei_co2,
117
+ ei_h2o=ei_h2o,
118
+ ei_so2=ei_so2,
119
+ ei_sulphates=ei_sulphates,
120
+ ei_oc=ei_oc,
121
+ )
122
+
123
+
124
+ @dataclasses.dataclass(frozen=True)
125
+ class HydrogenFuel(Fuel):
126
+ """Hydrogen Fuel.
127
+
128
+ References
129
+ ----------
130
+ - :cite:`khanEmissionsWaterVapour2022`
131
+ """
132
+
133
+ fuel_name: str = "Hydrogen"
134
+ q_fuel: float = 122.8e6
135
+ hydrogen_content: float = np.nan
136
+ ei_co2: float = 0.0
137
+ ei_h2o: float = 9.21
138
+ ei_so2: float = 0.0
139
+ ei_sulphates: float = 0.0
140
+ ei_oc: float = 0.0