Skip to content

Commit f87e794

Browse files
committed
Added Rinex 3 .o file parsing
1 parent 527790f commit f87e794

4 files changed

Lines changed: 440 additions & 0 deletions

File tree

gnss_lib_py/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from gnss_lib_py.parsers.precise_ephemerides import *
1414
from gnss_lib_py.parsers.nmea import *
1515
from gnss_lib_py.parsers.smartloc import *
16+
from gnss_lib_py.parsers.rinex import *
1617

1718
from gnss_lib_py.utils.coordinates import *
1819
from gnss_lib_py.utils.filters import *

gnss_lib_py/parsers/rinex.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
"""Functions to process broadcast navigation and observations from Rinex.
2+
3+
"""
4+
5+
__authors__ = "Ashwin Kanhere"
6+
__date__ = "19 Jul 2023"
7+
8+
import numpy as np
9+
import georinex as gr
10+
11+
from gnss_lib_py.parsers.navdata import NavData
12+
import gnss_lib_py.utils.constants as consts
13+
from gnss_lib_py.utils.time_conversions import datetime_to_gps_millis
14+
15+
16+
class RinexObs3(NavData):
17+
"""Class handling Rinex 3 observation files [1]_.
18+
19+
The Rinex Observation files (of the format .yyo) contain measured
20+
pseudoranges, carrier phase, doppler and signal-to-noise ratio
21+
measurements for multiple constellations and bands.
22+
This loader converts those file types into a NavData in which
23+
measurements from different bands are treated as separate measurement
24+
instances. Inherits from NavData().
25+
26+
27+
References
28+
----------
29+
.. [1] https://files.igs.org/pub/data/format/rinex305.pdf
30+
31+
32+
"""
33+
34+
def __init__(self, input_path):
35+
"""Loading Rinex 3 observation files into a NavData based class.
36+
37+
Should input path to RXM-RAWX.csv file.
38+
39+
Parameters
40+
----------
41+
input_path : string or path-like
42+
Path to measurement csv file
43+
44+
"""
45+
46+
obs_file = gr.load(input_path).to_dataframe()
47+
obs_header = gr.rinexheader(input_path)
48+
obs_measure_types = obs_header['fields']
49+
rx_bands = []
50+
for rx_measures in obs_measure_types.values():
51+
for single_measure in rx_measures:
52+
band = single_measure[1]
53+
if band not in rx_bands:
54+
rx_bands.extend(band)
55+
56+
obs_file.reset_index(inplace=True)
57+
# Convert time to gps_millis
58+
gps_millis = [datetime_to_gps_millis(df_row['time']) \
59+
for _, df_row in obs_file.iterrows()]
60+
obs_file['gps_millis'] = gps_millis
61+
obs_file = obs_file.drop(columns=['time'])
62+
obs_file = obs_file.rename(columns={"sv":"sv_id"})
63+
# Convert gnss_sv_id to gnss_id and sv_id (plus gnss_sv_id)
64+
obs_navdata_raw = NavData(pandas_df=obs_file)
65+
obs_navdata_raw['gnss_sv_id'] = obs_navdata_raw['sv_id']
66+
gnss_chars = [sv_id[0] for sv_id in np.atleast_1d(obs_navdata_raw['sv_id'])]
67+
gnss_nums = [sv_id[1:] for sv_id in np.atleast_1d(obs_navdata_raw['sv_id'])]
68+
gnss_id = [consts.CONSTELLATION_CHARS[gnss_char] for gnss_char in gnss_chars]
69+
obs_navdata_raw['gnss_id'] = np.asarray(gnss_id)
70+
obs_navdata_raw['sv_id'] = np.asarray(gnss_nums, dtype=int)
71+
# Convert the coded column names to glp standards and extract information
72+
# into glp row and columns format
73+
info_rows = ['gps_millis', 'gnss_sv_id', 'sv_id', 'gnss_id']
74+
super().__init__()
75+
for band in rx_bands:
76+
rename_map = {}
77+
keep_rows = info_rows.copy()
78+
measure_type_dict = self._measure_type_dict()
79+
for measure_char, measure_row in measure_type_dict.items():
80+
measure_band_row = \
81+
obs_navdata_raw.find_wildcard_indexes(f'{measure_char}{band}*',
82+
max_allow=1)
83+
measure_row_chars = measure_band_row[f'{measure_char}{band}*'][0]
84+
rename_map[measure_row_chars] = measure_row
85+
keep_rows.append(measure_row_chars)
86+
band_navdata = obs_navdata_raw.copy(rows=keep_rows)
87+
band_navdata.rename(rename_map, inplace=True)
88+
# Remove the cases with NaNs in the measurements
89+
for row in rename_map.values():
90+
band_navdata = band_navdata.where(row, np.nan, 'neq')
91+
# Assign the gnss_lib_py standard names for signal_type
92+
rx_constellations = np.unique(band_navdata['gnss_id'])
93+
signal_type_dict = self._signal_type_dict()
94+
signal_types = np.empty(len(band_navdata), dtype=object)
95+
for constellation in rx_constellations:
96+
signal_type = signal_type_dict[constellation][band]
97+
signal_types[band_navdata['gnss_id']==constellation] = signal_type
98+
band_navdata['signal_type'] = signal_types
99+
if len(self) == 0:
100+
self.concat(band_navdata, inplace=True)
101+
else:
102+
self.concat(band_navdata, inplace=True)
103+
self.sort('gps_millis', inplace=True)
104+
105+
@staticmethod
106+
def _measure_type_dict():
107+
"""Map of Rinex observation measurement types to standard names.
108+
109+
Returns
110+
-------
111+
measure_type_dict : Dict
112+
Dictionary of the form {rinex_character : measure_name}
113+
"""
114+
115+
measure_type_dict = {'C': 'raw_pr_m',
116+
'L': 'carrier_phase',
117+
'D': 'raw_doppler_hz',
118+
'S': 'cn0_dbhz'}
119+
return measure_type_dict
120+
121+
@staticmethod
122+
def _signal_type_dict():
123+
"""Dictionary from constellation and signal bands to signal types.
124+
125+
Returns
126+
-------
127+
signal_type_dict : Dict
128+
Dictionary of the form {constellation_band : {band : signal_type}}
129+
"""
130+
signal_type_dict = {}
131+
signal_type_dict['gps'] = {'1' : 'l1',
132+
'2' : 'l2',
133+
'5' : 'l5'}
134+
signal_type_dict['glonass'] = {'1' : 'g1',
135+
'4' : 'g1a',
136+
'2' : 'g2',
137+
'6' : 'g2a',
138+
'3' : 'g3'}
139+
signal_type_dict['galileo'] = {'1' : 'e1',
140+
'5' : 'e5a',
141+
'7' : 'e5b',
142+
'8' : 'e5',
143+
'6' : 'e6'}
144+
signal_type_dict['sbas'] = {'1' : 'l1',
145+
'5' : 'l5'}
146+
signal_type_dict['qzss'] = {'1' : 'l1',
147+
'2' : 'l2',
148+
'5' : 'l5',
149+
'6' : 'l6'}
150+
# beidou needs to be refined because the current level of detail isn't enough
151+
# to distinguish between different signals
152+
signal_type_dict['beidou'] = {'2' : 'b1',
153+
'1' : 'b1c',
154+
'5' : 'b2a',
155+
'7' : 'b2b',
156+
'8' : 'b2',
157+
'6' : 'b3'}
158+
signal_type_dict['irnss'] = {'5' : 'l5',
159+
'9' : 's'}
160+
return signal_type_dict

notebooks/tutorials/parsers.ipynb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,41 @@
401401
"print('Loaded NMEA data with raw data and ECEF coordinates\\n', nmea_navdata)"
402402
]
403403
},
404+
{
405+
"cell_type": "markdown",
406+
"id": "fe8e8d6a",
407+
"metadata": {},
408+
"source": [
409+
"## Rinex v3 Observation File Parsing"
410+
]
411+
},
412+
{
413+
"cell_type": "markdown",
414+
"id": "af135db1",
415+
"metadata": {},
416+
"source": [
417+
"Rinex is another file standard that is used in the GNSS community to store and transmit navigation information.\n",
418+
"Files with the extension `.yyo`, where `yy` is the year in which the measurement was made, are used to store and transmit\n",
419+
"measurements.\n",
420+
"These measurements files can any constellation and each measurement usually contains the pseudorange, carrier phase (or difference from carrier frequency),\n",
421+
"doppler, and signal-to-noise ration measurements.\n",
422+
"In the following lines, we show how to load a ``.o`` file into a NavData instance."
423+
]
424+
},
425+
{
426+
"cell_type": "code",
427+
"execution_count": null,
428+
"id": "9f279032",
429+
"metadata": {},
430+
"outputs": [],
431+
"source": [
432+
"# download Rinex obs file and load it into NavData instance\n",
433+
"!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/rinex/obs/rinex_obs_mixed_types.20o --quiet -O \"rinex_obs_mixed_types.20o\"\n",
434+
"rinex_obs_3 = glp.RinexObs3(\"rinex_obs_mixed_types.20o\")\n",
435+
"print('Loaded Rinex Obs 3 data for the first time instant\\n', \\\n",
436+
" rinex_obs_3.where('gps_millis', rinex_obs_3['gps_millis', 0], 'eq'))"
437+
]
438+
},
404439
{
405440
"cell_type": "markdown",
406441
"id": "cf51b89e",

0 commit comments

Comments
 (0)