pycontrails 0.58.0__cp314-cp314-macosx_11_0_arm64.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.cpython-314-darwin.so +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 +6 -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,250 @@
1
+ """Tools for searching for low Earth orbit satellite imagery."""
2
+
3
+ import dataclasses
4
+ import pathlib
5
+
6
+ import numpy as np
7
+ import pandas as pd
8
+
9
+ from pycontrails.core import Flight
10
+ from pycontrails.utils import dependencies
11
+
12
+ try:
13
+ import geojson
14
+ except ModuleNotFoundError as exc:
15
+ dependencies.raise_module_not_found_error(
16
+ name="datalib.leo_utils module",
17
+ package_name="geojson",
18
+ module_not_found_error=exc,
19
+ pycontrails_optional_package="sat",
20
+ )
21
+
22
+ _path_to_static = pathlib.Path(__file__).parent / "static"
23
+ ROI_QUERY_FILENAME = _path_to_static / "bq_roi_query.sql"
24
+
25
+
26
+ #: GeoJSON polygon that covers the entire globe.
27
+ GLOBAL_EXTENT = geojson.dumps(
28
+ geojson.Polygon([[(-180, -90), (180, -90), (180, 90), (-180, 90), (-180, -90)]])
29
+ )
30
+
31
+
32
+ @dataclasses.dataclass
33
+ class ROI:
34
+ """Spatiotemporal region of interest."""
35
+
36
+ #: Start time
37
+ start_time: np.datetime64
38
+
39
+ #: End time
40
+ end_time: np.datetime64
41
+
42
+ #: GeoJSON representation of spatial ROI.
43
+ extent: str
44
+
45
+ def __post_init__(self) -> None:
46
+ """Validate region of interest."""
47
+ if self.start_time > self.end_time:
48
+ msg = "start_time must be before end_time"
49
+ raise ValueError(msg)
50
+
51
+ try:
52
+ decoded = geojson.Feature(geometry=geojson.loads(self.extent))
53
+ except Exception as exc:
54
+ msg = "extent cannot be converted to GeoJSON structure"
55
+ raise ValueError(msg) from exc
56
+ if not decoded.is_valid:
57
+ msg = "extent is not valid GeoJSON"
58
+ raise ValueError(msg)
59
+
60
+
61
+ def track_to_geojson(flight: Flight) -> str:
62
+ """Convert ground track to GeoJSON string, splitting at antimeridian crossings.
63
+
64
+ Coordinates contain longitude and latitude only (no altitude coordinate)
65
+ and are padded to terminate and restart exactly at the antimeridian when
66
+ antimeridian crossings are encountered.
67
+
68
+ Parameters
69
+ ----------
70
+ flight : Flight
71
+ Flight with ground track to convert to GeoJSON string.
72
+
73
+ Returns
74
+ -------
75
+ str
76
+ String encoding of a GeoJSON MultiLineString containing ground track split at
77
+ antimeridian crossings.
78
+
79
+ See Also
80
+ --------
81
+ :meth:`Flight.to_geojson_multilinestring`
82
+ """
83
+
84
+ # Logic assumes longitudes are between -180 and 180.
85
+ # Raise an error if this is not the case.
86
+ if np.abs(flight["longitude"]).max() > 180.0:
87
+ msg = "Flight longitudes must be between -180 and 180."
88
+ raise ValueError(msg)
89
+
90
+ # Get feature collection containing a single multilinestring
91
+ # split at antimeridian crossings
92
+ fc = flight.to_geojson_multilinestring(split_antimeridian=True)
93
+
94
+ # Extract multilinestring
95
+ mls = fc["features"][0]["geometry"]
96
+
97
+ # Strip altitude coordinates
98
+ coords = [[[c[0], c[1]] for c in linestring] for linestring in mls["coordinates"]]
99
+
100
+ # No padding required if no antimeridian crossings were encountered
101
+ if len(coords) == 1:
102
+ return geojson.dumps(geojson.MultiLineString(coords))
103
+
104
+ # Pad at crossings
105
+ for i in range(len(coords) - 1):
106
+ x0 = coords[i][-1][0]
107
+ x1 = coords[i + 1][0][0]
108
+ if abs(x0) == 180.0 and abs(x1) == 180.0:
109
+ continue
110
+ y0 = coords[i][-1][1]
111
+ y1 = coords[i + 1][0][1]
112
+ xl = 180.0 * np.sign(x0)
113
+ xr = 180.0 * np.sign(x1)
114
+ w0 = np.abs(xr - x1)
115
+ w1 = np.abs(xl - x0)
116
+ yc = (w0 * y0 + w1 * y1) / (w0 + w1)
117
+ if abs(x0) < 180.0:
118
+ coords[i].append([xl, yc])
119
+ if abs(x1) < 180.0:
120
+ coords[i + 1].insert(0, [xr, yc])
121
+
122
+ # Encode as string
123
+ return geojson.dumps(geojson.MultiLineString(coords))
124
+
125
+
126
+ def query(table: str, roi: ROI, columns: list[str], extra_filters: str = "") -> pd.DataFrame:
127
+ """Find satellite imagery within region of interest.
128
+
129
+ This function requires access to the
130
+ `Google BigQuery API <https://cloud.google.com/bigquery?hl=en>`__
131
+ and uses the `BigQuery python library <https://cloud.google.com/python/docs/reference/bigquery/latest/index.html>`__.
132
+
133
+ Parameters
134
+ ----------
135
+ table : str
136
+ Name of BigQuery table to query
137
+ roi : ROI
138
+ Region of interest
139
+ columns : list[str]
140
+ Columns to return from Google
141
+ `BigQuery table <https://console.cloud.google.com/bigquery?p=bigquery-public-data&d=cloud_storage_geo_index&t=landsat_index&page=table&_ga=2.90807450.1051800793.1716904050-255800408.1705955196>`__.
142
+ extra_filters : str, optional
143
+ Additional selection filters, injected verbatim into constructed query.
144
+
145
+ Returns
146
+ -------
147
+ pd.DataFrame
148
+ Query results in pandas DataFrame
149
+ """
150
+ try:
151
+ from google.cloud import bigquery
152
+ except ModuleNotFoundError as exc:
153
+ dependencies.raise_module_not_found_error(
154
+ name="landsat module",
155
+ package_name="google-cloud-bigquery",
156
+ module_not_found_error=exc,
157
+ pycontrails_optional_package="landsat",
158
+ )
159
+
160
+ if len(columns) == 0:
161
+ msg = "At least column must be provided."
162
+ raise ValueError(msg)
163
+
164
+ start_time = pd.Timestamp(roi.start_time).strftime("%Y-%m-%d %H:%M:%S")
165
+ end_time = pd.Timestamp(roi.end_time).strftime("%Y-%m-%d %H:%M:%S")
166
+ extent = roi.extent.replace('"', "'")
167
+
168
+ client = bigquery.Client()
169
+ with open(ROI_QUERY_FILENAME) as f:
170
+ query_str = f.read().format(
171
+ table=table,
172
+ columns=",".join(columns),
173
+ start_time=start_time,
174
+ end_time=end_time,
175
+ geojson_str=extent,
176
+ extra_filters=extra_filters,
177
+ )
178
+
179
+ result = client.query(query_str)
180
+ return result.to_dataframe()
181
+
182
+
183
+ def intersect(
184
+ table: str, flight: Flight, columns: list[str], extra_filters: str = ""
185
+ ) -> pd.DataFrame:
186
+ """Find satellite imagery intersecting with flight track.
187
+
188
+ This function will return all scenes with a bounding box that includes flight waypoints
189
+ both before and after the sensing time.
190
+
191
+ This function requires access to the
192
+ `Google BigQuery API <https://cloud.google.com/bigquery?hl=en>`__
193
+ and uses the `BigQuery python library <https://cloud.google.com/python/docs/reference/bigquery/latest/index.html>`__.
194
+
195
+ Parameters
196
+ ----------
197
+ table : str
198
+ Name of BigQuery table to query
199
+ flight : Flight
200
+ Flight for intersection
201
+ columns : list[str]
202
+ Columns to return from Google
203
+ `BigQuery table <https://console.cloud.google.com/bigquery?p=bigquery-public-data&d=cloud_storage_geo_index&t=landsat_index&page=table&_ga=2.90807450.1051800793.1716904050-255800408.1705955196>`__.
204
+ extra_filters : str, optional
205
+ Additional selection filters, injected verbatim into constructed query.
206
+
207
+ Returns
208
+ -------
209
+ pd.DataFrame
210
+ Query results in pandas DataFrame
211
+ """
212
+
213
+ # create ROI with time span between flight start and end
214
+ # and spatial extent set to flight track
215
+ extent = track_to_geojson(flight)
216
+ roi = ROI(start_time=flight["time"].min(), end_time=flight["time"].max(), extent=extent)
217
+
218
+ # first pass: query for intersections with ROI
219
+ # requires additional columns for final intersection with flight
220
+ required_columns = set(["sensing_time", "west_lon", "east_lon", "south_lat", "north_lat"])
221
+ queried_columns = list(required_columns.union(set(columns)))
222
+ candidates = query(table, roi, queried_columns, extra_filters)
223
+
224
+ if len(candidates) == 0: # already know there are no intersections
225
+ return candidates[columns]
226
+
227
+ # second pass: keep images with where flight waypoints
228
+ # bounding sensing time are both within bounding box
229
+ flight_data = flight.dataframe
230
+
231
+ def intersects(scene: pd.Series) -> bool:
232
+ if scene["west_lon"] <= scene["east_lon"]: # scene does not span antimeridian
233
+ bbox_data = flight_data[
234
+ flight_data["longitude"].between(scene["west_lon"], scene["east_lon"])
235
+ & flight_data["latitude"].between(scene["south_lat"], scene["north_lat"])
236
+ ]
237
+ else: # scene spans antimeridian
238
+ bbox_data = flight_data[
239
+ (
240
+ flight_data["longitude"]
241
+ > scene["west_lon"] | flight.data["longitude"]
242
+ < scene["east_lon"]
243
+ )
244
+ & flight_data["latitude"].between(scene["south_lat"], scene["north_lat"])
245
+ ]
246
+ sensing_time = pd.Timestamp(scene["sensing_time"]).tz_localize(None)
247
+ return bbox_data["time"].min() <= sensing_time and bbox_data["time"].max() >= sensing_time
248
+
249
+ mask = candidates.apply(intersects, axis="columns")
250
+ return candidates[columns][mask]