From 48bc8e8239cd9aa0f937a2cc8c7cbcfd52faf3e2 Mon Sep 17 00:00:00 2001 From: Carles Fernandez Date: Mon, 10 Aug 2026 09:49:52 +0200 Subject: [PATCH] skyplot: improve RINEX 2/3/4 navigation record parsing --- utils/skyplot/skyplot.py | 467 +++++++++++++++++++++++++++++---------- 1 file changed, 348 insertions(+), 119 deletions(-) diff --git a/utils/skyplot/skyplot.py b/utils/skyplot/skyplot.py index e8f9a554e..cd4487c8b 100755 --- a/utils/skyplot/skyplot.py +++ b/utils/skyplot/skyplot.py @@ -41,7 +41,7 @@ except ImportError: print("Install them with: pip install matplotlib numpy") sys.exit(1) -__version__ = "1.0.0" +__version__ = "1.1.0" # Default position: Castelldefels, Barcelona DEFAULT_LAT = 41.275 @@ -409,6 +409,17 @@ def parse_nav_field(line: str, field: int, first_line: bool = False) -> float: return parse_rinex_float(line[start : start + 19]) +def parse_nav_field_v2(line: str, field: int, first_line: bool = False) -> float: + """Parse a 19-character numeric field from a RINEX 2 NAV record. + + RINEX 2.11 packs D19.12 fields one column earlier than RINEX 3/4: + after column 22 on the PRN/EPOCH/CLK line and after 3X on + continuation lines (Tables A4/A11/A16). + """ + start = (22 if first_line else 3) + 19 * field + return parse_rinex_float(line[start : start + 19]) + + def seconds_of_week(epoch: datetime) -> float: """Return seconds elapsed since Sunday 00:00 in the epoch's time scale.""" days_since_sunday = (epoch.weekday() + 1) % 7 @@ -448,14 +459,26 @@ def parse_state_vector_ephemeris( } if prn.startswith("R"): - ephemeris.update( - { - "sv_relative_freq_bias": parse_nav_field(lines[0], 1, True), - "message_frame_time": parse_nav_field(lines[0], 2, True), - "freq_num": parse_nav_field(lines[2], 3), - "age": parse_nav_field(lines[3], 3), - } - ) + ephemeris["sv_relative_freq_bias"] = parse_nav_field(lines[0], 1, True) + if message_type in ("L1OC", "L3OC"): + # GLONASS CDMA (RINEX 4.02): clock line carries the drift rate, + # orbit lines 2/3 carry data validity and TGD/ISC instead of the + # FDMA frequency number and age fields. + ephemeris.update( + { + "sv_clock_drift_rate": parse_nav_field(lines[0], 2, True), + "data_validity": parse_nav_field(lines[2], 3), + "isc": parse_nav_field(lines[3], 3), + } + ) + else: # FDMA (RINEX 4) and unframed RINEX 3 records + ephemeris.update( + { + "message_frame_time": parse_nav_field(lines[0], 2, True), + "freq_num": parse_nav_field(lines[2], 3), + "age": parse_nav_field(lines[3], 3), + } + ) else: ephemeris.update( { @@ -518,9 +541,9 @@ def parse_cnav_ephemeris( message_type: str, message_subtype: Optional[str] = None, ) -> Dict[str, Any]: - """Parse the RINEX 4 GPS/QZSS CNAV nine-line ephemeris layout.""" + """Parse the RINEX 4 GPS/QZSS CNAV (nine-line) or CNV2 (ten-line) layout.""" epoch = parse_nav_epoch(lines[0]) - return { + ephemeris = { "prn": prn, "epoch": epoch, "message_type": message_type, @@ -557,9 +580,150 @@ def parse_cnav_ephemeris( "isc_l2c": parse_nav_field(lines[7], 1), "isc_l5i5": parse_nav_field(lines[7], 2), "isc_l5q5": parse_nav_field(lines[7], 3), + } + + # CNV2 (CNAV-2, L1C) inserts the L1C inter-signal corrections between + # the legacy ISC line and the transmission-time line (Table A11). + tail = 8 + if message_type == "CNV2": + ephemeris["isc_l1cd"] = parse_nav_field(lines[8], 0) + ephemeris["isc_l1cp"] = parse_nav_field(lines[8], 1) + tail = 9 + + ephemeris["transmission_time"] = parse_nav_field(lines[tail], 0) + ephemeris["wn_op"] = parse_nav_field(lines[tail], 1) + ephemeris["flags"] = parse_nav_field(lines[tail], 2) + ephemeris["extra"] = lines[tail + 1 :] + return ephemeris + + +def parse_bds_cnav_ephemeris( + prn: str, + lines: List[str], + message_type: str, + message_subtype: Optional[str] = None, +) -> Dict[str, Any]: + """Parse the RINEX 4 BeiDou CNV1/CNV2 (ten-line) or CNV3 (nine-line) layout. + + Unlike GPS/QZSS CNAV, BeiDou CNAV records broadcast ToE directly on + broadcast-orbit line 3 (Tables A24-A26). + """ + ephemeris = { + "prn": prn, + "epoch": parse_nav_epoch(lines[0]), + "message_type": message_type, + "message_subtype": message_subtype, + "sv_clock_bias": parse_nav_field(lines[0], 0, True), + "sv_clock_drift": parse_nav_field(lines[0], 1, True), + "sv_clock_drift_rate": parse_nav_field(lines[0], 2, True), + "adot": parse_nav_field(lines[1], 0), + "crs": parse_nav_field(lines[1], 1), + "delta_n": parse_nav_field(lines[1], 2), + "m0": parse_nav_field(lines[1], 3), + "cuc": parse_nav_field(lines[2], 0), + "ecc": parse_nav_field(lines[2], 1), + "cus": parse_nav_field(lines[2], 2), + "sqrt_a": parse_nav_field(lines[2], 3), + "toe": parse_nav_field(lines[3], 0), + "cic": parse_nav_field(lines[3], 1), + "omega0": parse_nav_field(lines[3], 2), + "cis": parse_nav_field(lines[3], 3), + "i0": parse_nav_field(lines[4], 0), + "crc": parse_nav_field(lines[4], 1), + "omega": parse_nav_field(lines[4], 2), + "omega_dot": parse_nav_field(lines[4], 3), + "idot": parse_nav_field(lines[5], 0), + "delta_ndot": parse_nav_field(lines[5], 1), + "sat_type": parse_nav_field(lines[5], 2), + "top": parse_nav_field(lines[5], 3), + "sisai_oe": parse_nav_field(lines[6], 0), + "sisai_ocb": parse_nav_field(lines[6], 1), + "sisai_oc1": parse_nav_field(lines[6], 2), + "sisai_oc2": parse_nav_field(lines[6], 3), + } + + if message_type == "CNV3": + ephemeris.update( + { + "sismai": parse_nav_field(lines[7], 0), + "sv_health": parse_nav_field(lines[7], 1), + "integrity_flags": parse_nav_field(lines[7], 2), + "tgd_b2bi": parse_nav_field(lines[7], 3), + "transmission_time": parse_nav_field(lines[8], 0), + "extra": lines[9:], + } + ) + else: # CNV1 / CNV2 + if message_type == "CNV1": + ephemeris["isc_b1cd"] = parse_nav_field(lines[7], 0) + else: + ephemeris["isc_b2ad"] = parse_nav_field(lines[7], 1) + ephemeris.update( + { + "tgd_b1cp": parse_nav_field(lines[7], 2), + "tgd_b2ap": parse_nav_field(lines[7], 3), + "sismai": parse_nav_field(lines[8], 0), + "sv_health": parse_nav_field(lines[8], 1), + "integrity_flags": parse_nav_field(lines[8], 2), + "iodc": parse_nav_field(lines[8], 3), + "transmission_time": parse_nav_field(lines[9], 0), + "iode": parse_nav_field(lines[9], 3), + "extra": lines[10:], + } + ) + + return ephemeris + + +def parse_navic_l1nv_ephemeris( + prn: str, + lines: List[str], + message_type: str, + message_subtype: Optional[str] = None, +) -> Dict[str, Any]: + """Parse the RINEX 4 NavIC L1NV nine-line ephemeris layout (Table A31). + + L1NV carries IODEC instead of ToE on broadcast-orbit line 3; like + GPS/QZSS CNAV, the single reference epoch means toe = toc. + """ + epoch = parse_nav_epoch(lines[0]) + return { + "prn": prn, + "epoch": epoch, + "message_type": message_type, + "message_subtype": message_subtype, + "sv_clock_bias": parse_nav_field(lines[0], 0, True), + "sv_clock_drift": parse_nav_field(lines[0], 1, True), + "sv_clock_drift_rate": parse_nav_field(lines[0], 2, True), + "adot": parse_nav_field(lines[1], 0), + "crs": parse_nav_field(lines[1], 1), + "delta_n": parse_nav_field(lines[1], 2), + "m0": parse_nav_field(lines[1], 3), + "cuc": parse_nav_field(lines[2], 0), + "ecc": parse_nav_field(lines[2], 1), + "cus": parse_nav_field(lines[2], 2), + "sqrt_a": parse_nav_field(lines[2], 3), + "iodec": parse_nav_field(lines[3], 0), + "toe": seconds_of_week(epoch), + "cic": parse_nav_field(lines[3], 1), + "omega0": parse_nav_field(lines[3], 2), + "cis": parse_nav_field(lines[3], 3), + "i0": parse_nav_field(lines[4], 0), + "crc": parse_nav_field(lines[4], 1), + "omega": parse_nav_field(lines[4], 2), + "omega_dot": parse_nav_field(lines[4], 3), + "idot": parse_nav_field(lines[5], 0), + "delta_ndot": parse_nav_field(lines[5], 1), + "rsf": parse_nav_field(lines[5], 3), + "urai": parse_nav_field(lines[6], 0), + "sv_health": parse_nav_field(lines[6], 1), + "tgd_l1p_l5": parse_nav_field(lines[6], 2), + "tgd_s_l5": parse_nav_field(lines[6], 3), + "isc_s_l1p": parse_nav_field(lines[7], 0), + "isc_l1d_l1p": parse_nav_field(lines[7], 1), + "isc_l1p_s": parse_nav_field(lines[7], 2), + "isc_l1d_s": parse_nav_field(lines[7], 3), "transmission_time": parse_nav_field(lines[8], 0), - "wn_op": parse_nav_field(lines[8], 1), - "flags": parse_nav_field(lines[8], 2), "extra": lines[9:], } @@ -578,13 +742,35 @@ def parse_rinex4_ephemeris( system = prn[0] if prn else "" try: - if system in ("G", "J") and message_type == "CNAV": - if len(lines) < 9: - raise ValueError("CNAV record has fewer than nine data lines") + if system in ("G", "J") and message_type in ("CNAV", "CNV2"): + required = 10 if message_type == "CNV2" else 9 + if len(lines) < required: + raise ValueError( + f"{message_type} record has fewer than " + f"{required} data lines" + ) return parse_cnav_ephemeris( prn, lines, message_type, message_subtype ) + if system == "C" and message_type in ("CNV1", "CNV2", "CNV3"): + required = 9 if message_type == "CNV3" else 10 + if len(lines) < required: + raise ValueError( + f"{message_type} record has fewer than " + f"{required} data lines" + ) + return parse_bds_cnav_ephemeris( + prn, lines, message_type, message_subtype + ) + + if system == "I" and message_type == "L1NV": + if len(lines) < 9: + raise ValueError("L1NV record has fewer than nine data lines") + return parse_navic_l1nv_ephemeris( + prn, lines, message_type, message_subtype + ) + legacy_message_types = { "G": ("LNAV",), "J": ("LNAV",), @@ -601,8 +787,11 @@ def parse_rinex4_ephemeris( prn, lines, message_type, message_subtype ) - state_vector_message_types = {"R": "FDMA", "S": "SBAS"} - if message_type == state_vector_message_types.get(system): + state_vector_message_types = { + "R": ("FDMA", "L1OC", "L3OC"), + "S": ("SBAS",), + } + if message_type in state_vector_message_types.get(system, ()): if len(lines) < 4: raise ValueError( "state-vector record has fewer than four data lines" @@ -706,7 +895,7 @@ def read_rinex_nav(filename: str) -> Dict[str, List[Dict[str, Any]]]: dd = int(current_line[9:11]) hh = int(current_line[12:14]) mi = int(current_line[15:17]) - ss = float(current_line[18:23]) + ss = float(current_line[17:22]) # F5.1, columns 18-22 # Year mapping: 80–99 => 1980–1999, 00–79 => 2000–2079 yyyy = 1900 + yy if yy >= 80 else 2000 + yy @@ -750,49 +939,49 @@ def read_rinex_nav(filename: str) -> Dict[str, List[Dict[str, Any]]]: continue if v2_system == "N": - # GPS + # GPS (Table A4) ephemeris = { "prn": prn, "epoch": epoch, - "sv_clock_bias": parse_rinex_float( - lines[0][23:41] + "sv_clock_bias": parse_nav_field_v2( + lines[0], 0, True ), - "sv_clock_drift": parse_rinex_float( - lines[0][41:61] + "sv_clock_drift": parse_nav_field_v2( + lines[0], 1, True ), - "sv_clock_drift_rate": parse_rinex_float( - lines[0][61:80] + "sv_clock_drift_rate": parse_nav_field_v2( + lines[0], 2, True ), - "iode": parse_rinex_float(lines[1][4:22]), - "crs": parse_rinex_float(lines[1][22:41]), - "delta_n": parse_rinex_float(lines[1][41:60]), - "m0": parse_rinex_float(lines[1][61:80]), - "cuc": parse_rinex_float(lines[2][4:22]), - "ecc": parse_rinex_float(lines[2][22:41]), - "cus": parse_rinex_float(lines[2][41:60]), - "sqrt_a": parse_rinex_float(lines[2][60:80]), - "toe": parse_rinex_float(lines[3][4:22]), - "cic": parse_rinex_float(lines[3][22:41]), - "omega0": parse_rinex_float(lines[3][41:60]), - "cis": parse_rinex_float(lines[3][60:80]), - "i0": parse_rinex_float(lines[4][4:22]), - "crc": parse_rinex_float(lines[4][22:41]), - "omega": parse_rinex_float(lines[4][41:60]), - "omega_dot": parse_rinex_float(lines[4][60:80]), - "idot": parse_rinex_float(lines[5][4:22]), - "codes_l2": parse_rinex_float(lines[5][22:41]), - "gps_week": parse_rinex_float(lines[5][41:61]), - "l2p_flag": parse_rinex_float(lines[5][61:80]), - "sv_accuracy": parse_rinex_float(lines[6][4:22]), - "sv_health": parse_rinex_float(lines[6][22:41]), - "tgd": parse_rinex_float(lines[6][41:61]), - "iodc": parse_rinex_float(lines[6][61:80]), - "transmission_time": parse_rinex_float( - lines[7][4:22] + "iode": parse_nav_field_v2(lines[1], 0), + "crs": parse_nav_field_v2(lines[1], 1), + "delta_n": parse_nav_field_v2(lines[1], 2), + "m0": parse_nav_field_v2(lines[1], 3), + "cuc": parse_nav_field_v2(lines[2], 0), + "ecc": parse_nav_field_v2(lines[2], 1), + "cus": parse_nav_field_v2(lines[2], 2), + "sqrt_a": parse_nav_field_v2(lines[2], 3), + "toe": parse_nav_field_v2(lines[3], 0), + "cic": parse_nav_field_v2(lines[3], 1), + "omega0": parse_nav_field_v2(lines[3], 2), + "cis": parse_nav_field_v2(lines[3], 3), + "i0": parse_nav_field_v2(lines[4], 0), + "crc": parse_nav_field_v2(lines[4], 1), + "omega": parse_nav_field_v2(lines[4], 2), + "omega_dot": parse_nav_field_v2(lines[4], 3), + "idot": parse_nav_field_v2(lines[5], 0), + "codes_l2": parse_nav_field_v2(lines[5], 1), + "gps_week": parse_nav_field_v2(lines[5], 2), + "l2p_flag": parse_nav_field_v2(lines[5], 3), + "sv_accuracy": parse_nav_field_v2(lines[6], 0), + "sv_health": parse_nav_field_v2(lines[6], 1), + "tgd": parse_nav_field_v2(lines[6], 2), + "iodc": parse_nav_field_v2(lines[6], 3), + "transmission_time": parse_nav_field_v2( + lines[7], 0 ) if len(lines) > 7 else None, - "fit_interval": parse_rinex_float(lines[7][22:41]) + "fit_interval": parse_nav_field_v2(lines[7], 1) if len(lines) > 7 else None, "extra": lines[8:], @@ -802,53 +991,53 @@ def read_rinex_nav(filename: str) -> Dict[str, List[Dict[str, Any]]]: ephemeris = { "prn": prn, "epoch": epoch, - "sv_clock_bias": parse_rinex_float( - lines[0][23:41] + "sv_clock_bias": parse_nav_field_v2( + lines[0], 0, True ), - "sv_clock_drift": parse_rinex_float( - lines[0][41:61] + "sv_clock_drift": parse_nav_field_v2( + lines[0], 1, True ), - "sv_clock_drift_rate": parse_rinex_float( - lines[0][61:80] + "sv_clock_drift_rate": parse_nav_field_v2( + lines[0], 2, True ), - "x": parse_rinex_float(lines[1][4:22]), - "x_vel": parse_rinex_float(lines[1][22:41]), - "x_acc": parse_rinex_float(lines[1][41:60]), - "health": parse_rinex_float(lines[1][60:80]), - "y": parse_rinex_float(lines[2][4:22]), - "y_vel": parse_rinex_float(lines[2][22:41]), - "y_acc": parse_rinex_float(lines[2][41:61]), - "z": parse_rinex_float(lines[3][4:22]), - "z_vel": parse_rinex_float(lines[3][21:41]), - "z_acc": parse_rinex_float(lines[3][41:61]), + "x": parse_nav_field_v2(lines[1], 0), + "x_vel": parse_nav_field_v2(lines[1], 1), + "x_acc": parse_nav_field_v2(lines[1], 2), + "health": parse_nav_field_v2(lines[1], 3), + "y": parse_nav_field_v2(lines[2], 0), + "y_vel": parse_nav_field_v2(lines[2], 1), + "y_acc": parse_nav_field_v2(lines[2], 2), + "z": parse_nav_field_v2(lines[3], 0), + "z_vel": parse_nav_field_v2(lines[3], 1), + "z_acc": parse_nav_field_v2(lines[3], 2), "extra": lines[4:], } elif v2_system == "G": - # GLONASS + # GLONASS (Table A11) ephemeris = { "prn": prn, "epoch": epoch, - "sv_clock_bias": parse_rinex_float( - lines[0][23:42] + "sv_clock_bias": parse_nav_field_v2( + lines[0], 0, True ), - "sv_relative_freq_bias": parse_rinex_float( - lines[0][42:61] + "sv_relative_freq_bias": parse_nav_field_v2( + lines[0], 1, True ), - "message_frame_time": parse_rinex_float( - lines[0][61:80] + "message_frame_time": parse_nav_field_v2( + lines[0], 2, True ), - "x": parse_rinex_float(lines[1][4:22]), - "x_vel": parse_rinex_float(lines[1][22:41]), - "x_acc": parse_rinex_float(lines[1][41:60]), - "health": parse_rinex_float(lines[1][60:80]), - "y": parse_rinex_float(lines[2][4:22]), - "y_vel": parse_rinex_float(lines[2][22:41]), - "y_acc": parse_rinex_float(lines[2][41:60]), - "freq_num": parse_rinex_float(lines[2][60:80]), - "z": parse_rinex_float(lines[3][4:22]), - "z_vel": parse_rinex_float(lines[3][22:41]), - "z_acc": parse_rinex_float(lines[3][41:60]), - "age": parse_rinex_float(lines[3][60:80]), + "x": parse_nav_field_v2(lines[1], 0), + "x_vel": parse_nav_field_v2(lines[1], 1), + "x_acc": parse_nav_field_v2(lines[1], 2), + "health": parse_nav_field_v2(lines[1], 3), + "y": parse_nav_field_v2(lines[2], 0), + "y_vel": parse_nav_field_v2(lines[2], 1), + "y_acc": parse_nav_field_v2(lines[2], 2), + "freq_num": parse_nav_field_v2(lines[2], 3), + "z": parse_nav_field_v2(lines[3], 0), + "z_vel": parse_nav_field_v2(lines[3], 1), + "z_acc": parse_nav_field_v2(lines[3], 2), + "age": parse_nav_field_v2(lines[3], 3), "extra": lines[4:], } else: @@ -895,27 +1084,37 @@ def read_rinex_nav(filename: str) -> Dict[str, List[Dict[str, Any]]]: continue system = prn[0] - try: - epoch = parse_nav_epoch(current_line) - lines = [current_line] - line_count = 4 if system == "R" or system == "S" else 7 - for _ in range(line_count): - next_line = f.readline() - line_number += 1 - if not next_line: - break - lines.append(next_line) + # Collect the epoch line plus every continuation line (their + # satellite-identifier columns are blank). The record length + # varies with version and system -- e.g. GLONASS grew a fourth + # broadcast-orbit line in RINEX 3.05 -- so frame the record by + # content instead of a fixed line count. + lines = [current_line] + current_line = f.readline() + line_number += 1 + while ( + current_line + and current_line.strip() + and not current_line[0:3].strip() + ): + lines.append(current_line) + current_line = f.readline() + line_number += 1 - if len(lines) < line_count + 1: - current_line = f.readline() - line_number += 1 - continue + try: + epoch = parse_nav_epoch(lines[0]) + min_lines = 4 if system in ("R", "S") else 7 + if len(lines) < min_lines: + raise ValueError( + f"record has {len(lines)} lines, expected at least " + f"{min_lines}" + ) if system == "R": # GLONASS ephemeris = { "prn": prn, "epoch": epoch, - "sv_clock_bias": parse_rinex_float(lines[0][23:41]), + "sv_clock_bias": parse_rinex_float(lines[0][23:42]), "sv_relative_freq_bias": parse_rinex_float( lines[0][42:61] ), @@ -923,15 +1122,15 @@ def read_rinex_nav(filename: str) -> Dict[str, List[Dict[str, Any]]]: lines[0][61:80] ), "x": parse_rinex_float(lines[1][4:23]), - "x_vel": parse_rinex_float(lines[1][23:41]), + "x_vel": parse_rinex_float(lines[1][23:42]), "x_acc": parse_rinex_float(lines[1][42:61]), "health": parse_rinex_float(lines[1][61:80]), "y": parse_rinex_float(lines[2][4:23]), - "y_vel": parse_rinex_float(lines[2][23:41]), + "y_vel": parse_rinex_float(lines[2][23:42]), "y_acc": parse_rinex_float(lines[2][42:61]), "freq_num": parse_rinex_float(lines[2][61:80]), "z": parse_rinex_float(lines[3][4:23]), - "z_vel": parse_rinex_float(lines[3][23:41]), + "z_vel": parse_rinex_float(lines[3][23:42]), "z_acc": parse_rinex_float(lines[3][42:61]), "age": parse_rinex_float(lines[3][61:80]), "extra": lines[4:], @@ -1036,12 +1235,22 @@ def read_rinex_nav(filename: str) -> Dict[str, List[Dict[str, Any]]]: file=sys.stderr, ) - current_line = f.readline() - line_number += 1 - return satellites +def is_bds_geo_ephemeris(ephemeris: Dict[str, Any]) -> bool: + """Return True for BeiDou GEO satellites, whose broadcast ephemeris is + referred to a frame inclined -5 deg to the equator (BDS ICD).""" + sat_type = ephemeris.get("sat_type") + if sat_type: # CNV1/CNV2/CNV3: 1=GEO, 2=IGSO, 3=MEO + return int(sat_type) == 1 + try: + prn_num = int(ephemeris["prn"][1:]) + except ValueError: + return False + return prn_num <= 5 or prn_num >= 59 + + def calculate_satellite_position( ephemeris: Dict[str, Any], transmit_time: float ) -> Tuple[float, float, float]: @@ -1126,17 +1335,37 @@ def calculate_satellite_position( xk_prime = rk * cos(uk) yk_prime = rk * sin(uk) - # Corrected longitude of ascending node - omega_k = ( - ephemeris["omega0"] - + (ephemeris["omega_dot"] - omega_e_dot) * transmit_time - - omega_e_dot * ephemeris["toe"] - ) + if system == "C" and is_bds_geo_ephemeris(ephemeris): + # BeiDou GEO ephemerides are referred to a frame inclined -5 deg + # to the equator: longitude of ascending node without the Earth + # rotation term, then rotate into ECEF (BDS ICD). + omega_k = ( + ephemeris["omega0"] + + ephemeris["omega_dot"] * transmit_time + - omega_e_dot * ephemeris["toe"] + ) + xg = xk_prime * cos(omega_k) - yk_prime * cos(ik) * sin(omega_k) + yg = xk_prime * sin(omega_k) + yk_prime * cos(ik) * cos(omega_k) + zg = yk_prime * sin(ik) - # Earth-fixed coordinates - xk = xk_prime * cos(omega_k) - yk_prime * cos(ik) * sin(omega_k) - yk = xk_prime * sin(omega_k) + yk_prime * cos(ik) * cos(omega_k) - zk = yk_prime * sin(ik) + phi = omega_e_dot * transmit_time + sin_t = sin(np.radians(-5.0)) + cos_t = cos(np.radians(-5.0)) + xk = xg * cos(phi) + yg * sin(phi) * cos_t + zg * sin(phi) * sin_t + yk = -xg * sin(phi) + yg * cos(phi) * cos_t + zg * cos(phi) * sin_t + zk = -yg * sin_t + zg * cos_t + else: + # Corrected longitude of ascending node + omega_k = ( + ephemeris["omega0"] + + (ephemeris["omega_dot"] - omega_e_dot) * transmit_time + - omega_e_dot * ephemeris["toe"] + ) + + # Earth-fixed coordinates + xk = xk_prime * cos(omega_k) - yk_prime * cos(ik) * sin(omega_k) + yk = xk_prime * sin(omega_k) + yk_prime * cos(ik) * cos(omega_k) + zk = yk_prime * sin(ik) return xk, yk, zk