22
33"""
44
5- __authors__ = "Ashwin Kanhere"
6- __date__ = "25 Januray 2020"
5+ __authors__ = "Ashwin Kanhere, Derek Knowles"
6+ __date__ = "25 Jan 2020"
7+
8+ import warnings
79
810import numpy as np
911
12+ from gnss_lib_py .parsers .navdata import NavData
13+ from gnss_lib_py .utils .coordinates import ecef_to_geodetic
1014from gnss_lib_py .utils .filters import BaseExtendedKalmanFilter
1115
16+ def solve_ekf_gnss (measurements , init_dict = dict (),
17+ params_dict = dict ()):
18+ """Runs a GNSS Extended Kalman Filter across each timestep.
19+
20+ Runs an Extended Kalman Filter across each timestep and adds a new
21+ row for the receiver's position and clock bias.
22+
23+ Parameters
24+ ----------
25+ measurements : gnss_lib_py.parsers.navdata.NavData
26+ Instance of the NavData class
27+ init_dict : dict
28+ Initialization dict with initial states and covariances.
29+ params_dict : dict
30+ Dictionary of parameters for GNSS EKF.
31+
32+ Returns
33+ -------
34+ state_estimate : gnss_lib_py.parsers.navdata.NavData
35+ Estimated receiver position in ECEF frame in meters and the
36+ estimated receiver clock bias also in meters as an instance of
37+ the NavData class with shape (4 x # unique timesteps) and
38+ the following rows: x_rx_m, y_rx_m, z_rx_m, b_rx_m.
39+
40+ """
41+
42+ # check that all necessary rows exist
43+ measurements .in_rows (["gps_millis" ,"corr_pr_m" ,
44+ "x_sv_m" ,"y_sv_m" ,"z_sv_m" ,
45+ ])
46+
47+ # create initialization parameters.
48+ gnss_ekf = GNSSEKF (init_dict , params_dict )
49+
50+ states = []
51+
52+ for timestamp , delta_t , measurement_subset in measurements .loop_time ("gps_millis" ):
53+
54+ pos_sv_m = measurement_subset [["x_sv_m" ,"y_sv_m" ,"z_sv_m" ]].T
55+ pos_sv_m = np .atleast_2d (pos_sv_m )
56+
57+ corr_pr_m = measurement_subset ["corr_pr_m" ].reshape (- 1 ,1 )
58+
59+ # remove NaN indexes
60+ not_nan_indexes = ~ np .isnan (pos_sv_m ).any (axis = 1 )
61+ pos_sv_m = pos_sv_m [not_nan_indexes ]
62+ corr_pr_m = corr_pr_m [not_nan_indexes ]
63+
64+ # prediction step
65+ predict_dict = {"delta_t" : delta_t }
66+ gnss_ekf .predict (predict_dict = predict_dict )
67+
68+ # update step
69+ update_dict = {"pos_sv_m" : pos_sv_m }
70+ gnss_ekf .update (corr_pr_m , update_dict = update_dict )
71+
72+ states .append ([timestamp ] + np .squeeze (gnss_ekf .state ).tolist ())
73+
74+ states = np .array (states )
75+
76+ if states .size == 0 :
77+ warnings .warn ("No valid state estimate computed in solve_ekf_gnss, " \
78+ + "returning None." , RuntimeWarning )
79+ return None
80+
81+ state_estimate = NavData ()
82+ state_estimate ["gps_millis" ] = states [:,0 ]
83+ state_estimate ["x_rx_m" ] = states [:,1 ]
84+ state_estimate ["y_rx_m" ] = states [:,2 ]
85+ state_estimate ["z_rx_m" ] = states [:,3 ]
86+ state_estimate ["b_rx_m" ] = states [:,4 ]
87+
88+ lat ,lon ,alt = ecef_to_geodetic (state_estimate [["x_rx_m" ,
89+ "y_rx_m" ,
90+ "z_rx_m" ]])
91+ state_estimate ["lat_rx_deg" ] = lat
92+ state_estimate ["lon_rx_deg" ] = lon
93+ state_estimate ["alt_rx_deg" ] = alt
94+
95+ return state_estimate
96+
1297class GNSSEKF (BaseExtendedKalmanFilter ):
1398 """GNSS-only EKF implementation.
1499
@@ -17,42 +102,40 @@ class GNSSEKF(BaseExtendedKalmanFilter):
17102
18103 Attributes
19104 ----------
20- dt : float
105+ params_dict : dict
106+ Dictionary of parameters that may include the following.
107+ delta_t : float
21108 Time between prediction instances
22109 motion_type : string
23- Type of motion (stationary or constant velocity )
110+ Type of motion (`` stationary`` or ``constant_velocity`` )
24111 measure_type : string
25- NavData types (pseudoranges )
112+ NavData types (pseudorange )
26113 """
27114 def __init__ (self , init_dict , params_dict ):
28115 super ().__init__ (init_dict , params_dict )
29- self .dt = params_dict ['dt' ]
30- try :
31- self .motion_type = params_dict ['motion_type' ]
32- except KeyError :
33- self .motion_type = 'stationary'
34- try :
35- self .measure_type = params_dict ['measure_type' ]
36- except KeyError :
37- self .measure_type = 'pseudoranges'
38-
39- def dyn_model (self , u , predict_dict = None ):
40- """Non linear dynamics
116+
117+ self .delta_t = params_dict .get ('dt' ,1.0 )
118+ self .motion_type = params_dict .get ('motion_type' ,'stationary' )
119+ self .measure_type = params_dict .get ('measure_type' ,'pseudorange' )
120+
121+ def dyn_model (self , u , predict_dict = dict ()):
122+ """Nonlinear dynamics
41123
42124 Parameters
43125 ----------
44126 u : np.ndarray
45127 Control signal, not used for propagation
46- predict_dict : Dict
47- Additional prediction parameters, not used currently
128+ predict_dict : dict
129+ Additional prediction parameters, including ``delta_t``
130+ updates.
48131
49132 Returns
50133 -------
51134 new_x : np.ndarray
52135 Propagated state
53136 """
54- A = self .linearize_dynamics ()
55- new_x = A @ self .x
137+ A = self .linearize_dynamics (predict_dict )
138+ new_x = A @ self .state
56139 return new_x
57140
58141 def measure_model (self , update_dict ):
@@ -62,10 +145,15 @@ def measure_model(self, update_dict):
62145 :math:`\\ rho = \\ sqrt{(x-x_{sv})^2 + (y-y_{sv})^2 + (z-z_{sv})^2} + b`.
63146 See [1]_ for more details and models.
64147
148+ ``pos_sv_m`` must be a key in update_dict and must be an array
149+ of shape [3 x N] with rows of x_sv_m, y_sv_m, and z_sv_m in that
150+ order.
151+
65152 Parameters
66153 ----------
67- update_dict : Dict
68- Update dictionary containing satellite positions with key 'sv_pos'
154+ update_dict : dict
155+ Update dictionary containing satellite positions with key
156+ ``pos_sv_m``.
69157
70158 Returns
71159 -------
@@ -80,34 +168,41 @@ def measure_model(self, update_dict):
80168 applications. John Wiley & Sons, 2021.
81169 """
82170 if self .measure_type == 'pseudorange' :
83- sv_pos = update_dict ['sv_pos ' ]
84- pseudo = np .sqrt ((self .x [0 ] - sv_pos [0 , :])** 2
85- + (self .x [1 ] - sv_pos [1 , :])** 2
86- + (self .x [2 ] - sv_pos [2 , :])** 2 ) \
87- + self .x [6 ]
171+ pos_sv_m = update_dict ['pos_sv_m ' ]
172+ pseudo = np .sqrt ((self .state [0 ] - pos_sv_m [0 , :])** 2
173+ + (self .state [1 ] - pos_sv_m [1 , :])** 2
174+ + (self .state [2 ] - pos_sv_m [2 , :])** 2 ) \
175+ + self .state [6 ]
88176 z = np .reshape (pseudo , [- 1 , 1 ])
89177 else :
90178 raise NotImplementedError
91179 return z
92180
93- def linearize_dynamics (self , predict_dict = None ):
181+ def linearize_dynamics (self , predict_dict = dict () ):
94182 """Linearization of dynamics model
95183
96184 Parameters
97185 ----------
98- predict_dict : Dict
186+ predict_dict : dict
99187 Additional predict parameters, not used in current implementation
100188
101189 Returns
102190 -------
103191 A : np.ndarray
104192 Linear dynamics model depending on motion_type
193+ predict_dict : dict
194+ Dictionary of prediction parameters.
105195 """
196+
197+ # uses delta_t from predict_dict if exists, otherwise delta_t
198+ # from the class initialization.
199+ delta_t = predict_dict .get ('delta_t' , self .delta_t )
200+
106201 if self .motion_type == 'stationary' :
107202 A = np .eye (7 )
108203 elif self .motion_type == 'constant_velocity' :
109204 A = np .eye (7 )
110- A [:3 , - 4 :- 1 ] = self . dt * np .eye (3 )
205+ A [:3 , - 4 :- 1 ] = delta_t * np .eye (3 )
111206 else :
112207 raise NotImplementedError
113208 return A
@@ -117,21 +212,21 @@ def linearize_measurements(self, update_dict):
117212
118213 Parameters
119214 ----------
120- update_dict : Dict
121- Update dictionary containing satellite positions with key 'sv_pos '
215+ update_dict : dict
216+ Update dictionary containing satellite positions with key 'pos_sv_m '
122217
123218 Returns
124219 -------
125220 H : np.ndarray
126221 Jacobian of measurement model, dimension M x N
127222 """
128223 if self .measure_type == 'pseudorange' :
129- sv_pos = update_dict ['sv_pos ' ]
130- m = np .shape (sv_pos )[1 ]
131- H = np .zeros ([m , self .x_dim ])
224+ pos_sv_m = update_dict ['pos_sv_m ' ]
225+ m = np .shape (pos_sv_m )[1 ]
226+ H = np .zeros ([m , self .state_dim ])
132227 pseudo_expect = self .measure_model (update_dict )
133- rx_pos = np .reshape (self .x [:3 ], [- 1 , 1 ])
134- H [:, :3 ] = (rx_pos - sv_pos ).T / pseudo_expect
228+ rx_pos = np .reshape (self .state [:3 ], [- 1 , 1 ])
229+ H [:, :3 ] = (rx_pos - pos_sv_m ).T / pseudo_expect
135230 H [:, 6 ] = 1
136231 else :
137232 raise NotImplementedError
0 commit comments