1414import numpy as np
1515
1616from gnss_lib_py .parsers .navdata import NavData
17+ from gnss_lib_py .utils import constants as consts
1718from gnss_lib_py .utils .coordinates import ecef_to_geodetic
1819
1920def solve_wls (measurements , weight_type = None , only_bias = False ,
2021 receiver_state = None , tol = 1e-7 , max_count = 20 ,
21- delta_t_decimals = - 2 ):
22+ sv_rx_time = False , delta_t_decimals = - 2 ):
2223 """Runs weighted least squares across each timestep.
2324
2425 Runs weighted least squares across each timestep and adds a new
@@ -29,13 +30,7 @@ def solve_wls(measurements, weight_type = None, only_bias = False,
2930 in rx_est_m will be updated if only_bias is set to True.
3031
3132 If only_bias is set to True, then the receiver position must also
32- be passed in as the receiver_state
33-
34- receiver_state : gnss_lib_py.parsers.navdata.NavData
35- Either estimated or ground truth receiver position in ECEF frame
36- in meters as an instance of the NavData class with the
37- following rows: ``x_rx*_m``, `y_rx*_m``, ``z_rx*_m``,
38- ``gps_millis``.
33+ be passed in as the receiver_state.
3934
4035 Parameters
4136 ----------
@@ -56,6 +51,14 @@ def solve_wls(measurements, weight_type = None, only_bias = False,
5651 max_count : int
5752 Number of maximum iterations before process is aborted and
5853 solution returned.
54+ sv_rx_time : bool
55+ Flag that specifies whether the input SV positions are in the ECEF
56+ frame of reference corresponding to when the measurements were
57+ received. If set to `True`, the satellite positions are used as
58+ is. The default value is `False`, in which case the ECEF positions
59+ are assumed to in the ECEF frame at the time of signal transmission
60+ and are converted to the ECEF frame at the time of signal reception,
61+ while solving the WLS problem.
5962 delta_t_decimals : int
6063 Decimal places after which times are considered equal.
6164
@@ -102,6 +105,7 @@ def solve_wls(measurements, weight_type = None, only_bias = False,
102105 if weight_type is not None :
103106 if isinstance (weight_type ,str ) and weight_type in measurements .rows :
104107 weights = measurement_subset [weight_type ].reshape (- 1 ,1 )
108+ weights = weights [not_nan_indexes ]
105109 else :
106110 raise TypeError ("WLS weights must be None or row" \
107111 + " in NavData" )
@@ -118,7 +122,7 @@ def solve_wls(measurements, weight_type = None, only_bias = False,
118122 ,0 ].reshape (- 1 ,1 ),
119123 position [3 ])) # clock bias
120124 position = wls (position , pos_sv_m , corr_pr_m , weights ,
121- only_bias , tol , max_count )
125+ only_bias , tol , max_count , sv_rx_time = sv_rx_time )
122126 states .append ([timestamp ] + np .squeeze (position ).tolist ())
123127 except RuntimeError as error :
124128 if str (error ) not in runtime_error_idxs :
@@ -155,7 +159,7 @@ def solve_wls(measurements, weight_type = None, only_bias = False,
155159 return state_estimate
156160
157161def wls (rx_est_m , pos_sv_m , corr_pr_m , weights = None ,
158- only_bias = False , tol = 1e-7 , max_count = 20 ):
162+ only_bias = False , tol = 1e-7 , max_count = 20 , sv_rx_time = False ):
159163 """Weighted least squares solver for GNSS measurements.
160164
161165 The option for only_bias allows the user to only calculate the clock
@@ -170,7 +174,7 @@ def wls(rx_est_m, pos_sv_m, corr_pr_m, weights = None,
170174 array with shape (4 x 1) and the following order:
171175 x_rx_m, y_rx_m, z_rx_m, b_rx_m.
172176 pos_sv_m : np.ndarray
173- Satellite positions as an array of shape [# svs x 3] where
177+ Satellite ECEF positions as an array of shape [# svs x 3] where
174178 the columns contain in order x_sv_m, y_sv_m, and z_sv_m.
175179 corr_pr_m : np.ndarray
176180 Corrected pseudoranges for all satellites with shape of
@@ -186,6 +190,17 @@ def wls(rx_est_m, pos_sv_m, corr_pr_m, weights = None,
186190 max_count : int
187191 Number of maximum iterations before process is aborted and
188192 solution returned.
193+ sv_rx_time : bool
194+ Flag to indicate if the satellite positions at the time of
195+ transmission should be used as is or if they should be transformed
196+ to the ECEF frame of reference at the time of reception. For real
197+ measurements, use ``sv_rx_time=False`` to account for the Earth's
198+ rotation and convert SV positions from the ECEF frame at the time
199+ of signal transmission to the ECEF frame at the time of signal
200+ reception. If the SV positions should be used as is, set
201+ ``sv_rx_time=True`` to indicate that the given positions are in
202+ the ECEF frame of reference for when the signals are received.
203+ By default, ``sv_rx_time=False``.
189204
190205 Returns
191206 -------
@@ -195,11 +210,35 @@ def wls(rx_est_m, pos_sv_m, corr_pr_m, weights = None,
195210 array with shape (4 x 1) and the following order:
196211 x_rx_m, y_rx_m, z_rx_m, b_rx_m.
197212
213+ Notes
214+ -----
215+ This function internally updates the used SV position to account for
216+ the time taken for the signal to travel to the Earth from the GNSS
217+ satellites.
218+ Since the SV and receiver positions are calculated in an ECEF frame
219+ of reference, which is moving with the Earth's rotation, the reference
220+ frame is slightly (about 30 m along longitude) different when the
221+ signals are received than when the signals were transmitted. Given
222+ the receiver's position is estimated when the signal is received,
223+ the SV positions need to be updated to reflect the change in the
224+ frame of reference in which their position is calculated.
225+
226+ This update happens after every Gauss-Newton update step and is
227+ adapted from [1]_.
228+
229+ References
230+ ----------
231+ .. [1] https://github.com/google/gps-measurement-tools/blob/master/opensource/FlightTimeCorrection.m
232+
198233 """
199234
200235 rx_est_m = rx_est_m .copy () # don't change referenced value
201236
202237 count = 0
238+ # Store the SV position at the original receiver time.
239+ # This position will be modified by the time taken by the signal to
240+ # travel to the receiver.
241+ rx_time_pos_sv_m = pos_sv_m .copy ()
203242 num_svs = pos_sv_m .shape [0 ]
204243 if num_svs < 4 and not only_bias :
205244 raise RuntimeError ("Need at least four satellites for WLS." )
@@ -245,6 +284,16 @@ def wls(rx_est_m, pos_sv_m, corr_pr_m, weights = None,
245284 else :
246285 rx_est_m += pos_x_delta
247286
287+ if not sv_rx_time :
288+ # Update the satellite positions based on the time taken for
289+ # the signal to reach the Earth and the satellite clock bias.
290+ delta_t = (corr_pr_m .reshape (- 1 ) - rx_est_m [3 ,0 ])/ consts .C
291+ dtheta = consts .OMEGA_E_DOT * delta_t
292+ pos_sv_m [:, 0 ] = np .cos (dtheta )* rx_time_pos_sv_m [:,0 ] + \
293+ np .sin (dtheta )* rx_time_pos_sv_m [:,1 ]
294+ pos_sv_m [:, 1 ] = - np .sin (dtheta )* rx_time_pos_sv_m [:,0 ] + \
295+ np .cos (dtheta )* rx_time_pos_sv_m [:,1 ]
296+
248297 count += 1
249298
250299 if count >= max_count :
0 commit comments