14
14
import numpy as np
15
15
16
16
from gnss_lib_py .parsers .navdata import NavData
17
+ from gnss_lib_py .utils import constants as consts
17
18
from gnss_lib_py .utils .coordinates import ecef_to_geodetic
18
19
19
20
def solve_wls (measurements , weight_type = None , only_bias = False ,
20
21
receiver_state = None , tol = 1e-7 , max_count = 20 ,
21
- delta_t_decimals = - 2 ):
22
+ sv_rx_time = False , delta_t_decimals = - 2 ):
22
23
"""Runs weighted least squares across each timestep.
23
24
24
25
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,
29
30
in rx_est_m will be updated if only_bias is set to True.
30
31
31
32
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.
39
34
40
35
Parameters
41
36
----------
@@ -56,6 +51,14 @@ def solve_wls(measurements, weight_type = None, only_bias = False,
56
51
max_count : int
57
52
Number of maximum iterations before process is aborted and
58
53
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.
59
62
delta_t_decimals : int
60
63
Decimal places after which times are considered equal.
61
64
@@ -102,6 +105,7 @@ def solve_wls(measurements, weight_type = None, only_bias = False,
102
105
if weight_type is not None :
103
106
if isinstance (weight_type ,str ) and weight_type in measurements .rows :
104
107
weights = measurement_subset [weight_type ].reshape (- 1 ,1 )
108
+ weights = weights [not_nan_indexes ]
105
109
else :
106
110
raise TypeError ("WLS weights must be None or row" \
107
111
+ " in NavData" )
@@ -118,7 +122,7 @@ def solve_wls(measurements, weight_type = None, only_bias = False,
118
122
,0 ].reshape (- 1 ,1 ),
119
123
position [3 ])) # clock bias
120
124
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 )
122
126
states .append ([timestamp ] + np .squeeze (position ).tolist ())
123
127
except RuntimeError as error :
124
128
if str (error ) not in runtime_error_idxs :
@@ -155,7 +159,7 @@ def solve_wls(measurements, weight_type = None, only_bias = False,
155
159
return state_estimate
156
160
157
161
def 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 ):
159
163
"""Weighted least squares solver for GNSS measurements.
160
164
161
165
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,
170
174
array with shape (4 x 1) and the following order:
171
175
x_rx_m, y_rx_m, z_rx_m, b_rx_m.
172
176
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
174
178
the columns contain in order x_sv_m, y_sv_m, and z_sv_m.
175
179
corr_pr_m : np.ndarray
176
180
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,
186
190
max_count : int
187
191
Number of maximum iterations before process is aborted and
188
192
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``.
189
204
190
205
Returns
191
206
-------
@@ -195,11 +210,35 @@ def wls(rx_est_m, pos_sv_m, corr_pr_m, weights = None,
195
210
array with shape (4 x 1) and the following order:
196
211
x_rx_m, y_rx_m, z_rx_m, b_rx_m.
197
212
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
+
198
233
"""
199
234
200
235
rx_est_m = rx_est_m .copy () # don't change referenced value
201
236
202
237
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 ()
203
242
num_svs = pos_sv_m .shape [0 ]
204
243
if num_svs < 4 and not only_bias :
205
244
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,
245
284
else :
246
285
rx_est_m += pos_x_delta
247
286
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
+
248
297
count += 1
249
298
250
299
if count >= max_count :
0 commit comments