1111
1212from gnss_lib_py .parsers .navdata import NavData
1313from gnss_lib_py .algorithms .snapshot import solve_wls
14- from gnss_lib_py .utils import constants as consts
15- from gnss_lib_py .utils .sim_gnss import _find_delxyz_range
1614from gnss_lib_py .utils .coordinates import ecef_to_geodetic
1715from gnss_lib_py .utils .filters import BaseExtendedKalmanFilter
1816
@@ -51,80 +49,31 @@ def solve_gnss_ekf(measurements, init_dict = None,
5149 init_dict = {}
5250
5351 if "state_0" not in init_dict :
54- try :
55- # if the given measurement frame has a state estimate, use
56- # that, including the clock bias estimate
57- pos_est_rows = measurements .find_wildcard_indexes (["x_rx*_m" ,
58- "y_rx*_m" ,
59- "z_rx*_m" ,
60- "b_rx*_m" ],
61- max_allow = 1 )
62- not_nan_idxs = measurements .argwhere (pos_est_rows ['x_rx*_m' ],
63- np .nan , 'neq' )
64- state_0 = np .zeros ((7 ,1 ))
65- state_0 [0 ,0 ] = measurements [pos_est_rows ['x_rx*_m' ], not_nan_idxs [0 ]]
66- state_0 [1 ,0 ] = measurements [pos_est_rows ['y_rx*_m' ], not_nan_idxs [0 ]]
67- state_0 [2 ,0 ] = measurements [pos_est_rows ['z_rx*_m' ], not_nan_idxs [0 ]]
68- state_0 [6 ,0 ] = measurements [pos_est_rows ['b_rx*_m' ], not_nan_idxs [0 ]]
69- except KeyError :
70- try :
71- # a key error happened and one of the rows from the last
72- # try clause is not present. Try again without bias,
73- # which often missing from datasets
74- pos_est_rows = measurements .find_wildcard_indexes (["x_rx*_m" ,
75- "y_rx*_m" ,
76- "z_rx*_m" ],
77- max_allow = 1 )
78- not_nan_idxs = measurements .argwhere (pos_est_rows ['x_rx*_m' ],
79- np .nan , 'neq' )
80- state_0 = np .zeros ((7 ,1 ))
81- state_0 [0 ,0 ] = measurements [pos_est_rows ['x_rx*_m' ], not_nan_idxs [0 ]]
82- state_0 [1 ,0 ] = measurements [pos_est_rows ['y_rx*_m' ], not_nan_idxs [0 ]]
83- state_0 [2 ,0 ] = measurements [pos_est_rows ['z_rx*_m' ], not_nan_idxs [0 ]]
84- pos_0 = NavData ()
85- pos_0 ['gps_millis' ] = measurements ['gps_millis' , not_nan_idxs [0 ]]
86- pos_0 ['x_rx_m' ] = state_0 [0 ,0 ]
87- pos_0 ['y_rx_m' ] = state_0 [1 ,0 ]
88- pos_0 ['z_rx_m' ] = state_0 [2 ,0 ]
89- measurement_subset = measurements .copy (cols = not_nan_idxs [0 ])
90- pos_0 = solve_wls (measurement_subset ,
91- receiver_state = pos_0 ,
92- only_bias = True )
93- # if len(pos_0.where('b_rx_wls_m', np.nan, 'eq'))==0:
94- # break
95- state_0 [6 ,0 ] = pos_0 ['b_rx_wls_m' ]
96- except KeyError :
97- # position rows were not found again, use a WLS estimate
98- pos_0 = None
99- for _ , _ , measurement_subset in measurements .loop_time ("gps_millis" ,
100- delta_t_decimals = delta_t_decimals ):
101- pos_0 = solve_wls (measurement_subset )
102- # Assume that if 'x_rx_wls_m' is np.nan, then a state estimate
103- # has not been found and all x, y, and z are np.nan.
104- # If the length of elements where 'x_rx_wls_m' is np.nan is
105- # 0, then a solution has been found and can be used as an
106- # initialization
107- if len (pos_0 .where ('x_rx_wls_m' , np .nan , 'eq' ))== 0 :
108- break
109-
110- state_0 = np .zeros ((7 ,1 ))
111- if pos_0 is not None :
112- state_0 [:3 ,0 ] = pos_0 [["x_rx_wls_m" ,"y_rx_wls_m" ,"z_rx_wls_m" ]]
113- state_0 [6 ,0 ] = pos_0 [["b_rx_wls_m" ]]
52+ pos_0 = None
53+ for _ , _ , measurement_subset in measurements .loop_time ("gps_millis" ,
54+ delta_t_decimals = delta_t_decimals ):
55+ pos_0 = solve_wls (measurement_subset )
56+ if pos_0 is not None :
57+ break
58+
59+ state_0 = np .zeros ((7 ,1 ))
60+ if pos_0 is not None :
61+ state_0 [:3 ,0 ] = pos_0 [["x_rx_wls_m" ,"y_rx_wls_m" ,"z_rx_wls_m" ]]
62+ state_0 [6 ,0 ] = pos_0 [["b_rx_wls_m" ]]
63+
11464 init_dict ["state_0" ] = state_0
11565
11666 if "sigma_0" not in init_dict :
11767 sigma_0 = np .eye (init_dict ["state_0" ].size )
11868 init_dict ["sigma_0" ] = sigma_0
11969
12070 if "Q" not in init_dict :
121- raise RuntimeError ("Process noise must be specified in init_dict" )
71+ process_noise = np .eye (init_dict ["state_0" ].size )
72+ init_dict ["Q" ] = process_noise
12273
12374 if "R" not in init_dict :
124- raise RuntimeError ("Measurement noise must be specified in init_dict" )
125-
126- if "use_tx_time" not in init_dict :
127- init_dict ["use_tx_time" ] = False
75+ measurement_noise = np .eye (1 ) # gets overwritten
76+ init_dict ["R" ] = measurement_noise
12877
12978 # initialize parameter dictionary
13079 if params_dict is None :
@@ -212,7 +161,6 @@ def __init__(self, init_dict, params_dict):
212161 self .delta_t = params_dict .get ('dt' ,1.0 )
213162 self .motion_type = params_dict .get ('motion_type' ,'stationary' )
214163 self .measure_type = params_dict .get ('measure_type' ,'pseudorange' )
215- self .use_tx_time = init_dict .get ('use_tx_time' , False )
216164
217165 def dyn_model (self , u , predict_dict = None ):
218166 """Nonlinear dynamics
@@ -248,34 +196,11 @@ def measure_model(self, update_dict):
248196 of shape [3 x N] with rows of x_sv_m, y_sv_m, and z_sv_m in that
249197 order.
250198
251- This measurment model uses the current state estimate to find
252- the time taken for signals to propagate from the satellites to
253- the receiver and updates the SV positions to reflect the changed
254- ECEF reference frame.
255-
256- Since the ECEF reference frame moves with the Earth, the frame
257- of reference is different at different times.
258- SV positions are calculated for the time at which the signal was
259- transmitted but the receiver position is computed for the ECEF
260- frame of reference when the signals are received. Consequently,
261- the SV positions must be updated to account for the change in the
262- ECEF frame between signal transmission and reception.
263- However, given the EKF has an initial position guess around the
264- true position (either through prior knowledge or a prior state
265- estimation process such as WLS), we can simply correct the SV
266- positions once and use them as such, without further modification.
267-
268199 Parameters
269200 ----------
270201 update_dict : dict
271202 Update dictionary containing satellite positions with key
272- ``pos_sv_m`` and optionally ``tx_time``. ``tx_time`` specifies
273- if the filter should use the SV positions at time of
274- transmission (if True). If False, the the time it takes for
275- the signal to propagate from the satellite to the receiver
276- is accounted for and the SV positions are propagated forward
277- to the ECEF coordinate frame at the receiver time. By default,
278- ``tx_time`` is True.
203+ ``pos_sv_m``.
279204
280205 Returns
281206 -------
@@ -291,18 +216,6 @@ def measure_model(self, update_dict):
291216 """
292217 if self .measure_type == 'pseudorange' :
293218 pos_sv_m = update_dict ['pos_sv_m' ]
294- if not self .use_tx_time :
295- rx_pos_m = np .array ([[self .state [0 ]], [self .state [1 ]], [self .state [2 ]]])
296- num_svs = np .shape (pos_sv_m )[1 ]
297- _ , true_range = _find_delxyz_range (pos_sv_m .T , rx_pos_m , num_svs )
298- tx_time = (true_range - self .state [6 ])/ consts .C
299- dtheta = consts .OMEGA_E_DOT * tx_time
300- # The following two lines are expanded position updates for a
301- # rotation by dtheta radians about the z-axis, which updates
302- # the positions along the x and y axes but the position along
303- # the z-axis is unchanged.
304- pos_sv_m [0 , :] = np .cos (dtheta )* pos_sv_m [0 ,:] + np .sin (dtheta )* pos_sv_m [1 ,:]
305- pos_sv_m [1 , :] = - np .sin (dtheta )* pos_sv_m [0 ,:] + np .cos (dtheta )* pos_sv_m [1 ,:]
306219 pseudo = np .sqrt ((self .state [0 ] - pos_sv_m [0 , :])** 2
307220 + (self .state [1 ] - pos_sv_m [1 , :])** 2
308221 + (self .state [2 ] - pos_sv_m [2 , :])** 2 ) \
0 commit comments