nrl-tracker 1.2.0__py3-none-any.whl → 1.3.0__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.
- {nrl_tracker-1.2.0.dist-info → nrl_tracker-1.3.0.dist-info}/METADATA +1 -1
- {nrl_tracker-1.2.0.dist-info → nrl_tracker-1.3.0.dist-info}/RECORD +15 -12
- pytcl/__init__.py +1 -1
- pytcl/atmosphere/__init__.py +32 -1
- pytcl/atmosphere/ionosphere.py +512 -0
- pytcl/containers/rtree.py +199 -0
- pytcl/dynamic_estimation/kalman/square_root.py +52 -571
- pytcl/dynamic_estimation/kalman/sr_ukf.py +302 -0
- pytcl/dynamic_estimation/kalman/ud_filter.py +404 -0
- pytcl/magnetism/__init__.py +7 -0
- pytcl/magnetism/wmm.py +260 -23
- pytcl/mathematical_functions/special_functions/debye.py +132 -26
- {nrl_tracker-1.2.0.dist-info → nrl_tracker-1.3.0.dist-info}/LICENSE +0 -0
- {nrl_tracker-1.2.0.dist-info → nrl_tracker-1.3.0.dist-info}/WHEEL +0 -0
- {nrl_tracker-1.2.0.dist-info → nrl_tracker-1.3.0.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: nrl-tracker
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Python port of the U.S. Naval Research Laboratory's Tracker Component Library for target tracking algorithms
|
|
5
5
|
Author: Original: David F. Crouse, Naval Research Laboratory
|
|
6
6
|
Maintainer: Python Port Contributors
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
pytcl/__init__.py,sha256=
|
|
1
|
+
pytcl/__init__.py,sha256=PLcxb75b02Pbp9mvJ3PZYQR_ZEhjJybFZhYJ1CQTjL8,1893
|
|
2
2
|
pytcl/logging_config.py,sha256=j7Zrkal5LwUIos-_Dm3cGKUR-jMkFdSZZikJTtzTeoE,8883
|
|
3
3
|
pytcl/assignment_algorithms/__init__.py,sha256=f9V-TkEVmiKYYyth4PTpDfJvA7yYV_ys6Zix-QwWIYY,2136
|
|
4
4
|
pytcl/assignment_algorithms/data_association.py,sha256=tsRxWJZk9aAPmE99BKXGouEpFfZrjPjb4HXvgxFUHhU,11405
|
|
@@ -16,7 +16,8 @@ pytcl/astronomical/orbital_mechanics.py,sha256=8GssRanwTowCl6PJYqmB_SDnNznLUq5gk
|
|
|
16
16
|
pytcl/astronomical/reference_frames.py,sha256=gkbQrhHY_EN0xt8i7m0dIJp67GgpEcDArY7J3YH-Mb8,17981
|
|
17
17
|
pytcl/astronomical/relativity.py,sha256=YPsXLD-VRh-nqs1laC-wKpRO00fflm4GkyLhojPydbo,15441
|
|
18
18
|
pytcl/astronomical/time_systems.py,sha256=Jg0Zaq60hc4Ts1aQtb5bK4KSZhz-uQse8gYC89Y0-TA,15243
|
|
19
|
-
pytcl/atmosphere/__init__.py,sha256=
|
|
19
|
+
pytcl/atmosphere/__init__.py,sha256=swugW8rY2Jof-z_kPQ2P9vInBYjr1M69C1Q8AgN3RVo,1457
|
|
20
|
+
pytcl/atmosphere/ionosphere.py,sha256=1qC3hY-27pD0XcLBjU735deKYmmi6qnj2fDG1zNbTqg,14681
|
|
20
21
|
pytcl/atmosphere/models.py,sha256=pMLv8D7qoFqLZrlbTHLJJULOdDdhPskJ1m7KVKLV63E,9584
|
|
21
22
|
pytcl/clustering/__init__.py,sha256=bYdhC_XJEt6KUUni9bIPxaddXNEGmIJQvGkA14rK4J8,1697
|
|
22
23
|
pytcl/clustering/dbscan.py,sha256=PS6QlOwHFerbZNEb3zcNhN4oNQpgOOw5y0WskQzyKIo,7364
|
|
@@ -29,7 +30,7 @@ pytcl/containers/cluster_set.py,sha256=y36D5TNzvCN6xjg6taP2SD_MC-O5iLq9ncBlHsQ5I
|
|
|
29
30
|
pytcl/containers/covertree.py,sha256=ePIqH1-0CxSFqCwmQ_G6MXPlXs4xH0gsmoZXF8QxhDk,13271
|
|
30
31
|
pytcl/containers/kd_tree.py,sha256=9CKHAzid0DZ879hut8M4dyW_976pIWNLX3uWzELPIu4,18563
|
|
31
32
|
pytcl/containers/measurement_set.py,sha256=87AbdoZIUspn1yJsiMpyQ5LoEVcerUnXefXGGPtFTJg,12654
|
|
32
|
-
pytcl/containers/rtree.py,sha256=
|
|
33
|
+
pytcl/containers/rtree.py,sha256=Ss1ks6xlLnNeRlKpHoWxMcgQTPhVwjT5agMeq5DaH5A,21844
|
|
33
34
|
pytcl/containers/track_list.py,sha256=6q9Qgcwm-8H_JqtOCsMssF27av4XaSkhfDl-MWb1ABc,12520
|
|
34
35
|
pytcl/containers/vptree.py,sha256=4tUq0ktafusU1PILZkQxi27CZryKlsHtFbym-vZYQWk,8747
|
|
35
36
|
pytcl/coordinate_systems/__init__.py,sha256=jwYhu_-9AvOeP9WLG9PYtyDwfe0GjxNZ9-xCqiLymW4,3909
|
|
@@ -54,7 +55,9 @@ pytcl/dynamic_estimation/batch_estimation/__init__.py,sha256=JQ0s76Enov5a7plA4En
|
|
|
54
55
|
pytcl/dynamic_estimation/kalman/__init__.py,sha256=yoFLj0n-NRkdZnRVL-BkHBlATk8pfZEVlsY3BhSYgKc,2387
|
|
55
56
|
pytcl/dynamic_estimation/kalman/extended.py,sha256=51uhCqkZmErCx6MBfMq8eIQW8bD7n34zCe4v4dxNiMQ,10384
|
|
56
57
|
pytcl/dynamic_estimation/kalman/linear.py,sha256=1Zgg9gZya0Vxs9im7sPUqLj0Luo463vS-RSa6GCReFI,12248
|
|
57
|
-
pytcl/dynamic_estimation/kalman/square_root.py,sha256=
|
|
58
|
+
pytcl/dynamic_estimation/kalman/square_root.py,sha256=N7-lDml7Nw5HM5b5D11WOwG7rY1JlVoyis0ho-vk0H4,13345
|
|
59
|
+
pytcl/dynamic_estimation/kalman/sr_ukf.py,sha256=LeRGBSDpvSP9CyTZjEroz2Z2uueb6YpmzYricba0PDk,8640
|
|
60
|
+
pytcl/dynamic_estimation/kalman/ud_filter.py,sha256=fzSdcVO_P8-E2oXc32n79Rn56GI2VUmOoMDYBHw7keM,10077
|
|
58
61
|
pytcl/dynamic_estimation/kalman/unscented.py,sha256=RDK6USkko9lj1K4-WYydh3_8GMZNng_PJVjfc-c_OwM,15427
|
|
59
62
|
pytcl/dynamic_estimation/measurement_update/__init__.py,sha256=8rlyJwVpxf0fZj-AFo1hlewvryZRhUzcy3F8uMe6I8c,48
|
|
60
63
|
pytcl/dynamic_estimation/particle_filters/__init__.py,sha256=-DRF5rVF2749suLlArmkTvVkqeMcV_mIx0eLeTj6wNU,906
|
|
@@ -76,10 +79,10 @@ pytcl/gravity/egm.py,sha256=47I8nyXNhXUKPkufXahs4JGsBcqhM-9z2xGz0X4JPmU,18422
|
|
|
76
79
|
pytcl/gravity/models.py,sha256=rdY3Do4M1eRFO74gu3xy-bBn7tox3zM49wYbfnsIQWw,11159
|
|
77
80
|
pytcl/gravity/spherical_harmonics.py,sha256=IpBh0LW4BQMzJck9Li6yveGlvYigCuXaoApRWDPsWtc,16498
|
|
78
81
|
pytcl/gravity/tides.py,sha256=hef_BGewFGD7dJwg0t09Z6tfWLco_avATLuu66rnTpI,27733
|
|
79
|
-
pytcl/magnetism/__init__.py,sha256=
|
|
82
|
+
pytcl/magnetism/__init__.py,sha256=pBASOzCPHNnYqUH_XDEblhGtjz50vY9uW2KS25A0zQQ,2701
|
|
80
83
|
pytcl/magnetism/emm.py,sha256=5Jwl99wvdKYtx1-3LBB7x-w5KT-fqLiRg7uBW0Ai_Gw,22292
|
|
81
84
|
pytcl/magnetism/igrf.py,sha256=3g0PsH8IdbwQQS28OR5XWD-g-QxvfUva7jOkKToxndQ,13384
|
|
82
|
-
pytcl/magnetism/wmm.py,sha256=
|
|
85
|
+
pytcl/magnetism/wmm.py,sha256=q7AJrpOrn1EBbWNjltPxhGEwg3P44ay1pc4dI5OIyUY,23444
|
|
83
86
|
pytcl/mathematical_functions/__init__.py,sha256=zeJ1ffRRl83k2NHn3HTn-fgtFoWNPq6LCALc3xRo4Do,3767
|
|
84
87
|
pytcl/mathematical_functions/basic_matrix/__init__.py,sha256=kZv3kMAEHBdVxhbyMxTyM0s-4XJP1tK6po82UsIE4tc,1318
|
|
85
88
|
pytcl/mathematical_functions/basic_matrix/decompositions.py,sha256=PWJsFDiXM2T78RHdxBJZPFnl8kFbNZQpHrbpw0mhE00,12268
|
|
@@ -100,7 +103,7 @@ pytcl/mathematical_functions/signal_processing/filters.py,sha256=8Ojf4h4rfiucBXq
|
|
|
100
103
|
pytcl/mathematical_functions/signal_processing/matched_filter.py,sha256=AahJZRZk2IIXzRL7www0n8bc0XoKabaLOe8yYNSjuDY,22893
|
|
101
104
|
pytcl/mathematical_functions/special_functions/__init__.py,sha256=AJBCKj32daQxdahUQckW0bWowzOoapxni2eZnVXERdg,3859
|
|
102
105
|
pytcl/mathematical_functions/special_functions/bessel.py,sha256=M0mwLQBaUXEHA8wyKReJ2D66I1v1XR7y-txAipd-WDs,14377
|
|
103
|
-
pytcl/mathematical_functions/special_functions/debye.py,sha256=
|
|
106
|
+
pytcl/mathematical_functions/special_functions/debye.py,sha256=5u-2KIQniwoVlqGSQguYhO7RcFQXtvY0aetiDiMYtQ0,9576
|
|
104
107
|
pytcl/mathematical_functions/special_functions/elliptic.py,sha256=WyzBkrfZufIR5dUmCKGcxp6KNpVDrU89NGLDyRrZOqQ,7418
|
|
105
108
|
pytcl/mathematical_functions/special_functions/error_functions.py,sha256=a3SS8FYAMRv1KdCmebOZL95yjvVt9gZRF2XOjHvQ9M8,6253
|
|
106
109
|
pytcl/mathematical_functions/special_functions/gamma_functions.py,sha256=xXN_9SCokH10HjE8PpaPKHYVK_RZRHRAbZgR2mZYIAA,10191
|
|
@@ -145,8 +148,8 @@ pytcl/trackers/mht.py,sha256=7mwhMmja3ri2wnx7W1wueDGn2r3ArwAxJDPUJ7IZAkQ,20617
|
|
|
145
148
|
pytcl/trackers/multi_target.py,sha256=hvt89ERhMwpcHcIJeKHnkQSKdE3_LoRiX-gbaGoo300,10516
|
|
146
149
|
pytcl/trackers/single_target.py,sha256=Yy3FwaNTArMWcaod-0HVeiioNV4xLWxNDn_7ZPVqQYs,6562
|
|
147
150
|
pytcl/transponders/__init__.py,sha256=5fL4u3lKCYgPLo5uFeuZbtRZkJPABntuKYGUvVgMMEI,41
|
|
148
|
-
nrl_tracker-1.
|
|
149
|
-
nrl_tracker-1.
|
|
150
|
-
nrl_tracker-1.
|
|
151
|
-
nrl_tracker-1.
|
|
152
|
-
nrl_tracker-1.
|
|
151
|
+
nrl_tracker-1.3.0.dist-info/LICENSE,sha256=rB5G4WppIIUzMOYr2N6uyYlNJ00hRJqE5tie6BMvYuE,1612
|
|
152
|
+
nrl_tracker-1.3.0.dist-info/METADATA,sha256=9DRW_Wk7EPorvcFjSKjcpNIhklbfyAXEffZHD8BeS0E,10145
|
|
153
|
+
nrl_tracker-1.3.0.dist-info/WHEEL,sha256=pL8R0wFFS65tNSRnaOVrsw9EOkOqxLrlUPenUYnJKNo,91
|
|
154
|
+
nrl_tracker-1.3.0.dist-info/top_level.txt,sha256=17megxcrTPBWwPZTh6jTkwTKxX7No-ZqRpyvElnnO-s,6
|
|
155
|
+
nrl_tracker-1.3.0.dist-info/RECORD,,
|
pytcl/__init__.py
CHANGED
pytcl/atmosphere/__init__.py
CHANGED
|
@@ -3,8 +3,26 @@ Atmospheric models for tracking applications.
|
|
|
3
3
|
|
|
4
4
|
This module provides standard atmosphere models used for computing
|
|
5
5
|
temperature, pressure, density, and other properties at various altitudes.
|
|
6
|
+
|
|
7
|
+
Submodules
|
|
8
|
+
----------
|
|
9
|
+
models : Standard atmosphere models (US76, ISA)
|
|
10
|
+
ionosphere : Ionospheric models for GPS/GNSS corrections
|
|
6
11
|
"""
|
|
7
12
|
|
|
13
|
+
from pytcl.atmosphere.ionosphere import (
|
|
14
|
+
DEFAULT_KLOBUCHAR,
|
|
15
|
+
F_L1,
|
|
16
|
+
F_L2,
|
|
17
|
+
IonosphereState,
|
|
18
|
+
KlobucharCoefficients,
|
|
19
|
+
dual_frequency_tec,
|
|
20
|
+
ionospheric_delay_from_tec,
|
|
21
|
+
klobuchar_delay,
|
|
22
|
+
magnetic_latitude,
|
|
23
|
+
scintillation_index,
|
|
24
|
+
simple_iri,
|
|
25
|
+
)
|
|
8
26
|
from pytcl.atmosphere.models import G0 # Constants
|
|
9
27
|
from pytcl.atmosphere.models import (
|
|
10
28
|
GAMMA,
|
|
@@ -21,17 +39,30 @@ from pytcl.atmosphere.models import (
|
|
|
21
39
|
)
|
|
22
40
|
|
|
23
41
|
__all__ = [
|
|
42
|
+
# Atmosphere state and models
|
|
24
43
|
"AtmosphereState",
|
|
25
44
|
"us_standard_atmosphere_1976",
|
|
26
45
|
"isa_atmosphere",
|
|
27
46
|
"altitude_from_pressure",
|
|
28
47
|
"mach_number",
|
|
29
48
|
"true_airspeed_from_mach",
|
|
30
|
-
#
|
|
49
|
+
# Atmosphere constants
|
|
31
50
|
"T0",
|
|
32
51
|
"P0",
|
|
33
52
|
"RHO0",
|
|
34
53
|
"G0",
|
|
35
54
|
"R",
|
|
36
55
|
"GAMMA",
|
|
56
|
+
# Ionosphere
|
|
57
|
+
"IonosphereState",
|
|
58
|
+
"KlobucharCoefficients",
|
|
59
|
+
"DEFAULT_KLOBUCHAR",
|
|
60
|
+
"klobuchar_delay",
|
|
61
|
+
"dual_frequency_tec",
|
|
62
|
+
"ionospheric_delay_from_tec",
|
|
63
|
+
"simple_iri",
|
|
64
|
+
"magnetic_latitude",
|
|
65
|
+
"scintillation_index",
|
|
66
|
+
"F_L1",
|
|
67
|
+
"F_L2",
|
|
37
68
|
]
|
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ionospheric models for radio propagation and navigation applications.
|
|
3
|
+
|
|
4
|
+
This module provides ionospheric models used for computing signal delays,
|
|
5
|
+
electron density profiles, and Total Electron Content (TEC) estimates.
|
|
6
|
+
These are essential for GPS/GNSS corrections and radio wave propagation.
|
|
7
|
+
|
|
8
|
+
Models
|
|
9
|
+
------
|
|
10
|
+
- Klobuchar: GPS broadcast ionospheric model (single-frequency correction)
|
|
11
|
+
- NeQuick: Galileo ionospheric model placeholder
|
|
12
|
+
- IRI: International Reference Ionosphere simplified model
|
|
13
|
+
|
|
14
|
+
References
|
|
15
|
+
----------
|
|
16
|
+
.. [1] Klobuchar, J.A. (1987). "Ionospheric Time-Delay Algorithm for
|
|
17
|
+
Single-Frequency GPS Users". IEEE Transactions on Aerospace and
|
|
18
|
+
Electronic Systems, AES-23(3), 325-331.
|
|
19
|
+
.. [2] Nava, B., Coisson, P., & Radicella, S.M. (2008). "A new version
|
|
20
|
+
of the NeQuick ionosphere electron density model". Journal of
|
|
21
|
+
Atmospheric and Solar-Terrestrial Physics, 70(15), 1856-1862.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from typing import NamedTuple
|
|
25
|
+
|
|
26
|
+
import numpy as np
|
|
27
|
+
from numpy.typing import ArrayLike, NDArray
|
|
28
|
+
|
|
29
|
+
# Physical constants
|
|
30
|
+
SPEED_OF_LIGHT = 299792458.0 # m/s
|
|
31
|
+
F_L1 = 1575.42e6 # GPS L1 frequency (Hz)
|
|
32
|
+
F_L2 = 1227.60e6 # GPS L2 frequency (Hz)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class IonosphereState(NamedTuple):
|
|
36
|
+
"""
|
|
37
|
+
Ionospheric state at a given location and time.
|
|
38
|
+
|
|
39
|
+
Attributes
|
|
40
|
+
----------
|
|
41
|
+
tec : float or ndarray
|
|
42
|
+
Total Electron Content in TECU (10^16 electrons/m²).
|
|
43
|
+
delay_l1 : float or ndarray
|
|
44
|
+
Ionospheric delay at L1 frequency in meters.
|
|
45
|
+
delay_l2 : float or ndarray
|
|
46
|
+
Ionospheric delay at L2 frequency in meters.
|
|
47
|
+
f_peak : float or ndarray
|
|
48
|
+
Critical frequency of F2 layer in MHz.
|
|
49
|
+
h_peak : float or ndarray
|
|
50
|
+
Height of F2 layer peak in km.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
tec: float | NDArray[np.float64]
|
|
54
|
+
delay_l1: float | NDArray[np.float64]
|
|
55
|
+
delay_l2: float | NDArray[np.float64]
|
|
56
|
+
f_peak: float | NDArray[np.float64]
|
|
57
|
+
h_peak: float | NDArray[np.float64]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class KlobucharCoefficients(NamedTuple):
|
|
61
|
+
"""
|
|
62
|
+
Klobuchar ionospheric model coefficients.
|
|
63
|
+
|
|
64
|
+
These coefficients are broadcast by GPS satellites in the navigation message.
|
|
65
|
+
|
|
66
|
+
Attributes
|
|
67
|
+
----------
|
|
68
|
+
alpha : ndarray
|
|
69
|
+
Amplitude coefficients (4 values) in seconds.
|
|
70
|
+
beta : ndarray
|
|
71
|
+
Period coefficients (4 values) in seconds.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
alpha: NDArray[np.float64]
|
|
75
|
+
beta: NDArray[np.float64]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# Default Klobuchar coefficients (typical mid-latitude values)
|
|
79
|
+
DEFAULT_KLOBUCHAR = KlobucharCoefficients(
|
|
80
|
+
alpha=np.array([3.82e-8, 1.49e-8, -5.96e-8, -5.96e-8]),
|
|
81
|
+
beta=np.array([1.43e5, 0.0, -3.28e5, 1.13e5]),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def klobuchar_delay(
|
|
86
|
+
latitude: ArrayLike,
|
|
87
|
+
longitude: ArrayLike,
|
|
88
|
+
elevation: ArrayLike,
|
|
89
|
+
azimuth: ArrayLike,
|
|
90
|
+
gps_time: ArrayLike,
|
|
91
|
+
coefficients: KlobucharCoefficients | None = None,
|
|
92
|
+
) -> NDArray[np.float64]:
|
|
93
|
+
"""
|
|
94
|
+
Compute ionospheric delay using the Klobuchar model.
|
|
95
|
+
|
|
96
|
+
The Klobuchar model is the standard GPS broadcast ionospheric
|
|
97
|
+
correction model. It provides single-frequency ionospheric
|
|
98
|
+
delay estimates accurate to about 50% RMS.
|
|
99
|
+
|
|
100
|
+
Parameters
|
|
101
|
+
----------
|
|
102
|
+
latitude : array_like
|
|
103
|
+
User geodetic latitude in radians.
|
|
104
|
+
longitude : array_like
|
|
105
|
+
User geodetic longitude in radians.
|
|
106
|
+
elevation : array_like
|
|
107
|
+
Satellite elevation angle in radians.
|
|
108
|
+
azimuth : array_like
|
|
109
|
+
Satellite azimuth angle in radians.
|
|
110
|
+
gps_time : array_like
|
|
111
|
+
GPS time of week in seconds.
|
|
112
|
+
coefficients : KlobucharCoefficients, optional
|
|
113
|
+
Ionospheric coefficients from GPS navigation message.
|
|
114
|
+
If None, uses default mid-latitude values.
|
|
115
|
+
|
|
116
|
+
Returns
|
|
117
|
+
-------
|
|
118
|
+
delay : ndarray
|
|
119
|
+
Ionospheric delay in meters (at L1 frequency).
|
|
120
|
+
|
|
121
|
+
Examples
|
|
122
|
+
--------
|
|
123
|
+
>>> # User at 40°N, 105°W, satellite at 45° elevation
|
|
124
|
+
>>> delay = klobuchar_delay(
|
|
125
|
+
... np.radians(40), np.radians(-105),
|
|
126
|
+
... np.radians(45), np.radians(180),
|
|
127
|
+
... gps_time=43200 # Noon
|
|
128
|
+
... )
|
|
129
|
+
>>> delay > 0
|
|
130
|
+
True
|
|
131
|
+
|
|
132
|
+
Notes
|
|
133
|
+
-----
|
|
134
|
+
The Klobuchar model assumes a thin-shell ionosphere at 350 km altitude
|
|
135
|
+
and uses a cosine model for diurnal variation. It typically removes
|
|
136
|
+
about 50% of the ionospheric delay.
|
|
137
|
+
|
|
138
|
+
References
|
|
139
|
+
----------
|
|
140
|
+
.. [1] IS-GPS-200, Interface Specification.
|
|
141
|
+
"""
|
|
142
|
+
latitude = np.asarray(latitude, dtype=np.float64)
|
|
143
|
+
longitude = np.asarray(longitude, dtype=np.float64)
|
|
144
|
+
elevation = np.asarray(elevation, dtype=np.float64)
|
|
145
|
+
azimuth = np.asarray(azimuth, dtype=np.float64)
|
|
146
|
+
gps_time = np.asarray(gps_time, dtype=np.float64)
|
|
147
|
+
|
|
148
|
+
if coefficients is None:
|
|
149
|
+
coefficients = DEFAULT_KLOBUCHAR
|
|
150
|
+
|
|
151
|
+
alpha = coefficients.alpha
|
|
152
|
+
beta = coefficients.beta
|
|
153
|
+
|
|
154
|
+
# Semi-circles (GPS convention)
|
|
155
|
+
phi_u = latitude / np.pi # User latitude in semi-circles
|
|
156
|
+
lam_u = longitude / np.pi # User longitude in semi-circles
|
|
157
|
+
|
|
158
|
+
# Earth's central angle (semi-circles)
|
|
159
|
+
psi = 0.0137 / (elevation / np.pi + 0.11) - 0.022
|
|
160
|
+
|
|
161
|
+
# Ionospheric pierce point latitude (semi-circles)
|
|
162
|
+
phi_i = phi_u + psi * np.cos(azimuth)
|
|
163
|
+
phi_i = np.clip(phi_i, -0.416, 0.416)
|
|
164
|
+
|
|
165
|
+
# Ionospheric pierce point longitude (semi-circles)
|
|
166
|
+
lam_i = lam_u + psi * np.sin(azimuth) / np.cos(phi_i * np.pi)
|
|
167
|
+
|
|
168
|
+
# Geomagnetic latitude (semi-circles)
|
|
169
|
+
phi_m = phi_i + 0.064 * np.cos((lam_i - 1.617) * np.pi)
|
|
170
|
+
|
|
171
|
+
# Local time at ionospheric pierce point (seconds)
|
|
172
|
+
t = 43200 * lam_i + gps_time
|
|
173
|
+
t = np.mod(t, 86400)
|
|
174
|
+
|
|
175
|
+
# Obliquity factor
|
|
176
|
+
F = 1.0 + 16.0 * (0.53 - elevation / np.pi) ** 3
|
|
177
|
+
|
|
178
|
+
# Ionospheric delay computation
|
|
179
|
+
# Amplitude
|
|
180
|
+
AMP = alpha[0] + alpha[1] * phi_m + alpha[2] * phi_m**2 + alpha[3] * phi_m**3
|
|
181
|
+
AMP = np.maximum(AMP, 0)
|
|
182
|
+
|
|
183
|
+
# Period
|
|
184
|
+
PER = beta[0] + beta[1] * phi_m + beta[2] * phi_m**2 + beta[3] * phi_m**3
|
|
185
|
+
PER = np.maximum(PER, 72000)
|
|
186
|
+
|
|
187
|
+
# Phase
|
|
188
|
+
x = 2 * np.pi * (t - 50400) / PER
|
|
189
|
+
|
|
190
|
+
# Ionospheric time delay (seconds)
|
|
191
|
+
delay_sec = np.where(
|
|
192
|
+
np.abs(x) < 1.57,
|
|
193
|
+
F * (5e-9 + AMP * (1 - x**2 / 2 + x**4 / 24)),
|
|
194
|
+
F * 5e-9,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# Convert to meters
|
|
198
|
+
delay_m = delay_sec * SPEED_OF_LIGHT
|
|
199
|
+
|
|
200
|
+
return delay_m
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def dual_frequency_tec(
|
|
204
|
+
pseudorange_l1: ArrayLike,
|
|
205
|
+
pseudorange_l2: ArrayLike,
|
|
206
|
+
) -> NDArray[np.float64]:
|
|
207
|
+
"""
|
|
208
|
+
Compute Total Electron Content from dual-frequency pseudoranges.
|
|
209
|
+
|
|
210
|
+
This method uses the dispersive nature of the ionosphere to
|
|
211
|
+
estimate TEC from the difference in L1 and L2 pseudoranges.
|
|
212
|
+
|
|
213
|
+
Parameters
|
|
214
|
+
----------
|
|
215
|
+
pseudorange_l1 : array_like
|
|
216
|
+
L1 pseudorange in meters.
|
|
217
|
+
pseudorange_l2 : array_like
|
|
218
|
+
L2 pseudorange in meters.
|
|
219
|
+
|
|
220
|
+
Returns
|
|
221
|
+
-------
|
|
222
|
+
tec : ndarray
|
|
223
|
+
Total Electron Content in TECU (10^16 electrons/m²).
|
|
224
|
+
|
|
225
|
+
Notes
|
|
226
|
+
-----
|
|
227
|
+
The ionospheric delay is proportional to TEC and inversely
|
|
228
|
+
proportional to frequency squared:
|
|
229
|
+
delay = 40.3 * TEC / f²
|
|
230
|
+
|
|
231
|
+
The difference in delays at L1 and L2 gives:
|
|
232
|
+
P2 - P1 = 40.3 * TEC * (1/f1² - 1/f2²)
|
|
233
|
+
|
|
234
|
+
This is the standard dual-frequency ionospheric correction method.
|
|
235
|
+
"""
|
|
236
|
+
pseudorange_l1 = np.asarray(pseudorange_l1, dtype=np.float64)
|
|
237
|
+
pseudorange_l2 = np.asarray(pseudorange_l2, dtype=np.float64)
|
|
238
|
+
|
|
239
|
+
# Ionospheric coefficient
|
|
240
|
+
K = 40.3 # m³/s²
|
|
241
|
+
|
|
242
|
+
# Frequency squared terms
|
|
243
|
+
f1_sq = F_L1**2
|
|
244
|
+
f2_sq = F_L2**2
|
|
245
|
+
|
|
246
|
+
# TEC from pseudorange difference
|
|
247
|
+
# P2 - P1 = K * TEC * (1/f2² - 1/f1²) / 10^16
|
|
248
|
+
# Note: negative because f1 > f2
|
|
249
|
+
delta_inv_f_sq = 1 / f2_sq - 1 / f1_sq
|
|
250
|
+
tec = (pseudorange_l2 - pseudorange_l1) / (K * delta_inv_f_sq) / 1e16
|
|
251
|
+
|
|
252
|
+
return tec
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def ionospheric_delay_from_tec(
|
|
256
|
+
tec: ArrayLike,
|
|
257
|
+
frequency: float = F_L1,
|
|
258
|
+
) -> NDArray[np.float64]:
|
|
259
|
+
"""
|
|
260
|
+
Compute ionospheric delay from Total Electron Content.
|
|
261
|
+
|
|
262
|
+
Parameters
|
|
263
|
+
----------
|
|
264
|
+
tec : array_like
|
|
265
|
+
Total Electron Content in TECU (10^16 electrons/m²).
|
|
266
|
+
frequency : float, optional
|
|
267
|
+
Signal frequency in Hz. Default is GPS L1.
|
|
268
|
+
|
|
269
|
+
Returns
|
|
270
|
+
-------
|
|
271
|
+
delay : ndarray
|
|
272
|
+
Ionospheric delay in meters.
|
|
273
|
+
|
|
274
|
+
Notes
|
|
275
|
+
-----
|
|
276
|
+
The ionospheric delay for a signal is:
|
|
277
|
+
delay = 40.3 * TEC * 10^16 / f²
|
|
278
|
+
"""
|
|
279
|
+
tec = np.asarray(tec, dtype=np.float64)
|
|
280
|
+
|
|
281
|
+
K = 40.3 # m³/s²
|
|
282
|
+
delay = K * tec * 1e16 / frequency**2
|
|
283
|
+
|
|
284
|
+
return delay
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def simple_iri(
|
|
288
|
+
latitude: ArrayLike,
|
|
289
|
+
longitude: ArrayLike,
|
|
290
|
+
altitude: ArrayLike,
|
|
291
|
+
hour: ArrayLike,
|
|
292
|
+
month: int = 6,
|
|
293
|
+
solar_flux: float = 150.0,
|
|
294
|
+
) -> IonosphereState:
|
|
295
|
+
"""
|
|
296
|
+
Simplified International Reference Ionosphere (IRI) model.
|
|
297
|
+
|
|
298
|
+
This provides approximate electron density and TEC values based on
|
|
299
|
+
simplified IRI physics. For accurate predictions, use the full IRI
|
|
300
|
+
model or external services.
|
|
301
|
+
|
|
302
|
+
Parameters
|
|
303
|
+
----------
|
|
304
|
+
latitude : array_like
|
|
305
|
+
Geodetic latitude in radians.
|
|
306
|
+
longitude : array_like
|
|
307
|
+
Geodetic longitude in radians.
|
|
308
|
+
altitude : array_like
|
|
309
|
+
Altitude in meters.
|
|
310
|
+
hour : array_like
|
|
311
|
+
Local hour (0-24).
|
|
312
|
+
month : int, optional
|
|
313
|
+
Month of year (1-12). Default is 6 (June).
|
|
314
|
+
solar_flux : float, optional
|
|
315
|
+
F10.7 solar flux in SFU. Default is 150 (moderate activity).
|
|
316
|
+
|
|
317
|
+
Returns
|
|
318
|
+
-------
|
|
319
|
+
state : IonosphereState
|
|
320
|
+
Ionospheric state with TEC, delays, and F2 layer parameters.
|
|
321
|
+
|
|
322
|
+
Notes
|
|
323
|
+
-----
|
|
324
|
+
This is a simplified empirical model suitable for educational purposes
|
|
325
|
+
and rough estimates. For operational use, the full IRI-2020 model
|
|
326
|
+
should be employed.
|
|
327
|
+
|
|
328
|
+
Examples
|
|
329
|
+
--------
|
|
330
|
+
>>> state = simple_iri(np.radians(40), np.radians(-105), 300e3, 12)
|
|
331
|
+
>>> state.tec > 0
|
|
332
|
+
True
|
|
333
|
+
"""
|
|
334
|
+
latitude = np.asarray(latitude, dtype=np.float64)
|
|
335
|
+
longitude = np.asarray(longitude, dtype=np.float64)
|
|
336
|
+
altitude = np.asarray(altitude, dtype=np.float64)
|
|
337
|
+
hour = np.asarray(hour, dtype=np.float64)
|
|
338
|
+
|
|
339
|
+
# Convert latitude to degrees for calculations
|
|
340
|
+
lat_deg = np.degrees(latitude)
|
|
341
|
+
# lon_deg not used in simplified model but kept for future expansion
|
|
342
|
+
|
|
343
|
+
# Simplified F2 layer critical frequency (foF2) model
|
|
344
|
+
# Based on typical diurnal and latitudinal variations
|
|
345
|
+
lat_factor = np.cos(latitude) ** 0.8
|
|
346
|
+
hour_angle = 2 * np.pi * (hour - 14) / 24 # Peak around 14:00 local
|
|
347
|
+
diurnal = 0.5 * (1 + np.cos(hour_angle))
|
|
348
|
+
|
|
349
|
+
# Solar activity factor
|
|
350
|
+
solar_factor = 0.5 + 0.5 * (solar_flux - 70) / 180
|
|
351
|
+
solar_factor = np.clip(solar_factor, 0.3, 1.2)
|
|
352
|
+
|
|
353
|
+
# Seasonal factor (simplified)
|
|
354
|
+
season_angle = 2 * np.pi * (month - 1) / 12
|
|
355
|
+
season_factor = 1.0 + 0.2 * np.cos(season_angle - np.pi * np.sign(lat_deg))
|
|
356
|
+
|
|
357
|
+
# F2 layer critical frequency (MHz)
|
|
358
|
+
f_peak = 5.0 + 8.0 * lat_factor * diurnal * solar_factor * season_factor
|
|
359
|
+
f_peak = np.maximum(f_peak, 2.0)
|
|
360
|
+
|
|
361
|
+
# F2 layer peak height (km)
|
|
362
|
+
h_peak = 250 + 100 * (1 - lat_factor) + 50 * (1 - diurnal)
|
|
363
|
+
|
|
364
|
+
# Simplified TEC calculation (TECU)
|
|
365
|
+
# TEC roughly scales with foF2 squared
|
|
366
|
+
base_tec = 0.5 * f_peak**2
|
|
367
|
+
tec = base_tec * solar_factor * season_factor
|
|
368
|
+
|
|
369
|
+
# Ionospheric delays
|
|
370
|
+
delay_l1 = ionospheric_delay_from_tec(tec, F_L1)
|
|
371
|
+
delay_l2 = ionospheric_delay_from_tec(tec, F_L2)
|
|
372
|
+
|
|
373
|
+
# Handle scalar vs array output
|
|
374
|
+
if np.ndim(latitude) == 0:
|
|
375
|
+
return IonosphereState(
|
|
376
|
+
tec=float(tec),
|
|
377
|
+
delay_l1=float(delay_l1),
|
|
378
|
+
delay_l2=float(delay_l2),
|
|
379
|
+
f_peak=float(f_peak),
|
|
380
|
+
h_peak=float(h_peak),
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
return IonosphereState(
|
|
384
|
+
tec=tec,
|
|
385
|
+
delay_l1=delay_l1,
|
|
386
|
+
delay_l2=delay_l2,
|
|
387
|
+
f_peak=f_peak,
|
|
388
|
+
h_peak=h_peak,
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def magnetic_latitude(
|
|
393
|
+
latitude: ArrayLike,
|
|
394
|
+
longitude: ArrayLike,
|
|
395
|
+
) -> NDArray[np.float64]:
|
|
396
|
+
"""
|
|
397
|
+
Compute approximate geomagnetic latitude.
|
|
398
|
+
|
|
399
|
+
Uses a simple dipole approximation with the magnetic pole at
|
|
400
|
+
approximately 80.5°N, 72.8°W.
|
|
401
|
+
|
|
402
|
+
Parameters
|
|
403
|
+
----------
|
|
404
|
+
latitude : array_like
|
|
405
|
+
Geodetic latitude in radians.
|
|
406
|
+
longitude : array_like
|
|
407
|
+
Geodetic longitude in radians.
|
|
408
|
+
|
|
409
|
+
Returns
|
|
410
|
+
-------
|
|
411
|
+
mag_lat : ndarray
|
|
412
|
+
Geomagnetic latitude in radians.
|
|
413
|
+
"""
|
|
414
|
+
latitude = np.asarray(latitude, dtype=np.float64)
|
|
415
|
+
longitude = np.asarray(longitude, dtype=np.float64)
|
|
416
|
+
|
|
417
|
+
# Approximate magnetic pole location (2020 epoch)
|
|
418
|
+
pole_lat = np.radians(80.5)
|
|
419
|
+
pole_lon = np.radians(-72.8)
|
|
420
|
+
|
|
421
|
+
# Spherical law of cosines for angular distance
|
|
422
|
+
cos_mag_lat = np.sin(latitude) * np.sin(pole_lat) + np.cos(latitude) * np.cos(
|
|
423
|
+
pole_lat
|
|
424
|
+
) * np.cos(longitude - pole_lon)
|
|
425
|
+
|
|
426
|
+
# Geomagnetic colatitude
|
|
427
|
+
mag_colat = np.arccos(np.clip(cos_mag_lat, -1, 1))
|
|
428
|
+
|
|
429
|
+
# Geomagnetic latitude
|
|
430
|
+
mag_lat = np.pi / 2 - mag_colat
|
|
431
|
+
|
|
432
|
+
return mag_lat
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def scintillation_index(
|
|
436
|
+
magnetic_latitude: ArrayLike,
|
|
437
|
+
hour: ArrayLike,
|
|
438
|
+
kp_index: float = 3.0,
|
|
439
|
+
) -> NDArray[np.float64]:
|
|
440
|
+
"""
|
|
441
|
+
Estimate ionospheric scintillation index S4.
|
|
442
|
+
|
|
443
|
+
Provides a rough estimate of amplitude scintillation based on
|
|
444
|
+
geomagnetic latitude, local time, and geomagnetic activity.
|
|
445
|
+
|
|
446
|
+
Parameters
|
|
447
|
+
----------
|
|
448
|
+
magnetic_latitude : array_like
|
|
449
|
+
Geomagnetic latitude in radians.
|
|
450
|
+
hour : array_like
|
|
451
|
+
Local hour (0-24).
|
|
452
|
+
kp_index : float, optional
|
|
453
|
+
Kp geomagnetic activity index (0-9). Default is 3 (moderate).
|
|
454
|
+
|
|
455
|
+
Returns
|
|
456
|
+
-------
|
|
457
|
+
s4 : ndarray
|
|
458
|
+
S4 amplitude scintillation index (0-1).
|
|
459
|
+
|
|
460
|
+
Notes
|
|
461
|
+
-----
|
|
462
|
+
S4 > 0.3 indicates moderate scintillation.
|
|
463
|
+
S4 > 0.6 indicates strong scintillation that may affect receivers.
|
|
464
|
+
"""
|
|
465
|
+
magnetic_latitude = np.asarray(magnetic_latitude, dtype=np.float64)
|
|
466
|
+
hour = np.asarray(hour, dtype=np.float64)
|
|
467
|
+
|
|
468
|
+
# Scintillation is most intense:
|
|
469
|
+
# - At equatorial latitudes (within ±20° of magnetic equator)
|
|
470
|
+
# - At high latitudes (auroral zone, |lat| > 60°)
|
|
471
|
+
# - Post-sunset to midnight (local time 19-24)
|
|
472
|
+
# - During high geomagnetic activity
|
|
473
|
+
|
|
474
|
+
mag_lat_deg = np.abs(np.degrees(magnetic_latitude))
|
|
475
|
+
|
|
476
|
+
# Equatorial contribution
|
|
477
|
+
equatorial = np.exp(-((mag_lat_deg - 15) ** 2) / 200)
|
|
478
|
+
|
|
479
|
+
# Auroral contribution
|
|
480
|
+
auroral = np.exp(-((mag_lat_deg - 70) ** 2) / 100)
|
|
481
|
+
|
|
482
|
+
# Combined latitude factor
|
|
483
|
+
lat_factor = np.maximum(equatorial, 0.3 * auroral)
|
|
484
|
+
|
|
485
|
+
# Local time factor (peak at ~20:00 local)
|
|
486
|
+
hour_angle = 2 * np.pi * (hour - 20) / 24
|
|
487
|
+
time_factor = 0.5 * (1 + np.cos(hour_angle))
|
|
488
|
+
|
|
489
|
+
# Geomagnetic activity factor
|
|
490
|
+
kp_factor = 0.3 + 0.7 * (kp_index / 9)
|
|
491
|
+
|
|
492
|
+
# S4 estimate
|
|
493
|
+
s4 = 0.8 * lat_factor * time_factor * kp_factor
|
|
494
|
+
|
|
495
|
+
return np.clip(s4, 0, 1)
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
__all__ = [
|
|
499
|
+
"IonosphereState",
|
|
500
|
+
"KlobucharCoefficients",
|
|
501
|
+
"DEFAULT_KLOBUCHAR",
|
|
502
|
+
"klobuchar_delay",
|
|
503
|
+
"dual_frequency_tec",
|
|
504
|
+
"ionospheric_delay_from_tec",
|
|
505
|
+
"simple_iri",
|
|
506
|
+
"magnetic_latitude",
|
|
507
|
+
"scintillation_index",
|
|
508
|
+
# Constants
|
|
509
|
+
"SPEED_OF_LIGHT",
|
|
510
|
+
"F_L1",
|
|
511
|
+
"F_L2",
|
|
512
|
+
]
|