pycontrails 0.54.8__cp311-cp311-macosx_11_0_arm64.whl → 0.54.9__cp311-cp311-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.
- pycontrails/_version.py +2 -2
- pycontrails/core/aircraft_performance.py +48 -35
- pycontrails/core/models.py +54 -9
- pycontrails/core/rgi_cython.cpython-311-darwin.so +0 -0
- pycontrails/datalib/goes.py +146 -2
- pycontrails/ext/synthetic_flight.py +1 -1
- pycontrails/models/ps_model/ps_model.py +4 -7
- {pycontrails-0.54.8.dist-info → pycontrails-0.54.9.dist-info}/METADATA +1 -1
- {pycontrails-0.54.8.dist-info → pycontrails-0.54.9.dist-info}/RECORD +13 -13
- {pycontrails-0.54.8.dist-info → pycontrails-0.54.9.dist-info}/WHEEL +1 -1
- {pycontrails-0.54.8.dist-info → pycontrails-0.54.9.dist-info}/licenses/LICENSE +0 -0
- {pycontrails-0.54.8.dist-info → pycontrails-0.54.9.dist-info}/licenses/NOTICE +0 -0
- {pycontrails-0.54.8.dist-info → pycontrails-0.54.9.dist-info}/top_level.txt +0 -0
pycontrails/_version.py
CHANGED
|
@@ -4,15 +4,9 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import abc
|
|
6
6
|
import dataclasses
|
|
7
|
-
import sys
|
|
8
7
|
import warnings
|
|
9
8
|
from typing import Any, Generic, NoReturn, overload
|
|
10
9
|
|
|
11
|
-
if sys.version_info >= (3, 12):
|
|
12
|
-
from typing import override
|
|
13
|
-
else:
|
|
14
|
-
from typing_extensions import override
|
|
15
|
-
|
|
16
10
|
import numpy as np
|
|
17
11
|
import numpy.typing as npt
|
|
18
12
|
|
|
@@ -20,6 +14,7 @@ from pycontrails.core import flight, fuel
|
|
|
20
14
|
from pycontrails.core.fleet import Fleet
|
|
21
15
|
from pycontrails.core.flight import Flight
|
|
22
16
|
from pycontrails.core.met import MetDataset
|
|
17
|
+
from pycontrails.core.met_var import AirTemperature, EastwardWind, MetVariable, NorthwardWind
|
|
23
18
|
from pycontrails.core.models import Model, ModelParams, interpolate_met
|
|
24
19
|
from pycontrails.core.vector import GeoVectorDataset
|
|
25
20
|
from pycontrails.physics import jet
|
|
@@ -97,6 +92,9 @@ class AircraftPerformance(Model):
|
|
|
97
92
|
"""
|
|
98
93
|
|
|
99
94
|
source: Flight
|
|
95
|
+
met_variables: tuple[MetVariable, ...] = ()
|
|
96
|
+
optional_met_variables: tuple[MetVariable, ...] = (AirTemperature, EastwardWind, NorthwardWind)
|
|
97
|
+
default_params = AircraftPerformanceParams
|
|
100
98
|
|
|
101
99
|
@overload
|
|
102
100
|
def eval(self, source: Fleet, **params: Any) -> Fleet: ...
|
|
@@ -129,7 +127,8 @@ class AircraftPerformance(Model):
|
|
|
129
127
|
self.set_source_met()
|
|
130
128
|
self._cleanup_indices()
|
|
131
129
|
|
|
132
|
-
# Calculate true airspeed if not included on source
|
|
130
|
+
# Calculate temperature and true airspeed if not included on source
|
|
131
|
+
self.ensure_air_temperature_on_source()
|
|
133
132
|
self.ensure_true_airspeed_on_source()
|
|
134
133
|
|
|
135
134
|
if isinstance(self.source, Fleet):
|
|
@@ -162,23 +161,6 @@ class AircraftPerformance(Model):
|
|
|
162
161
|
- ``total_fuel_burn``: total fuel burn, [:math:`kg`]
|
|
163
162
|
"""
|
|
164
163
|
|
|
165
|
-
@override
|
|
166
|
-
def set_source_met(self, *args: Any, **kwargs: Any) -> None:
|
|
167
|
-
fill_with_isa = self.params["fill_low_altitude_with_isa_temperature"]
|
|
168
|
-
if fill_with_isa and (self.met is None or "air_temperature" not in self.met):
|
|
169
|
-
if "air_temperature" in self.source:
|
|
170
|
-
_fill_low_altitude_with_isa_temperature(self.source, 0.0)
|
|
171
|
-
else:
|
|
172
|
-
self.source["air_temperature"] = self.source.T_isa()
|
|
173
|
-
fill_with_isa = False # we've just filled it
|
|
174
|
-
|
|
175
|
-
super().set_source_met(*args, **kwargs)
|
|
176
|
-
if not fill_with_isa:
|
|
177
|
-
return
|
|
178
|
-
|
|
179
|
-
met_level_0 = self.met.data["level"][-1].item() # type: ignore[union-attr]
|
|
180
|
-
_fill_low_altitude_with_isa_temperature(self.source, met_level_0)
|
|
181
|
-
|
|
182
164
|
def simulate_fuel_and_performance(
|
|
183
165
|
self,
|
|
184
166
|
*,
|
|
@@ -491,25 +473,57 @@ class AircraftPerformance(Model):
|
|
|
491
473
|
Derived performance metrics at each waypoint.
|
|
492
474
|
"""
|
|
493
475
|
|
|
494
|
-
def
|
|
476
|
+
def ensure_air_temperature_on_source(self) -> None:
|
|
477
|
+
"""Add ``air_temperature`` field to :attr:`source` data if not already present.
|
|
478
|
+
|
|
479
|
+
This function operates in-place. If ``air_temperature`` is not already present
|
|
480
|
+
on :attr:`source`, it is calculated by interpolation from met data.
|
|
481
|
+
"""
|
|
482
|
+
fill_with_isa = self.params["fill_low_altitude_with_isa_temperature"]
|
|
483
|
+
|
|
484
|
+
if "air_temperature" in self.source:
|
|
485
|
+
if not fill_with_isa:
|
|
486
|
+
return
|
|
487
|
+
_fill_low_altitude_with_isa_temperature(self.source, 0.0)
|
|
488
|
+
return
|
|
489
|
+
|
|
490
|
+
temp_available = self.met is not None and "air_temperature" in self.met
|
|
491
|
+
|
|
492
|
+
if not temp_available:
|
|
493
|
+
if fill_with_isa:
|
|
494
|
+
self.source["air_temperature"] = self.source.T_isa()
|
|
495
|
+
return
|
|
496
|
+
msg = (
|
|
497
|
+
"Cannot compute air temperature without providing met data that includes an "
|
|
498
|
+
"'air_temperature' variable. Either include met data with 'air_temperature' "
|
|
499
|
+
"in the model constructor, define 'air_temperature' data on the flight, or set "
|
|
500
|
+
"'fill_low_altitude_with_isa_temperature' to True."
|
|
501
|
+
)
|
|
502
|
+
raise ValueError(msg)
|
|
503
|
+
|
|
504
|
+
interpolate_met(self.met, self.source, "air_temperature", **self.interp_kwargs)
|
|
505
|
+
|
|
506
|
+
if not fill_with_isa:
|
|
507
|
+
return
|
|
508
|
+
|
|
509
|
+
met_level_0 = self.met.data["level"][-1].item() # type: ignore[union-attr]
|
|
510
|
+
_fill_low_altitude_with_isa_temperature(self.source, met_level_0)
|
|
511
|
+
|
|
512
|
+
def ensure_true_airspeed_on_source(self) -> None:
|
|
495
513
|
"""Add ``true_airspeed`` field to :attr:`source` data if not already present.
|
|
496
514
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
npt.NDArray[np.floating]
|
|
500
|
-
True airspeed, [:math:`m s^{-1}`]. If ``true_airspeed`` is already present
|
|
501
|
-
on :attr:`source`, this is returned directly. Otherwise, it is calculated
|
|
502
|
-
using :meth:`Flight.segment_true_airspeed`.
|
|
515
|
+
This function operates in-place. If ``true_airspeed`` is not already present
|
|
516
|
+
on :attr:`source`, it is calculated using :meth:`Flight.segment_true_airspeed`.
|
|
503
517
|
"""
|
|
504
518
|
tas = self.source.get("true_airspeed")
|
|
505
519
|
fill_with_groundspeed = self.params["fill_low_altitude_with_zero_wind"]
|
|
506
520
|
|
|
507
521
|
if tas is not None:
|
|
508
522
|
if not fill_with_groundspeed:
|
|
509
|
-
return
|
|
523
|
+
return
|
|
510
524
|
cond = np.isnan(tas)
|
|
511
525
|
tas[cond] = self.source.segment_groundspeed()[cond]
|
|
512
|
-
return
|
|
526
|
+
return
|
|
513
527
|
|
|
514
528
|
# Use current cocip convention: eastward_wind on met, u_wind on source
|
|
515
529
|
wind_available = ("u_wind" in self.source and "v_wind" in self.source) or (
|
|
@@ -520,7 +534,7 @@ class AircraftPerformance(Model):
|
|
|
520
534
|
if fill_with_groundspeed:
|
|
521
535
|
tas = self.source.segment_groundspeed()
|
|
522
536
|
self.source["true_airspeed"] = tas
|
|
523
|
-
return
|
|
537
|
+
return
|
|
524
538
|
msg = (
|
|
525
539
|
"Cannot compute 'true_airspeed' without 'eastward_wind' and 'northward_wind' "
|
|
526
540
|
"met data. Either include met data in the model constructor, define "
|
|
@@ -545,7 +559,6 @@ class AircraftPerformance(Model):
|
|
|
545
559
|
|
|
546
560
|
out = self.source.segment_true_airspeed(u, v)
|
|
547
561
|
self.source["true_airspeed"] = out
|
|
548
|
-
return out
|
|
549
562
|
|
|
550
563
|
|
|
551
564
|
@dataclasses.dataclass
|
pycontrails/core/models.py
CHANGED
|
@@ -706,17 +706,20 @@ class Model(ABC):
|
|
|
706
706
|
# https://github.com/python/cpython/blob/618b7a8260bb40290d6551f24885931077309590/Lib/collections/__init__.py#L231
|
|
707
707
|
__marker = object()
|
|
708
708
|
|
|
709
|
-
def
|
|
710
|
-
|
|
709
|
+
def get_data_param(
|
|
710
|
+
self, other: SourceType, key: str, default: Any = __marker, *, set_attr: bool = True
|
|
711
|
+
) -> Any:
|
|
712
|
+
"""Get data from other source-compatible object with default set by model parameter key.
|
|
711
713
|
|
|
712
714
|
Retrieves data with the following hierarchy:
|
|
713
715
|
|
|
714
|
-
1. :attr:`
|
|
715
|
-
2. :attr:`
|
|
716
|
+
1. :attr:`other.data[key]`. Returns ``np.ndarray | xr.DataArray``.
|
|
717
|
+
2. :attr:`other.attrs[key]`
|
|
716
718
|
3. :attr:`params[key]`
|
|
717
719
|
4. ``default``
|
|
718
720
|
|
|
719
|
-
In case 3., the value of :attr:`params[key]` is attached to :attr:`
|
|
721
|
+
In case 3., the value of :attr:`params[key]` is attached to :attr:`other.attrs[key]`
|
|
722
|
+
unless ``set_attr`` is set to False.
|
|
720
723
|
|
|
721
724
|
Parameters
|
|
722
725
|
----------
|
|
@@ -731,31 +734,33 @@ class Model(ABC):
|
|
|
731
734
|
Returns
|
|
732
735
|
-------
|
|
733
736
|
Any
|
|
734
|
-
Value(s) found for key in
|
|
737
|
+
Value(s) found for key in ``other`` data, ``other`` attrs, or model params
|
|
735
738
|
|
|
736
739
|
Raises
|
|
737
740
|
------
|
|
738
741
|
KeyError
|
|
739
742
|
Raises KeyError if key is not found in any location and ``default`` is not provided.
|
|
740
743
|
|
|
744
|
+
|
|
741
745
|
See Also
|
|
742
746
|
--------
|
|
747
|
+
- get_source_param
|
|
743
748
|
- GeoVectorDataset.get_data_or_attr
|
|
744
749
|
"""
|
|
745
750
|
marker = self.__marker
|
|
746
751
|
|
|
747
|
-
out =
|
|
752
|
+
out = other.data.get(key, marker)
|
|
748
753
|
if out is not marker:
|
|
749
754
|
return out
|
|
750
755
|
|
|
751
|
-
out =
|
|
756
|
+
out = other.attrs.get(key, marker)
|
|
752
757
|
if out is not marker:
|
|
753
758
|
return out
|
|
754
759
|
|
|
755
760
|
out = self.params.get(key, marker)
|
|
756
761
|
if out is not marker:
|
|
757
762
|
if set_attr:
|
|
758
|
-
|
|
763
|
+
other.attrs[key] = out
|
|
759
764
|
|
|
760
765
|
return out
|
|
761
766
|
|
|
@@ -765,6 +770,46 @@ class Model(ABC):
|
|
|
765
770
|
msg = f"Key '{key}' not found in source data, attrs, or model params"
|
|
766
771
|
raise KeyError(msg)
|
|
767
772
|
|
|
773
|
+
def get_source_param(self, key: str, default: Any = __marker, *, set_attr: bool = True) -> Any:
|
|
774
|
+
"""Get source data with default set by parameter key.
|
|
775
|
+
|
|
776
|
+
Retrieves data with the following hierarchy:
|
|
777
|
+
|
|
778
|
+
1. :attr:`source.data[key]`. Returns ``np.ndarray | xr.DataArray``.
|
|
779
|
+
2. :attr:`source.attrs[key]`
|
|
780
|
+
3. :attr:`params[key]`
|
|
781
|
+
4. ``default``
|
|
782
|
+
|
|
783
|
+
In case 3., the value of :attr:`params[key]` is attached to :attr:`source.attrs[key]`
|
|
784
|
+
unless ``set_attr`` is set to False.
|
|
785
|
+
|
|
786
|
+
Parameters
|
|
787
|
+
----------
|
|
788
|
+
key : str
|
|
789
|
+
Key to retrieve
|
|
790
|
+
default : Any, optional
|
|
791
|
+
Default value if key is not found.
|
|
792
|
+
set_attr : bool, optional
|
|
793
|
+
If True (default), set :attr:`source.attrs[key]` to :attr:`params[key]` if found.
|
|
794
|
+
This allows for better post model evaluation tracking.
|
|
795
|
+
|
|
796
|
+
Returns
|
|
797
|
+
-------
|
|
798
|
+
Any
|
|
799
|
+
Value(s) found for key in source data, source attrs, or model params
|
|
800
|
+
|
|
801
|
+
Raises
|
|
802
|
+
------
|
|
803
|
+
KeyError
|
|
804
|
+
Raises KeyError if key is not found in any location and ``default`` is not provided.
|
|
805
|
+
|
|
806
|
+
See Also
|
|
807
|
+
--------
|
|
808
|
+
- get_data_param
|
|
809
|
+
- GeoVectorDataset.get_data_or_attr
|
|
810
|
+
"""
|
|
811
|
+
return self.get_data_param(self.source, key, default, set_attr=set_attr)
|
|
812
|
+
|
|
768
813
|
def _cleanup_indices(self) -> None:
|
|
769
814
|
"""Cleanup indices artifacts if ``params["interpolation_use_indices"]`` is True."""
|
|
770
815
|
if self.params["interpolation_use_indices"] and isinstance(self.source, GeoVectorDataset):
|
|
Binary file
|
pycontrails/datalib/goes.py
CHANGED
|
@@ -16,6 +16,7 @@ from __future__ import annotations
|
|
|
16
16
|
|
|
17
17
|
import datetime
|
|
18
18
|
import enum
|
|
19
|
+
import os
|
|
19
20
|
import tempfile
|
|
20
21
|
from collections.abc import Iterable
|
|
21
22
|
|
|
@@ -566,9 +567,12 @@ class GOES:
|
|
|
566
567
|
|
|
567
568
|
def _load_via_tempfile(data: bytes) -> xr.Dataset:
|
|
568
569
|
"""Load xarray dataset via temporary file."""
|
|
569
|
-
with tempfile.NamedTemporaryFile(
|
|
570
|
+
with tempfile.NamedTemporaryFile(delete=False) as tmp:
|
|
570
571
|
tmp.write(data)
|
|
572
|
+
try:
|
|
571
573
|
return xr.load_dataset(tmp.name)
|
|
574
|
+
finally:
|
|
575
|
+
os.remove(tmp.name)
|
|
572
576
|
|
|
573
577
|
|
|
574
578
|
def _concat_c02(ds1: XArrayType, ds2: XArrayType) -> XArrayType:
|
|
@@ -660,6 +664,10 @@ def to_true_color(da: xr.DataArray, gamma: float = 2.2) -> npt.NDArray[np.float3
|
|
|
660
664
|
----------
|
|
661
665
|
- `Unidata's true color recipe <https://unidata.github.io/python-gallery/examples/mapping_GOES16_TrueColor.html>`_
|
|
662
666
|
"""
|
|
667
|
+
if not np.all(np.isin([1, 2, 3], da["band_id"])):
|
|
668
|
+
msg = "DataArray must contain bands 1, 2, and 3 for true color"
|
|
669
|
+
raise ValueError(msg)
|
|
670
|
+
|
|
663
671
|
red = da.sel(band_id=2).values
|
|
664
672
|
green = da.sel(band_id=3).values
|
|
665
673
|
blue = da.sel(band_id=1).values
|
|
@@ -716,6 +724,9 @@ def to_ash(da: xr.DataArray, convention: str = "SEVIRI") -> npt.NDArray[np.float
|
|
|
716
724
|
array([0.0127004 , 0.22793579, 0.3930847 ], dtype=float32)
|
|
717
725
|
"""
|
|
718
726
|
if convention == "standard":
|
|
727
|
+
if not np.all(np.isin([11, 13, 14, 15], da["band_id"])):
|
|
728
|
+
msg = "DataArray must contain bands 11, 13, 14, and 15 for standard ash"
|
|
729
|
+
raise ValueError(msg)
|
|
719
730
|
c11 = da.sel(band_id=11).values # 8.44
|
|
720
731
|
c13 = da.sel(band_id=13).values # 10.33
|
|
721
732
|
c14 = da.sel(band_id=14).values # 11.19
|
|
@@ -725,7 +736,10 @@ def to_ash(da: xr.DataArray, convention: str = "SEVIRI") -> npt.NDArray[np.float
|
|
|
725
736
|
green = c14 - c11
|
|
726
737
|
blue = c13
|
|
727
738
|
|
|
728
|
-
elif convention in
|
|
739
|
+
elif convention in ("SEVIRI", "MIT"): # retain MIT for backwards compatibility
|
|
740
|
+
if not np.all(np.isin([11, 14, 15], da["band_id"])):
|
|
741
|
+
msg = "DataArray must contain bands 11, 14, and 15 for SEVIRI ash"
|
|
742
|
+
raise ValueError(msg)
|
|
729
743
|
c11 = da.sel(band_id=11).values # 8.44
|
|
730
744
|
c14 = da.sel(band_id=14).values # 11.19
|
|
731
745
|
c15 = da.sel(band_id=15).values # 12.27
|
|
@@ -770,3 +784,133 @@ def _clip_and_scale(
|
|
|
770
784
|
Clipped and scaled array.
|
|
771
785
|
"""
|
|
772
786
|
return (arr.clip(low, high) - low) / (high - low)
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
def parallax_correct(
|
|
790
|
+
longitude: npt.NDArray[np.floating],
|
|
791
|
+
latitude: npt.NDArray[np.floating],
|
|
792
|
+
altitude: npt.NDArray[np.floating],
|
|
793
|
+
goes_da: xr.DataArray,
|
|
794
|
+
) -> tuple[npt.NDArray[np.floating], npt.NDArray[np.floating]]:
|
|
795
|
+
r"""Apply parallax correction to WGS84 geodetic coordinates based on satellite perspective.
|
|
796
|
+
|
|
797
|
+
This function considers the ray from the satellite to the points of interest and finds
|
|
798
|
+
the intersection of this ray with the WGS84 ellipsoid. The intersection point is then
|
|
799
|
+
returned as the corrected longitude and latitude coordinates.
|
|
800
|
+
|
|
801
|
+
::
|
|
802
|
+
|
|
803
|
+
@ satellite
|
|
804
|
+
\
|
|
805
|
+
\
|
|
806
|
+
\
|
|
807
|
+
\
|
|
808
|
+
\
|
|
809
|
+
* aircraft
|
|
810
|
+
\
|
|
811
|
+
\
|
|
812
|
+
x parallax corrected aircraft
|
|
813
|
+
------------------------- surface
|
|
814
|
+
|
|
815
|
+
If the point of interest is not visible from the satellite (ie, on the opposite side of the
|
|
816
|
+
earth), the function returns nan for the corrected coordinates.
|
|
817
|
+
|
|
818
|
+
This function requires the :mod:`pyproj` package to be installed.
|
|
819
|
+
|
|
820
|
+
Parameters
|
|
821
|
+
----------
|
|
822
|
+
longitude : npt.NDArray[np.floating]
|
|
823
|
+
A 1D array of longitudes in degrees.
|
|
824
|
+
latitude : npt.NDArray[np.floating]
|
|
825
|
+
A 1D array of latitudes in degrees.
|
|
826
|
+
altitude : npt.NDArray[np.floating]
|
|
827
|
+
A 1D array of altitudes in meters.
|
|
828
|
+
goes_da : xr.DataArray
|
|
829
|
+
DataArray containing the GOES projection information. Only the ``goes_imager_projection``
|
|
830
|
+
field of the :attr:`xr.DataArray.attrs` is used.
|
|
831
|
+
|
|
832
|
+
Returns
|
|
833
|
+
-------
|
|
834
|
+
tuple[npt.NDArray[np.floating], npt.NDArray[np.floating]]
|
|
835
|
+
A tuple containing the corrected longitude and latitude coordinates.
|
|
836
|
+
|
|
837
|
+
"""
|
|
838
|
+
goes_imager_projection = goes_da.attrs["goes_imager_projection"]
|
|
839
|
+
sat_lon = goes_imager_projection["longitude_of_projection_origin"]
|
|
840
|
+
sat_lat = goes_imager_projection["latitude_of_projection_origin"]
|
|
841
|
+
sat_alt = goes_imager_projection["perspective_point_height"]
|
|
842
|
+
|
|
843
|
+
try:
|
|
844
|
+
import pyproj
|
|
845
|
+
except ModuleNotFoundError as exc:
|
|
846
|
+
dependencies.raise_module_not_found_error(
|
|
847
|
+
name="parallax_correct function",
|
|
848
|
+
package_name="pyproj",
|
|
849
|
+
module_not_found_error=exc,
|
|
850
|
+
pycontrails_optional_package="pyproj",
|
|
851
|
+
)
|
|
852
|
+
|
|
853
|
+
# Convert from WGS84 to ECEF coordinates
|
|
854
|
+
ecef_crs = pyproj.CRS("EPSG:4978")
|
|
855
|
+
transformer = pyproj.Transformer.from_crs("WGS84", ecef_crs, always_xy=True)
|
|
856
|
+
|
|
857
|
+
p0 = np.array(transformer.transform([sat_lon], [sat_lat], [sat_alt]))
|
|
858
|
+
p1 = np.array(transformer.transform(longitude, latitude, altitude))
|
|
859
|
+
|
|
860
|
+
# Major and minor axes of the ellipsoid
|
|
861
|
+
a = ecef_crs.ellipsoid.semi_major_metre # type: ignore[union-attr]
|
|
862
|
+
b = ecef_crs.ellipsoid.semi_minor_metre # type: ignore[union-attr]
|
|
863
|
+
intersection = _intersection_with_ellipsoid(p0, p1, a, b)
|
|
864
|
+
|
|
865
|
+
# Convert back to WGS84 coordinates
|
|
866
|
+
inv_transformer = pyproj.Transformer.from_crs(ecef_crs, "WGS84", always_xy=True)
|
|
867
|
+
return inv_transformer.transform(*intersection)[:2] # final coord is (close to) 0
|
|
868
|
+
|
|
869
|
+
|
|
870
|
+
def _intersection_with_ellipsoid(
|
|
871
|
+
p0: npt.NDArray[np.floating],
|
|
872
|
+
p1: npt.NDArray[np.floating],
|
|
873
|
+
a: float,
|
|
874
|
+
b: float,
|
|
875
|
+
) -> npt.NDArray[np.floating]:
|
|
876
|
+
"""Find the intersection of a line with the surface of an ellipsoid."""
|
|
877
|
+
# Calculate the direction vector
|
|
878
|
+
px, py, pz = p0
|
|
879
|
+
v = p1 - p0
|
|
880
|
+
vx, vy, vz = v
|
|
881
|
+
|
|
882
|
+
# The line between p0 and p1 in parametric form is p(t) = p0 + t * v
|
|
883
|
+
# We need to find t such that p(t) lies on the ellipsoid
|
|
884
|
+
# x^2 / a^2 + y^2 / a^2 + z^2 / b^2 = 1
|
|
885
|
+
# (px + t * vx)^2 / a^2 + (py + t * vy)^2 / a^2 + (pz + t * vz)^2 / b^2 = 1
|
|
886
|
+
# Rearranging gives a quadratic in t
|
|
887
|
+
|
|
888
|
+
# Calculate the coefficients of this quadratic equation
|
|
889
|
+
A = vx**2 / a**2 + vy**2 / a**2 + vz**2 / b**2
|
|
890
|
+
B = 2 * (px * vx / a**2 + py * vy / a**2 + pz * vz / b**2)
|
|
891
|
+
C = px**2 / a**2 + py**2 / a**2 + pz**2 / b**2 - 1.0
|
|
892
|
+
|
|
893
|
+
# Calculate the discriminant
|
|
894
|
+
D = B**2 - 4 * A * C
|
|
895
|
+
sqrtD = np.sqrt(D, where=D >= 0, out=np.full_like(D, np.nan))
|
|
896
|
+
|
|
897
|
+
# Calculate the two possible solutions for t
|
|
898
|
+
t0 = (-B + sqrtD) / (2.0 * A)
|
|
899
|
+
t1 = (-B - sqrtD) / (2.0 * A)
|
|
900
|
+
|
|
901
|
+
# Calculate the intersection points
|
|
902
|
+
intersection0 = p0 + t0 * v
|
|
903
|
+
intersection1 = p0 + t1 * v
|
|
904
|
+
|
|
905
|
+
# Pick the intersection point that is closer to the aircraft (p1)
|
|
906
|
+
d0 = np.linalg.norm(intersection0 - p1, axis=0)
|
|
907
|
+
d1 = np.linalg.norm(intersection1 - p1, axis=0)
|
|
908
|
+
out = np.where(d0 < d1, intersection0, intersection1)
|
|
909
|
+
|
|
910
|
+
# Fill the points in which the aircraft is not visible by the satellite with nan
|
|
911
|
+
# This occurs when the earth is between the satellite and the aircraft
|
|
912
|
+
# In other words, we can check for t0 < 1 (or t1 < 1)
|
|
913
|
+
opposite_side = t0 < 1.0
|
|
914
|
+
out[:, opposite_side] = np.nan
|
|
915
|
+
|
|
916
|
+
return out
|
|
@@ -207,7 +207,7 @@ class SyntheticFlight:
|
|
|
207
207
|
|
|
208
208
|
def _id(self) -> int:
|
|
209
209
|
"""Get random flight ID."""
|
|
210
|
-
return self.rng.integers(100_000, 999_999)
|
|
210
|
+
return self.rng.integers(100_000, 999_999).item()
|
|
211
211
|
|
|
212
212
|
def _define_aircraft(self) -> None:
|
|
213
213
|
"""Define or update instance variables pertaining to flight aircrafts.
|
|
@@ -27,7 +27,6 @@ from pycontrails.core.aircraft_performance import (
|
|
|
27
27
|
)
|
|
28
28
|
from pycontrails.core.flight import Flight
|
|
29
29
|
from pycontrails.core.met import MetDataset
|
|
30
|
-
from pycontrails.core.met_var import AirTemperature, EastwardWind, MetVariable, NorthwardWind
|
|
31
30
|
from pycontrails.models.ps_model import ps_operational_limits as ps_lims
|
|
32
31
|
from pycontrails.models.ps_model.ps_aircraft_params import (
|
|
33
32
|
PSAircraftEngineParams,
|
|
@@ -70,8 +69,6 @@ class PSFlight(AircraftPerformance):
|
|
|
70
69
|
|
|
71
70
|
name = "PSFlight"
|
|
72
71
|
long_name = "Poll-Schumann Aircraft Performance Model"
|
|
73
|
-
met_variables: tuple[MetVariable, ...] = (AirTemperature,)
|
|
74
|
-
optional_met_variables = EastwardWind, NorthwardWind
|
|
75
72
|
default_params = PSFlightParams
|
|
76
73
|
|
|
77
74
|
aircraft_engine_params: Mapping[str, PSAircraftEngineParams]
|
|
@@ -160,10 +157,10 @@ class PSFlight(AircraftPerformance):
|
|
|
160
157
|
time=fl["time"],
|
|
161
158
|
true_airspeed=true_airspeed,
|
|
162
159
|
air_temperature=fl["air_temperature"],
|
|
163
|
-
aircraft_mass=self.
|
|
164
|
-
thrust=self.
|
|
165
|
-
engine_efficiency=self.
|
|
166
|
-
fuel_flow=self.
|
|
160
|
+
aircraft_mass=self.get_data_param(fl, "aircraft_mass", None),
|
|
161
|
+
thrust=self.get_data_param(fl, "thrust", None),
|
|
162
|
+
engine_efficiency=self.get_data_param(fl, "engine_efficiency", None),
|
|
163
|
+
fuel_flow=self.get_data_param(fl, "fuel_flow", None),
|
|
167
164
|
q_fuel=q_fuel,
|
|
168
165
|
n_iter=self.params["n_iter"],
|
|
169
166
|
amass_oew=amass_oew,
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
pycontrails-0.54.
|
|
2
|
-
pycontrails-0.54.
|
|
3
|
-
pycontrails-0.54.
|
|
4
|
-
pycontrails-0.54.
|
|
5
|
-
pycontrails-0.54.
|
|
6
|
-
pycontrails-0.54.
|
|
7
|
-
pycontrails/_version.py,sha256=
|
|
1
|
+
pycontrails-0.54.9.dist-info/RECORD,,
|
|
2
|
+
pycontrails-0.54.9.dist-info/WHEEL,sha256=Kzi_LGfTg-FNEzEX7EqbKCIH8pcIKYoNvyMeGYy6zfA,136
|
|
3
|
+
pycontrails-0.54.9.dist-info/top_level.txt,sha256=Z8J1R_AiBAyCVjNw6jYLdrA68PrQqTg0t3_Yek_IZ0Q,29
|
|
4
|
+
pycontrails-0.54.9.dist-info/METADATA,sha256=GXkDhEy_CnY9nb15RugHFjlcep4jTGWxbUWvEP1JDQ4,9131
|
|
5
|
+
pycontrails-0.54.9.dist-info/licenses/LICENSE,sha256=gJ-h7SFFD1mCfR6a7HILvEtodDT6Iig8bLXdgqR6ucA,10175
|
|
6
|
+
pycontrails-0.54.9.dist-info/licenses/NOTICE,sha256=fiBPdjYibMpDzf8hqcn7TvAQ-yeK10q_Nqq24DnskYg,1962
|
|
7
|
+
pycontrails/_version.py,sha256=l1wvNNahe7IMIrSdzQJEux_vCx8lrDFKSfZfunCXM84,513
|
|
8
8
|
pycontrails/__init__.py,sha256=9ypSB2fKZlKghTvSrjWo6OHm5qfASwiTIvlMew3Olu4,2037
|
|
9
9
|
pycontrails/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
-
pycontrails/core/rgi_cython.cpython-311-darwin.so,sha256=
|
|
10
|
+
pycontrails/core/rgi_cython.cpython-311-darwin.so,sha256=s-WbkKcvE3w1YCWj6CNZrjMqb9UEsCTH4q8Cj2wVvsM,311088
|
|
11
11
|
pycontrails/core/vector.py,sha256=N-3VhPaUEyFSJWjplMKFcv9GLvEqAibKn1zqJWuNZQU,73601
|
|
12
|
-
pycontrails/core/models.py,sha256=
|
|
12
|
+
pycontrails/core/models.py,sha256=z_U39b4Mx7ZIqnqMKgQEV5mB1NIZDnOnB52MBMc2zp8,43815
|
|
13
13
|
pycontrails/core/interpolation.py,sha256=wovjj3TAf3xonVxjarclpvZLyLq6N7wZQQXsI9hT3YA,25713
|
|
14
14
|
pycontrails/core/fleet.py,sha256=0hi_N4R93St-7iD29SE0EnadpBEl_p9lSGtDwpWvGkk,16704
|
|
15
15
|
pycontrails/core/flight.py,sha256=QZTGeZVnZ14UUWHSqgCSU49g_EGQZel-hzKwm_9dcFY,80653
|
|
@@ -19,11 +19,11 @@ pycontrails/core/cache.py,sha256=IIyx726zN7JzNSKV0JJDksMI9OhCLdnJShmBVStRqzI,281
|
|
|
19
19
|
pycontrails/core/__init__.py,sha256=p0O09HxdeXU0X5Z3zrHMlTfXa92YumT3fJ8wJBI5ido,856
|
|
20
20
|
pycontrails/core/flightplan.py,sha256=xgyYLi36OlNKtIFuOHaifcDM6XMBYTyMQlXAtfd-6Js,7519
|
|
21
21
|
pycontrails/core/met.py,sha256=4XQAJrKWBN0SZQSeBpMUnkLn87vYpn2VMiY3dQyFRIw,103992
|
|
22
|
-
pycontrails/core/aircraft_performance.py,sha256=
|
|
22
|
+
pycontrails/core/aircraft_performance.py,sha256=Kk_Rb61jDOWPmCQHwn2jR5vMPmB8b3aq1iTWfiUMj9U,28232
|
|
23
23
|
pycontrails/core/airports.py,sha256=ubYo-WvxKPd_dUcADx6yew9Tqh1a4VJDgX7aFqLYwB8,6775
|
|
24
24
|
pycontrails/core/met_var.py,sha256=lAbp3cko_rzMk_u0kq-F27sUXUxUKikUvCNycwp9ILY,12020
|
|
25
25
|
pycontrails/core/coordinates.py,sha256=0ySsHtqTon7GMbuwmmxMbI92j3ueMteJZh4xxNm5zto,5391
|
|
26
|
-
pycontrails/datalib/goes.py,sha256=
|
|
26
|
+
pycontrails/datalib/goes.py,sha256=6yGmtdKMAuEsMhgrprHxVCOz2mWoiZTYHpO-WrmK804,31919
|
|
27
27
|
pycontrails/datalib/landsat.py,sha256=r6366rEF7fOA7mT5KySCPGJplgGE5LvBw5fMqk-U1oM,19697
|
|
28
28
|
pycontrails/datalib/__init__.py,sha256=hW9NWdFPC3y_2vHMteQ7GgQdop3917MkDaf5ZhU2RBY,369
|
|
29
29
|
pycontrails/datalib/sentinel.py,sha256=hYSxIlQnyJHqtHWlKn73HOK_1pm-_IbGebmkHnh4UcA,17172
|
|
@@ -48,7 +48,7 @@ pycontrails/datalib/gfs/__init__.py,sha256=pXNjb9cJC6ngpuCnoHnmVZ2RHzbHZ0AlsyGvg
|
|
|
48
48
|
pycontrails/datalib/spire/spire.py,sha256=h25BVgSr7E71Ox3-y9WgqFvp-54L08yzb2Ou-iMl7wM,24242
|
|
49
49
|
pycontrails/datalib/spire/__init__.py,sha256=3-My8yQItS6PL0DqXgNaltLqvN6T7nbnNnLD-sy7kt4,186
|
|
50
50
|
pycontrails/datalib/spire/exceptions.py,sha256=U0V_nZTLhxJwrzldvU9PdESx8-zLddRH3FmzkJyFyrI,1714
|
|
51
|
-
pycontrails/ext/synthetic_flight.py,sha256=
|
|
51
|
+
pycontrails/ext/synthetic_flight.py,sha256=wROBQErfr_IhEPndC97fuWbnZQega2Z89VhzoXzZMO8,16802
|
|
52
52
|
pycontrails/ext/cirium.py,sha256=DFPfRwLDwddpucAPRQhyT4bDGh0VvvoViMUd3pidam8,415
|
|
53
53
|
pycontrails/ext/empirical_grid.py,sha256=FPNQA0x4nVwBXFlbs3DgIapSrXFYhoc8b8IX0M4xhBc,4363
|
|
54
54
|
pycontrails/ext/bada.py,sha256=YlQq4nnFyWza1Am2e2ZucpaICHDuUFRTrtVzIKMzf9s,1091
|
|
@@ -94,7 +94,7 @@ pycontrails/models/cocip/radiative_heating.py,sha256=1U4SQWwogtyQ2u6J996kAHP0Ofp
|
|
|
94
94
|
pycontrails/models/cocip/contrail_properties.py,sha256=qdvFykCYBee17G1jDklzyoeYWDMzoOXCP-A6p9P_4sE,56053
|
|
95
95
|
pycontrails/models/cocip/unterstrasser_wake_vortex.py,sha256=edMHuWKzFN1P4EMWC2HRv5ZS_rUI7Q5Nw3LsYkrI0mE,18936
|
|
96
96
|
pycontrails/models/ps_model/__init__.py,sha256=Fuum5Rq8ya8qkvbeq2wh6NDo-42RCRnK1Y-2syYy0Ck,553
|
|
97
|
-
pycontrails/models/ps_model/ps_model.py,sha256=
|
|
97
|
+
pycontrails/models/ps_model/ps_model.py,sha256=fgFekJpGuAu73KvpfLhlAbIwR7JJGwQpLILWmrONywc,31925
|
|
98
98
|
pycontrails/models/ps_model/ps_aircraft_params.py,sha256=I2nBkdnRo9YGMn-0k35ooYpzPNJkHyEH5cU3K-Cz8b0,13350
|
|
99
99
|
pycontrails/models/ps_model/ps_operational_limits.py,sha256=XwMHO8yu8EZUWtxRgjRKwxmCrmKGoHO7Ob6nlfkrthI,16441
|
|
100
100
|
pycontrails/models/ps_model/ps_grid.py,sha256=rBsCEQkGY4cTf57spF0sqCfOo4oC4auE8ngS_mcl0VM,26207
|
|
File without changes
|
|
File without changes
|
|
File without changes
|