Skip to content

Commit 7e9ac29

Browse files
committed
add heading, velocity, acceleration standards
1 parent 1ea12f7 commit 7e9ac29

7 files changed

Lines changed: 97 additions & 51 deletions

File tree

docs/source/reference/reference.rst

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,8 @@ State estimate naming conventions are as follows:
175175
* :code:`x_rx_m` : (float) receiver ECEF x position estimate in meters.
176176
* :code:`y_rx_m` : (float) receiver ECEF y position estimate in meters.
177177
* :code:`z_rx_m` : (float) receiver ECEF z position estimate in meters.
178+
* :code:`v_rx_mps` : (float) receiver total velocity estimate in
179+
meters per second.
178180
* :code:`vx_rx_mps` : (float) receiver ECEF x velocity estimate in
179181
meters per second.
180182
* :code:`vy_rx_mps` : (float) receiver ECEF y velocity estimate in
@@ -183,6 +185,8 @@ State estimate naming conventions are as follows:
183185
meters per second.
184186
* :code:`ax_rx_mps2` : (float) receiver ECEF x acceleration estimate in
185187
meters per second squared.
188+
* :code:`a_rx_mps2` : (float) receiver total acceleration estimate in
189+
meters per second squared.
186190
* :code:`ay_rx_mps2` : (float) receiver ECEF y acceleration estimate in
187191
meters per second squared.
188192
* :code:`az_rx_mps2` : (float) receiver ECEF z acceleration estimate in
@@ -196,6 +200,9 @@ State estimate naming conventions are as follows:
196200
degrees.
197201
* :code:`alt_rx_m` : (float) receiver altitude position estimate in
198202
meters. Referenced to the WGS-84 ellipsoid.
203+
* :code:`heading_rx_rad` : (float) receiver heading estimate in radians
204+
where 0 radians is North and pi/2 radians is East, etc.
205+
Assumed to be radians in the range between 0 and 2pi.
199206

200207
Receiver ground truth naming conventions are as follows:
201208

@@ -209,19 +216,31 @@ Receiver ground truth naming conventions are as follows:
209216
meters.
210217
* :code:`z_rx_gt_m` : (float) receiver ECEF z ground truth position in
211218
meters.
219+
* :code:`v_rx_gt_mps` : (float) receiver total velocity ground truth in
220+
meters per second.
212221
* :code:`vx_rx_gt_mps` : (float) receiver ECEF x velocity ground truth
213222
in meters per second.
214223
* :code:`vy_rx_gt_mps` : (float) receiver ECEF y velocity ground truth
215224
in meters per second.
216225
* :code:`vz_rx_gt_mps` : (float) receiver ECEF z velocity ground truth
217226
in meters per second.
227+
* :code:`a_rx_gt_mps2` : (float) receiver total acceleration estimate in
228+
meters per second squared.
229+
* :code:`ax_rx_gt_mps2` : (float) receiver ECEF x acceleration ground truth
230+
in meters per second squared.
231+
* :code:`ay_rx_gt_mps2` : (float) receiver ECEF y acceleration ground truth
232+
in meters per second squared.
233+
* :code:`az_rx_gt_mps2` : (float) receiver ECEF z acceleration ground truth
234+
in meters per second squared.
218235
* :code:`lat_rx_gt_deg` : (float) receiver ground truth latitude in
219236
degrees.
220237
* :code:`lon_rx_gt_deg` : (float) receiver ground truth longitude in
221238
degrees.
222239
* :code:`alt_rx_gt_m` : (float) receiver ground truth altitude in meters.
223240
Referenced to the WGS-84 ellipsoid.
224-
241+
* :code:`heading_rx_gt_rad` : (float) receiver heading estimate in
242+
radians where 0 radians is North and pi/2 radians is East, etc.
243+
Assumed to be radians in the range between 0 and 2pi.
225244

226245
Module Level Function References
227246
--------------------------------

gnss_lib_py/parsers/android.py

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import pandas as pd
1515

1616
from gnss_lib_py.parsers.navdata import NavData
17+
from gnss_lib_py.utils.coordinates import wrap_0_to_2pi
1718
from gnss_lib_py.utils.coordinates import geodetic_to_ecef
1819
from gnss_lib_py.utils.coordinates import ecef_to_geodetic
1920
from gnss_lib_py.utils.time_conversions import unix_to_gps_millis
@@ -271,14 +272,18 @@ def postprocess(self):
271272
https://www.kaggle.com/code/gymf123/tips-notes-from-the-competition-hosts
272273
"""
273274
# Correcting reported altitude
274-
self['alt_gt_m'] = self['alt_gt_m'] - 61.
275-
gt_lla = np.transpose(np.vstack([self['lat_gt_deg'],
276-
self['lon_gt_deg'],
277-
self['alt_gt_m']]))
275+
self['alt_rx_gt_m'] = self['alt_rx_gt_m'] - 61.
276+
gt_lla = np.transpose(np.vstack([self['lat_rx_gt_deg'],
277+
self['lon_rx_gt_deg'],
278+
self['alt_rx_gt_m']]))
278279
gt_ecef = geodetic_to_ecef(gt_lla)
279-
self["x_gt_m"] = gt_ecef[:,0]
280-
self["y_gt_m"] = gt_ecef[:,1]
281-
self["z_gt_m"] = gt_ecef[:,2]
280+
self["x_rx_gt_m"] = gt_ecef[:,0]
281+
self["y_rx_gt_m"] = gt_ecef[:,1]
282+
self["z_rx_gt_m"] = gt_ecef[:,2]
283+
284+
# convert bearing degrees to heading in radians
285+
self["heading_rx_gt_rad"] = np.deg2rad(self["heading_rx_gt_rad"])
286+
self["heading_rx_gt_rad"] = wrap_0_to_2pi(self["heading_rx_gt_rad"])
282287

283288
@staticmethod
284289
def _row_map():
@@ -289,10 +294,12 @@ def _row_map():
289294
row_map : Dict
290295
Dictionary of the form {old_name : new_name}
291296
"""
292-
row_map = {'latDeg' : 'lat_gt_deg',
293-
'lngDeg' : 'lon_gt_deg',
294-
'heightAboveWgs84EllipsoidM' : 'alt_gt_m',
295-
'millisSinceGpsEpoch' : 'gps_millis'
297+
row_map = {'latDeg' : 'lat_rx_gt_deg',
298+
'lngDeg' : 'lon_rx_gt_deg',
299+
'heightAboveWgs84EllipsoidM' : 'alt_rx_gt_m',
300+
'millisSinceGpsEpoch' : 'gps_millis',
301+
'speedMps' : 'v_rx_gt_mps',
302+
'courseDegree' : 'heading_rx_gt_rad',
296303
}
297304
return row_map
298305

@@ -309,20 +316,24 @@ def postprocess(self):
309316
Notes
310317
-----
311318
"""
312-
if np.any(np.isnan(self['alt_gt_m'])):
319+
if np.any(np.isnan(self['alt_rx_gt_m'])):
313320
warnings.warn("Some altitude values were missing, using 0m ", RuntimeWarning)
314-
self['alt_gt_m'] = np.nan_to_num(self['alt_gt_m'])
315-
gt_lla = np.transpose(np.vstack([self['lat_gt_deg'],
316-
self['lon_gt_deg'],
317-
self['alt_gt_m']]))
321+
self['alt_rx_gt_m'] = np.nan_to_num(self['alt_rx_gt_m'])
322+
gt_lla = np.transpose(np.vstack([self['lat_rx_gt_deg'],
323+
self['lon_rx_gt_deg'],
324+
self['alt_rx_gt_m']]))
318325
gt_ecef = geodetic_to_ecef(gt_lla)
319-
self["x_gt_m"] = gt_ecef[:,0]
320-
self["y_gt_m"] = gt_ecef[:,1]
321-
self["z_gt_m"] = gt_ecef[:,2]
326+
self["x_rx_gt_m"] = gt_ecef[:,0]
327+
self["y_rx_gt_m"] = gt_ecef[:,1]
328+
self["z_rx_gt_m"] = gt_ecef[:,2]
322329

323330
# add gps milliseconds
324331
self["gps_millis"] = unix_to_gps_millis(self['unix_millis'])
325332

333+
# convert bearing degrees to heading in radians
334+
self["heading_rx_gt_rad"] = np.deg2rad(self["heading_rx_gt_rad"])
335+
self["heading_rx_gt_rad"] = wrap_0_to_2pi(self["heading_rx_gt_rad"])
336+
326337
@staticmethod
327338
def _row_map():
328339
"""Map row names from loaded data to gnss_lib_py standard
@@ -332,10 +343,13 @@ def _row_map():
332343
row_map : Dict
333344
Dictionary of the form {old_name : new_name}
334345
"""
335-
row_map = {'LatitudeDegrees' : 'lat_gt_deg',
336-
'LongitudeDegrees' : 'lon_gt_deg',
337-
'AltitudeMeters' : 'alt_gt_m',
338-
'UnixTimeMillis' : 'unix_millis'
346+
row_map = {'LatitudeDegrees' : 'lat_rx_gt_deg',
347+
'LongitudeDegrees' : 'lon_rx_gt_deg',
348+
'AltitudeMeters' : 'alt_rx_gt_m',
349+
'SpeedMps' : 'v_rx_gt_mps',
350+
'BearingDegrees' : 'heading_rx_gt_rad',
351+
'UnixTimeMillis' : 'unix_millis',
352+
339353
}
340354
return row_map
341355

gnss_lib_py/parsers/smart_loc.py

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import numpy as np
99

1010
from gnss_lib_py.parsers.navdata import NavData
11-
from gnss_lib_py.utils.coordinates import LocalCoord, geodetic_to_ecef
11+
from gnss_lib_py.utils.coordinates import LocalCoord, geodetic_to_ecef, wrap_0_to_2pi
1212
from gnss_lib_py.utils.time_conversions import tow_to_gps_millis
1313

1414

@@ -56,6 +56,16 @@ def postprocess(self):
5656
self["gps_millis"] = [tow_to_gps_millis(*x) for x in
5757
zip(self["gps_week"],self["gps_tow"])]
5858

59+
# convert SmartLoc East counterclockwise heading into
60+
# North clockwise heading standard
61+
self["heading_rx_gt_rad"] = np.pi/2. - self["heading_rx_gt_rad"]
62+
self["heading_rx_gt_rad"] = wrap_0_to_2pi(self["heading_rx_gt_rad"])
63+
64+
# remove duplicate rows
65+
self.remove(rows=["GPSWeek [weeks]",
66+
"GPSSecondsOfWeek [s]"
67+
],inplace=True)
68+
5969

6070
@staticmethod
6171
def _row_map():
@@ -78,12 +88,14 @@ def _row_map():
7888
'Estimated Doppler measurement standard deviation (doStdev) [Hz]' \
7989
: 'doppler_sigma_hz',
8090
'Longitude (GT Lon) [deg]' : 'lon_rx_gt_deg',
81-
'Longitude Cov (GT Lon) [deg]' : 'lon_rx_gt_sigma_deg',
91+
'Longitude Cov (GT Lon) [deg]' : 'lon_sigma_rx_gt_deg',
8292
'Latitude (GT Lat) [deg]' : 'lat_rx_gt_deg',
83-
'Latitude Cov (GT Lat) [deg]' : 'lat_rx_gt_sigma_deg',
93+
'Latitude Cov (GT Lat) [deg]' : 'lat_sigma_rx_gt_deg',
8494
'Height above ellipsoid (GT Height) [m]' : 'alt_rx_gt_m',
85-
'Height above ellipsoid Cov (GT Height) [m]' : 'alt_rx_gt_sigma_m'
86-
95+
'Height above ellipsoid Cov (GT Height) [m]' : 'alt_sigma_rx_gt_m',
96+
'Heading (0° = East, counterclockwise) - (GT Heading) [rad]' : 'heading_rx_gt_rad',
97+
'Velocity (GT Velocity) [m/s]' : 'v_rx_gt_mps',
98+
'Acceleration (GT Acceleration) [ms^2]' : 'a_rx_gt_mps2',
8799
}
88100
return row_map
89101

@@ -151,11 +163,11 @@ def calculate_gt_vel(smartloc_raw):
151163
'az_rx_gt_mps2' : []}
152164
for _, _, measure_frame in smartloc_raw.loop_time('gps_millis', \
153165
delta_t_decimals = -2):
154-
yaw = measure_frame['Heading (0° = East, counterclockwise) - (GT Heading) [rad]', 0]
155-
vel_gt = measure_frame['Velocity (GT Velocity) [m/s]', 0]
156-
acc_gt = measure_frame['Acceleration (GT Acceleration) [ms^2]', 0]
157-
vel_ned = np.array([[np.sin(yaw)*vel_gt], [np.cos(yaw)*vel_gt], [0.]])
158-
acc_ned = np.array([[np.sin(yaw)*acc_gt], [np.cos(yaw)*acc_gt], [0.]])
166+
yaw = measure_frame['heading_rx_gt_rad', 0]
167+
vel_gt = measure_frame['v_rx_gt_mps', 0]
168+
acc_gt = measure_frame['a_rx_gt_mps2', 0]
169+
vel_ned = np.array([[np.cos(yaw)*vel_gt], [np.sin(yaw)*vel_gt], [0.]])
170+
acc_ned = np.array([[np.cos(yaw)*acc_gt], [np.sin(yaw)*acc_gt], [0.]])
159171
vel_ecef = ned_frame.ned_to_ecefv(vel_ned)
160172
acc_ecef = ned_frame.ned_to_ecefv(acc_ned)
161173
vel_acc['vx_rx_gt_mps'].extend(np.repeat(vel_ecef[0, 0], len(measure_frame)))
@@ -167,4 +179,3 @@ def calculate_gt_vel(smartloc_raw):
167179
for row, values in vel_acc.items():
168180
smartloc_raw[row] = values
169181
return smartloc_raw
170-

tests/parsers/test_android.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -634,7 +634,7 @@ def test_gt_alt_nan(root_path_2022):
634634
gt_2022_nan = os.path.join(root_path_2022, 'alt_nan_ground_truth.csv')
635635
with pytest.warns(RuntimeWarning):
636636
gt_2022 = android.AndroidGroundTruth2022(gt_2022_nan)
637-
np.testing.assert_almost_equal(gt_2022['alt_gt_m'],
637+
np.testing.assert_almost_equal(gt_2022['alt_rx_gt_m'],
638638
np.zeros(len(gt_2022)))
639639

640640
def test_remove_all_data(derived_path_xl):

tests/parsers/test_smart_loc.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
import pandas as pd
1313

1414
from gnss_lib_py.parsers.navdata import NavData
15+
from gnss_lib_py.utils.coordinates import wrap_0_to_2pi
1516
from gnss_lib_py.parsers.smart_loc import SmartLocRaw, remove_nlos, \
1617
calculate_gt_ecef, calculate_gt_vel
1718

19+
1820
@pytest.fixture(name="root_path")
1921
def fixture_root_path():
2022
"""Location of measurements for unit test
@@ -109,10 +111,14 @@ def test_smartloc_raw_df_equivalence(smartloc_raw, pd_df):
109111
"""
110112
# Also tests if strings are being converted back correctly
111113
measure_df = smartloc_raw.pandas_df()
114+
measure_df["heading_rx_gt_rad"] = -measure_df["heading_rx_gt_rad"] + np.pi/2.
115+
measure_df["heading_rx_gt_rad"] = wrap_0_to_2pi(measure_df["heading_rx_gt_rad"])
112116
inverse_row_map = {v : k for k,v in smartloc_raw._row_map().items()}
113117
measure_df.rename(columns=inverse_row_map, inplace=True)
114118
measure_df.drop(columns='gps_millis',inplace=True)
115119
pd_df['GNSS identifier (gnssId) []'] = pd_df['GNSS identifier (gnssId) []'].str.lower()
120+
pd_df.drop(columns="GPSWeek [weeks]",inplace=True)
121+
pd_df.drop(columns="GPSSecondsOfWeek [s]",inplace=True)
116122
pd.testing.assert_frame_equal(pd_df.sort_index(axis=1),
117123
measure_df.sort_index(axis=1),
118124
check_dtype=False, check_names=True)
@@ -266,5 +272,3 @@ def test_calculate_gt_vel_ecef(smartloc_raw):
266272
'az_rx_gt_mps2',])
267273
old_shape[0] = old_shape[0] + 6
268274
np.testing.assert_equal(old_shape, new_shape)
269-
270-

tests/utils/test_gnss_models.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,21 +58,20 @@ def calculate_state(android_gt, idx):
5858
state : gnss_lib_py.parsers.navdata.NavData
5959
NavData containing state information for one time instance.
6060
"""
61-
bearing_rad = np.deg2rad(android_gt['BearingDegrees', idx])
6261

63-
v_gt_n = android_gt['SpeedMps', idx]*np.cos(bearing_rad)
64-
v_gt_e = android_gt['SpeedMps', idx]*np.sin(bearing_rad)
62+
v_gt_n = android_gt['v_rx_gt_mps', idx]*np.cos(android_gt['heading_rx_gt_rad', idx])
63+
v_gt_e = android_gt['v_rx_gt_mps', idx]*np.sin(android_gt['heading_rx_gt_rad', idx])
6564
v_ned = np.array([[v_gt_n],[v_gt_e],[0]])
66-
llh = np.array([[android_gt['lat_gt_deg', idx]],
67-
[android_gt['lon_gt_deg', idx]],
68-
[android_gt['alt_gt_m', idx]]])
65+
llh = np.array([[android_gt['lat_rx_gt_deg', idx]],
66+
[android_gt['lon_rx_gt_deg', idx]],
67+
[android_gt['alt_rx_gt_m', idx]]])
6968
local_frame = LocalCoord.from_geodetic(llh)
7069
vx_ecef = local_frame.ned_to_ecefv(v_ned)
7170

7271
state = NavData()
73-
state['x_rx_m'] = android_gt['x_gt_m', idx]
74-
state['y_rx_m'] = android_gt['y_gt_m', idx]
75-
state['z_rx_m'] = android_gt['z_gt_m', idx]
72+
state['x_rx_m'] = android_gt['x_rx_gt_m', idx]
73+
state['y_rx_m'] = android_gt['y_rx_gt_m', idx]
74+
state['z_rx_m'] = android_gt['z_rx_gt_m', idx]
7675
state['vx_rx_mps'] = vx_ecef[0,0]
7776
state['vy_rx_mps'] = vx_ecef[1,0]
7877
state['vz_rx_mps'] = vx_ecef[2,0]
@@ -128,7 +127,6 @@ def test_pseudorange_corrections(gps_measurement_frames, android_gt, iono_params
128127
curr_millis = frame['gps_millis', 0]
129128
gt_slice_idx = android_gt.argwhere('gps_millis', curr_millis)
130129
state = calculate_state(android_gt, gt_slice_idx)
131-
# x_ecef = android_gt[['x_gt_m', 'y_gt_m', 'z_gt_m'], gt_slice_idx]
132130

133131
# Test corrections with ephemeris parameters
134132
est_clk, est_trp, est_iono = gnss_models.calculate_pseudorange_corr(

tests/utils/test_sv_models.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ def test_sv_state_model(gps_measurement_frames, android_gt):
144144
# Get Android Derived states, sorted by SVs
145145
curr_millis = android_frames[idx]['gps_millis', 0]
146146
gt_slice_idx = android_gt.argwhere('gps_millis', curr_millis)
147-
x_ecef = android_gt[['x_gt_m', 'y_gt_m', 'z_gt_m'], gt_slice_idx]
147+
x_ecef = android_gt[['x_rx_gt_m', 'y_rx_gt_m', 'z_rx_gt_m'], gt_slice_idx]
148148

149149

150150
est_sv_posvel, _, _ = sv_models._find_sv_location(curr_millis, x_ecef, ephem=vis_ephems[idx])
@@ -181,7 +181,7 @@ def test_visible_ephem(all_gps_ephem, gps_measurement_frames, android_gt):
181181
for idx, frame in enumerate(android_frames):
182182
curr_millis = frame['gps_millis', 0]
183183
gt_slice_idx = android_gt.argwhere('gps_millis', curr_millis)
184-
x_ecef = android_gt[['x_gt_m', 'y_gt_m', 'z_gt_m'], gt_slice_idx]
184+
x_ecef = android_gt[['x_rx_gt_m', 'y_rx_gt_m', 'z_rx_gt_m'], gt_slice_idx]
185185
# Test visible satellite computation with ephemeris
186186
eph = sv_models._find_visible_ephem(curr_millis, x_ecef, ephem=vis_ephems[idx], el_mask=0.)
187187
vis_svs = set(eph['sv_id'])
@@ -210,7 +210,7 @@ def test_visible_sv_posvel(gps_measurement_frames, android_gt):
210210
for idx, sv_posvel in enumerate(sv_states):
211211
curr_millis = android_frames[idx]['gps_millis', 0]
212212
gt_slice_idx = android_gt.argwhere('gps_millis', curr_millis)
213-
x_ecef = android_gt[['x_gt_m', 'y_gt_m', 'z_gt_m'], gt_slice_idx]
213+
x_ecef = android_gt[['x_rx_gt_m', 'y_rx_gt_m', 'z_rx_gt_m'], gt_slice_idx]
214214

215215
# Test that actually visible satellites are subset of expected satellites
216216
vis_posvel = sv_models._find_visible_sv_posvel(curr_millis, x_ecef, sv_posvel, el_mask=0.)

0 commit comments

Comments
 (0)