mirror of
https://github.com/gnss-sdr/gnss-sdr
synced 2026-10-16 04:31:50 +00:00
skyplot: improve RINEX 2/3/4 navigation record parsing
This commit is contained in:
+348
-119
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user