{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# GNSS Radio Occultation Observations\n",
"\n",
"Global Navigation Satellite System (GNSS) Radio Occultation (RO) samples the environment by observing changes to GNSS signals as the pass through the Earth's atmosphere. This TAT-C analysis function models when and where GNSS-RO observations can be sampled at the tangent point between a receiver and transmitter, i.e., point along the line-of-sight with minimum distance to the geocenter. This analysis *does not* consider atmospheric refraction, therefore represents only a coarse estimate of GNSS-RO observations suitable for early-stage mission analysis."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Mathematical Basis\n",
"\n",
"### Transmitter/Receiver Geometry\n",
"\n",
"Assume the transmitter (tx) and receiver (rx) have positions $x_{tx}$ and $x_{rx}$ and velocities $v_{tx}$ and $v_{rx}$, respectively, in a geocentric inertial frame. The transmitter has position $x_{rx,tx}=x_{tx}-x_{rx}$ and velocity $v_{rx,tx}=v_{tx}-v_{rx}$ relative to the receiver.\n",
"\n",
"
\n",
"\n",
"The tangent point has position (via vector geometry):\n",
"\n",
"$x_{tp} = x_{tx} - \\left(x_{tx} \\cdot \\frac{x_{rx,tx}}{||x_{rx,tx}||}\\right) x_{rx,tx}$ \n",
"\n",
"and velocity (via the chain rule):\n",
"\n",
"$v_{tp} = \\frac{d x_{tp}}{dt} = v_{tx} - \\left(x_{tx} \\cdot \\frac{x_{rx,tx}}{||x_{rx,tx}||}\\right) v_{rx,tx} - \\left[ \\frac{v_{tx} \\cdot x_{rx,tx} + x_{tx} + v_{rx,tx}}{||x_{rx,tx}||} - 2\\frac{\\left(x_{tx}\\cdot x_{rx,tx}\\right)\\left(v_{rx,tx}\\cdot x_{rx,tx}\\right)}{||x_{rx,tx}||^2} \\right] x_{rx,tx}$"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The pitch angle of the transmitter as viewed by the receiver (above/below local horizon) is given by\n",
"\n",
"$\\theta_{rx,tx} = \\text{atan2} \\left( \\tilde{x}_{tx,rx}^n \\cdot \\frac{x_{rx}}{||x_{rx}||}, \\tilde{x}_{tx,rx}^n \\cdot \\frac{v_{rx}}{||v_{rx}||} \\right)$\n",
"\n",
"where\n",
"\n",
"$\\tilde{x}_{tx,rx}^n = x_{tx,rx} - (n_{rx} \\cdot x_{tx,rx}) n_{rx}$ \n",
"\n",
"is the relative transmitter position in the plane normal to the receiver orbit, and \n",
"\n",
"$n_{rx} = \\frac{ x_{rx} \\times v_{rx}}{||x_{rx} \\times v_{rx}||}$ \n",
"\n",
"is the unit normal to the receiver orbit plane.\n",
"\n",
"
"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The yaw angle of the transmitter as viewed by the receiver (left/right along local horizontal) is given by\n",
"\n",
"$\\Omega_{rx,tx} = \\text{atan2} \\left( \\tilde{x}_{tx,rx}^t \\cdot n_{rx}, \\tilde{x}_{tx,rx}^t \\cdot \\frac{v_{rx}}{||v_{rx}||} \\right)$\n",
"\n",
"where\n",
"\n",
"$\\tilde{x}_{tx,rx}^t = x_{tx,rx} - (\\frac{ x_{rx} }{||x_{rx} ||} \\cdot x_{tx,rx}) \\frac{ x_{rx} }{||x_{rx} ||}$ \n",
"\n",
"is the relative transmitter position in the plane tangent to the receiver orbit.\n",
"\n",
"
"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### RO Observation Criteria\n",
"\n",
"There are three criteria for a valid RO observation."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"First, the tangent point must be between the transmitter and receiver:\n",
"\n",
"$\\text{sgn} \\left( (x_{tp} - x_{tx}) \\cdot (x_{tp} - x_{rx}) \\right) < 0$\n",
"\n",
"
"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Second, the absolute value yaw angle of the transmitter as viewed by the receiver must be within a maximum bound due to antenna gain patterns:\n",
"\n",
"$\\text{mod}\\left( | \\Omega_{rx,tx}|, 180 - \\Omega_{max} \\right) < \\Omega_{max}$"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Third, the height of the tangent point must be within a specified range:\n",
"\n",
"$h_{min} < h < h_{max}$\n",
"\n",
"where $h$ is elevation of the tangent point $x_{tp}$ above a specified geoid such as WGS 84."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Analysis"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"First, we define scenario configurations. Three parameters set valid GNSS-RO observation conditions between a receiver and transmitter:\n",
" * `max_yaw` sets the maximum yaw angle (in degrees) between the receiver and transmitter within the receiver's orbit plane.\n",
" * `range_elevation` sets the minimum and maximum allowable elevation (in meters) of the tangent point between receiver and transmitter. While realistic GNSS-RO observations sample the atmosphere, a negative minimum value accounts for the lack of refraction considered in this analysis.\n",
" * `sample_elevation` sets the elevation (in meters) of the tangent point to represent a GNSS-RO observation as a single point, rather than an arc through space.\n",
"\n",
"Other scenario parameters configure the temporal bounds of analysis (`start` and `duration`) and set the temporal resolution of orbital motion (`time_step`)."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"from datetime import datetime, timedelta, timezone\n",
"import numpy as np\n",
"\n",
"# occultation validity constraints\n",
"max_yaw = 65 # deg\n",
"range_elevation = (-200e3, 60e3) # m\n",
"sample_elevation = 0 # m\n",
"\n",
"# scenario configuration\n",
"start = datetime(2023, 12, 9, tzinfo=timezone.utc)\n",
"time_step = timedelta(seconds=10)\n",
"duration = timedelta(hours=1)\n",
"times = np.array([start + i * time_step for i in range(duration // time_step)])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Each analysis relies on the definition of a GNSS receiver (e.g., COSMIC-2 FMS) and one or more GNSS transmitters (e.g., the GPS, GLONASS, Galileo, and BeiDou constellations). The code below imports two line elements (TLEs) for each satellite orbit (as of late 2023) and builds a TAT-C `Satellite` object."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"from tatc.schemas import Satellite, TwoLineElements\n",
"\n",
"txs = []\n",
"\n",
"for gnss_dat in [\"gps_tle.dat\", \"glo_tle.dat\", \"gal_tle.dat\", \"bei_tle.dat\"]:\n",
" with open(gnss_dat, \"r\") as f:\n",
" lines = f.read().splitlines()\n",
" for i in range(0, len(lines), 3):\n",
" txs.append(\n",
" Satellite(\n",
" name=lines[i], orbit=TwoLineElements(tle=lines[i + 1 : i + 3])\n",
" )\n",
" )\n",
"\n",
"rx = Satellite(\n",
" name=\"COSMIC-2 FM5\",\n",
" orbit=TwoLineElements(\n",
" tle=[\n",
" \"1 44358U 19036V 23354.40324524 .00015027 00000-0 91237-3 0 9994\",\n",
" \"2 44358 24.0023 0.0734 0003537 251.0816 108.9304 15.08623759245240\",\n",
" ]\n",
" ),\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"TAT-C simulates GNSS-RO observations using the `collect_ro_observations` script. The function propagates the position of the receiver and transmitters and computes the three conditions required for a valid GNSS-RO observation: 1) the tangent point must between the receiver and transmitter, 2) the acute receiver-transmitter azimuth angle must be less than `max_azimuth`, and 3) the tangent point elevation must be within the `range_elevation` range.\n",
"\n",
"The outputs report each RO observation including the following fields:\n",
" * `receiver`: the receiver satellite name\n",
" * `transmitter`: the transmitter satellite name\n",
" * `is_rising`: true, if the receiver-transmitter azimuth angle is less than `max_azimuth` (false indicates it is greater than 180 - `max_azimuth`)\n",
" * `geometry`: a multipoint geometry that describes the observation arc of points separated by `time_step`\n",
" * `position`: a point geometry that describes the observation closest to the `sample_elevation` value\n",
" * `rx_tx_azimuth`: the transmitter azimuth angle as viewed by the receiver\n",
" * `tp_tx_azimuth`: the transmitter azimuth angle as viewed by the tangent point (i.e., degrees clockwise from North)\n",
" * `start`: start of the RO observation arc\n",
" * `end`: end of the RO observation arc\n",
" * `time`: time of the observation closest to the `sample_elevation` value\n"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
| \n", " | receiver | \n", "transmitter | \n", "is_rising | \n", "geometry | \n", "position | \n", "rx_tx_pitch | \n", "rx_tx_yaw | \n", "tp_tx_azimuth | \n", "start | \n", "end | \n", "time | \n", "
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | \n", "COSMIC-2 FM5 | \n", "BEIDOU-3 M7 (C27) | \n", "False | \n", "MULTIPOINT Z (-0.03172 8.43027 -111422.03599, ... | \n", "POINT Z (-0.0317183312985065 8.430274910021483... | \n", "-154.829254 | \n", "174.181601 | \n", "298.666783 | \n", "2023-12-09 00:00:00+00:00 | \n", "2023-12-09 00:00:30+00:00 | \n", "2023-12-09 00:00:00+00:00 | \n", "
| 1 | \n", "COSMIC-2 FM5 | \n", "COSMOS 2534 (758) | \n", "False | \n", "MULTIPOINT Z (8.38669 15.17354 -20482.00391, 8... | \n", "POINT Z (8.386687522755755 15.173539678375606 ... | \n", "-153.251127 | \n", "148.414002 | \n", "323.957256 | \n", "2023-12-09 00:00:00+00:00 | \n", "2023-12-09 00:01:20+00:00 | \n", "2023-12-09 00:00:00+00:00 | \n", "
| 2 | \n", "COSMIC-2 FM5 | \n", "BEIDOU-3 M12 (C26) | \n", "True | \n", "MULTIPOINT Z (43.67416 -10.25178 12330.4085, 4... | \n", "POINT Z (43.67415791885335 -10.251779478855163... | \n", "-22.664009 | \n", "6.514396 | \n", "104.465665 | \n", "2023-12-09 00:00:00+00:00 | \n", "2023-12-09 00:00:20+00:00 | \n", "2023-12-09 00:00:00+00:00 | \n", "
| 3 | \n", "COSMIC-2 FM5 | \n", "COSMOS 2557 (706K) | \n", "False | \n", "MULTIPOINT Z (5.13858 7.72295 58877.01276, 5.2... | \n", "POINT Z (5.509687281172558 7.9175282357802645 ... | \n", "-156.574267 | \n", "169.009785 | \n", "304.029604 | \n", "2023-12-09 00:00:20+00:00 | \n", "2023-12-09 00:02:00+00:00 | \n", "2023-12-09 00:00:50+00:00 | \n", "
| 4 | \n", "COSMIC-2 FM5 | \n", "GSAT0215 (PRN E21) | \n", "True | \n", "MULTIPOINT Z (43.4354 -21.61952 -197994.75109,... | \n", "POINT Z (44.94849495030172 -20.81904846996197 ... | \n", "-23.893350 | \n", "-17.767677 | \n", "126.837457 | \n", "2023-12-09 00:00:10+00:00 | \n", "2023-12-09 00:01:50+00:00 | \n", "2023-12-09 00:01:30+00:00 | \n", "
| ... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "
| 133 | \n", "COSMIC-2 FM5 | \n", "BEIDOU-3S M1S (C58) | \n", "False | \n", "MULTIPOINT Z (-148.06901 -0.43361 47281.53233,... | \n", "POINT Z (-146.992685046998 -0.8190136846429314... | \n", "-151.471053 | \n", "-140.421688 | \n", "212.568932 | \n", "2023-12-09 00:57:50+00:00 | \n", "2023-12-09 00:59:50+00:00 | \n", "2023-12-09 00:58:20+00:00 | \n", "
| 134 | \n", "COSMIC-2 FM5 | \n", "BEIDOU-3 M12 (C26) | \n", "False | \n", "MULTIPOINT Z (-157.82214 20.0755 44731.25716, ... | \n", "POINT Z (-157.66151831979104 20.63188433047461... | \n", "-155.288807 | \n", "154.959046 | \n", "271.270420 | \n", "2023-12-09 00:58:10+00:00 | \n", "2023-12-09 00:59:50+00:00 | \n", "2023-12-09 00:58:30+00:00 | \n", "
| 135 | \n", "COSMIC-2 FM5 | \n", "COSMOS 2500 (755) | \n", "True | \n", "MULTIPOINT Z (-126.69412 44.90944 -194723.7506... | \n", "POINT Z (-121.58919915764217 44.59651929331758... | \n", "-47.093233 | \n", "63.730350 | \n", "16.239802 | \n", "2023-12-09 00:58:40+00:00 | \n", "2023-12-09 00:59:50+00:00 | \n", "2023-12-09 00:59:50+00:00 | \n", "
| 136 | \n", "COSMIC-2 FM5 | \n", "GSAT0224 (PRN E10) | \n", "True | \n", "MULTIPOINT Z (-123.55537 45.42334 -193679.7174... | \n", "POINT Z (-121.36527296079892 45.17437814841084... | \n", "-47.875649 | \n", "63.751499 | \n", "16.226191 | \n", "2023-12-09 00:59:20+00:00 | \n", "2023-12-09 00:59:50+00:00 | \n", "2023-12-09 00:59:50+00:00 | \n", "
| 137 | \n", "COSMIC-2 FM5 | \n", "GPS BIIF-8 (PRN 03) | \n", "True | \n", "MULTIPOINT Z (-102.06523 18.52248 -194739.9090... | \n", "POINT Z (-101.93398093154444 18.83019958019392... | \n", "-25.946455 | \n", "-11.634382 | \n", "96.639069 | \n", "2023-12-09 00:59:30+00:00 | \n", "2023-12-09 00:59:50+00:00 | \n", "2023-12-09 00:59:50+00:00 | \n", "
138 rows × 11 columns
\n", "