Skip to content

Commit 59b2a6a

Browse files
Merge pull request #133 from Stanford-NavLab/derek/precise-interpolate
Derek/precise interpolate
2 parents 2fc2026 + 5ea38f8 commit 59b2a6a

12 files changed

Lines changed: 310 additions & 599 deletions

File tree

gnss_lib_py/parsers/clk.py

Lines changed: 58 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -93,80 +93,71 @@ def __init__(self, input_paths):
9393
self["sv_id"] = sv_id
9494
self["b_sv_m"] = b_sv_m
9595

96-
def extract_clk(self, gnss_sv_id, sidx, ipos = 10, \
97-
method='CubicSpline', verbose = False):
98-
"""Computing interpolated function over clk data for any GNSS
96+
def interpolate_clk(self, navdata, window=6, verbose=False):
97+
"""Interpolate clock data from clk file, adding inplace to given
98+
NavData instance.
9999
100100
Parameters
101101
----------
102-
gnss_sv_id : string
103-
Constellations and SV id of satellite for which position is
104-
to be determined.
105-
See standard nomenclature reference for more details.
106-
sidx : int
107-
Nearest index within clk time series around which interpolated
108-
function needs to be centered.
109-
ipos : int
110-
No. of data points from clk data on either side of sidx
111-
that will be used for computing interpolated function.
112-
method : string
113-
Type of interpolation method used for clk data (the default is
114-
CubicSpline, which depicts third-order polynomial).
102+
navdata : gnss_lib_py.parsers.navdata.NavData
103+
Instance of the NavData class that must include rows for
104+
``gps_millis`` and ``gnss_sv_id``
105+
window : int
106+
Number of points to use in interpolation window.
115107
verbose : bool
116-
If true, prints extra debugging statements.
108+
Flag (True/False) for whether to print intermediate steps useful
109+
for debugging/reviewing (the default is False)
117110
118-
Returns
119-
-------
120-
func_satbias : np.ndarray
121-
Instance with 1-D array of scipy.interpolate.interpolate.interp1d
122-
that is loaded with .clk data
123111
"""
112+
# add satellite indexes if not present already.
113+
sv_idx_keys = ['b_sv_m', 'b_dot_sv_mps']
124114

125-
clkdata = self.where("gnss_sv_id",gnss_sv_id)
115+
for sv_idx_key in sv_idx_keys:
116+
if sv_idx_key not in navdata.rows:
117+
navdata[sv_idx_key] = np.nan
126118

127-
if method=='CubicSpline':
128-
low_i = (sidx - ipos) if (sidx - ipos) >= 0 else 0
129-
high_i = (sidx + ipos) if (sidx + ipos) <= len(clkdata["gps_millis"]) else -1
119+
available_svs_from_clk = np.unique(self["gnss_sv_id"])
130120

121+
for gnss_sv_id in np.unique(navdata["gnss_sv_id"]):
131122
if verbose:
132-
print('Nearest clk: ', sidx, clkdata["gps_millis"][sidx],
133-
clkdata["b_sv_m"][sidx])
134-
135-
func_satbias = interpolate.CubicSpline(clkdata["gps_millis"][low_i:high_i], \
136-
clkdata["b_sv_m"][low_i:high_i])
137-
138-
return func_satbias
139-
140-
141-
def clk_snapshot(self, func_satbias, cxtime, hstep = 5e-1,
142-
method='CubicSpline'):
143-
"""Compute satellite clock bias and drift from clk interpolated function
144-
145-
Parameters
146-
----------
147-
func_satbias : scipy.interpolate._cubic.CubicSpline
148-
Instance with interpolated function for satellite bias from .clk data
149-
cxtime : float
150-
Time at which satellite clock bias and drift is to be computed
151-
hstep : float
152-
Step size in milliseconds used to computing clock drift using
153-
central differencing (the default is 5e-1)
154-
method : string
155-
Type of interpolation method used for sp3 data (the default is
156-
CubicSpline, which depicts third-order polynomial)
157-
158-
Returns
159-
-------
160-
satbias_clk : float
161-
Computed satellite clock bias (in seconds)
162-
satdrift_clk : float
163-
Computed satellite clock drift (in seconds/seconds)
164-
"""
165-
166-
if method=='CubicSpline':
167-
sat_t = func_satbias([cxtime-0.5*hstep, cxtime, cxtime+0.5*hstep])
168-
169-
satbias_clk = sat_t[1]
170-
satdrift_clk = (sat_t[2]-sat_t[0]) / hstep
171-
172-
return satbias_clk, (satdrift_clk * 1e3)
123+
print("interpolating clk for ",gnss_sv_id)
124+
navdata_id = navdata.where("gnss_sv_id",gnss_sv_id)
125+
navdata_id_gps_millis = np.atleast_1d(navdata_id["gps_millis"])
126+
127+
if gnss_sv_id in available_svs_from_clk:
128+
clk_id = self.where("gnss_sv_id",gnss_sv_id)
129+
x_data = clk_id["gps_millis"]
130+
y_data = clk_id["b_sv_m"]
131+
132+
if np.min(navdata_id_gps_millis) < x_data[0] \
133+
or np.max(navdata_id_gps_millis) > x_data[-1]:
134+
raise RuntimeError("clk data does not include all "\
135+
+ "times in measurement file.")
136+
137+
b_sv_m = np.zeros(len(navdata_id))
138+
b_dot_sv_mps = np.zeros(len(navdata_id))
139+
140+
# iterate through needed polynomials so don't repeat fit
141+
insert_indexes = np.searchsorted(x_data,navdata_id_gps_millis)
142+
for insert_index in np.unique(insert_indexes):
143+
max_index = min(len(clk_id)-1,insert_index+int(window/2))
144+
min_index = max(0,insert_index-int(window/2))
145+
x_window = x_data[min_index:max_index]
146+
y_window = y_data[min_index:max_index]
147+
148+
insert_index_idxs = np.argwhere(insert_indexes==insert_index)
149+
x_interpret = navdata_id_gps_millis[insert_index_idxs]
150+
151+
poly = np.polynomial.polynomial.Polynomial.fit(x_window, y_window, deg=3)
152+
b_sv = poly(x_interpret)
153+
154+
polyder = poly.deriv()
155+
b_dot_sv = polyder(x_interpret)
156+
157+
b_sv_m[insert_index_idxs] = b_sv
158+
# multiply by 1000 since currently meters/milliseconds
159+
b_dot_sv_mps[insert_index_idxs] = b_dot_sv*1000
160+
161+
row_idx = navdata.argwhere("gnss_sv_id",gnss_sv_id)
162+
navdata["b_sv_m",row_idx] = b_sv_m
163+
navdata["b_dot_sv_mps",row_idx] = b_dot_sv_mps

gnss_lib_py/parsers/sp3.py

Lines changed: 65 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ def __init__(self, input_paths):
4747
input_paths = [input_paths]
4848

4949
gps_millis = []
50-
unix_millis = []
5150
gnss_sv_ids = []
5251
gnss_id = []
5352
sv_id = []
@@ -104,94 +103,78 @@ def __init__(self, input_paths):
104103
self["y_sv_m"] = y_sv_m
105104
self["z_sv_m"] = z_sv_m
106105

107-
def extract_sp3(self, gnss_sv_id, sidx, ipos = 10, \
108-
method = 'CubicSpline', verbose = False):
109-
"""Computing interpolated function over sp3 data for any GNSS
106+
def interpolate_sp3(self, navdata, window=6, verbose=False):
107+
"""Interpolate ECEF position data from sp3 file, inplace to
108+
given navdata instance.
110109
111110
Parameters
112111
----------
113-
gnss_sv_id : string
114-
PRN of satellite for which position should be determined.
115-
sidx : int
116-
Nearest index within sp3 time series around which interpolated
117-
function needs to be centered.
118-
ipos : int
119-
No. of data points from sp3 data on either side of sidx
120-
that will be used for computing interpolated function.
121-
method : string
122-
Type of interpolation method used for sp3 data (the default is
123-
CubicSpline, which depicts third-order polynomial).
112+
navdata : gnss_lib_py.parsers.navdata.NavData
113+
Instance of the NavData class that must include rows for
114+
``gps_millis`` and ``gnss_sv_id``.
115+
window : int
116+
Number of points to use in interpolation window.
124117
verbose : bool
125-
If true, prints extra debugging statements.
118+
Flag (True/False) for whether to print intermediate steps useful
119+
for debugging/reviewing (the default is False)
126120
127-
Returns
128-
-------
129-
func_satpos : np.ndarray
130-
Instance with 3-D array of scipy.interpolate.interpolate.interp1d
131-
that is loaded with .sp3 data
132121
"""
122+
# add satellite indexes if not present already.
123+
sv_idx_keys = ['x_sv_m', 'y_sv_m', 'z_sv_m', \
124+
'vx_sv_mps','vy_sv_mps','vz_sv_mps']
133125

134-
sp3data = self.where("gnss_sv_id",gnss_sv_id)
126+
for sv_idx_key in sv_idx_keys:
127+
if sv_idx_key not in navdata.rows:
128+
navdata[sv_idx_key] = np.nan
135129

136-
func_satpos = np.empty((3,), dtype=object)
137-
func_satpos[:] = np.nan
138-
139-
if method=='CubicSpline':
140-
low_i = (sidx - ipos) if (sidx - ipos) >= 0 else 0
141-
high_i = (sidx + ipos) if (sidx + ipos) <= len(sp3data["gps_millis"]) else -1
130+
available_svs_from_sp3 = np.unique(self["gnss_sv_id"])
142131

132+
for gnss_sv_id in np.unique(navdata["gnss_sv_id"]):
143133
if verbose:
144-
print('Nearest sp3: ', sidx, sp3data["gps_millis"][sidx], \
145-
sp3data["x_sv_m"][sidx],
146-
sp3data["y_sv_m"][sidx],
147-
sp3data["z_sv_m"][sidx])
148-
149-
func_satpos[0] = interpolate.CubicSpline(sp3data["gps_millis"][low_i:high_i], \
150-
sp3data["x_sv_m"][low_i:high_i])
151-
func_satpos[1] = interpolate.CubicSpline(sp3data["gps_millis"][low_i:high_i], \
152-
sp3data["y_sv_m"][low_i:high_i])
153-
func_satpos[2] = interpolate.CubicSpline(sp3data["gps_millis"][low_i:high_i], \
154-
sp3data["z_sv_m"][low_i:high_i])
155-
156-
return func_satpos
157-
158-
159-
def sp3_snapshot(self, func_satpos, cxtime, hstep = 5e-1,
160-
method='CubicSpline'):
161-
"""Compute satellite 3D position and velocity.
162-
163-
Computes from the sp3 interpolated function.
164-
165-
Parameters
166-
----------
167-
func_satpos : np.ndarray
168-
Instance with 3-D array of scipy.interpolate.interpolate.interp1d
169-
that is loaded with .sp3 data.
170-
cxtime : float
171-
Time at which the satellite 3-D position and velocity needs to be
172-
computed, given 3-D array of interpolated functions.
173-
hstep : float
174-
Step size in milliseconds used to computing 3-D velocity of any
175-
given satellite using central differencing the default is 5e-1).
176-
method : string
177-
Type of interpolation method used for sp3 data (the default is
178-
CubicSpline, which depicts third-order polynomial).
179-
180-
Returns
181-
-------
182-
satpos_sp3 : 3-D array
183-
Computed satellite position in ECEF frame (Earth's rotation not included)
184-
satvel_sp3 : 3-D array
185-
Computed satellite velocity in ECEF frame (Earth's rotation not included)
186-
"""
187-
if method=='CubicSpline':
188-
sat_x = func_satpos[0]([cxtime-0.5*hstep, cxtime, cxtime+0.5*hstep])
189-
sat_y = func_satpos[1]([cxtime-0.5*hstep, cxtime, cxtime+0.5*hstep])
190-
sat_z = func_satpos[2]([cxtime-0.5*hstep, cxtime, cxtime+0.5*hstep])
191-
192-
satpos_sp3 = np.array([sat_x[1], sat_y[1], sat_z[1]])
193-
satvel_sp3 = np.array([ (sat_x[2]-sat_x[0]) / hstep, \
194-
(sat_y[2]-sat_y[0]) / hstep, \
195-
(sat_z[2]-sat_z[0]) / hstep ])
196-
197-
return satpos_sp3, (satvel_sp3 * 1e3)
134+
print("interpolating sp3 for ",gnss_sv_id)
135+
navdata_id = navdata.where("gnss_sv_id",gnss_sv_id)
136+
navdata_id_gps_millis = np.atleast_1d(navdata_id["gps_millis"])
137+
138+
if gnss_sv_id in available_svs_from_sp3:
139+
sp3_id = self.where("gnss_sv_id",gnss_sv_id)
140+
x_data = sp3_id["gps_millis"]
141+
y_data = sp3_id[["x_sv_m","y_sv_m","z_sv_m"]]
142+
143+
if np.min(navdata_id_gps_millis) < x_data[0] \
144+
or np.max(navdata_id_gps_millis) > x_data[-1]:
145+
raise RuntimeError("sp3 data does not include "\
146+
+ "appropriate times in measurement"\
147+
+ " file for SV ",gnss_sv_id)
148+
149+
xyz_sv_m = np.zeros((6,len(navdata_id)))
150+
151+
# iterate through needed polynomials so don't repeat fit
152+
insert_indexes = np.searchsorted(x_data,navdata_id_gps_millis)
153+
for insert_index in np.unique(insert_indexes):
154+
max_index = min(len(sp3_id)-1,insert_index+int(window/2))
155+
min_index = max(0,insert_index-int(window/2))
156+
x_window = x_data[min_index:max_index]
157+
y_window = y_data[:,min_index:max_index]
158+
159+
insert_index_idxs = np.argwhere(insert_indexes==insert_index)
160+
x_interpret = navdata_id_gps_millis[insert_index_idxs]
161+
162+
# iterate through x,y, and z
163+
for xyz_index in range(3):
164+
poly = np.polynomial.polynomial.Polynomial.fit(x_window, y_window[xyz_index,:], deg=3)
165+
xyz_sv = poly(x_interpret)
166+
167+
polyder = poly.deriv()
168+
vxyz_sv = polyder(x_interpret)
169+
170+
xyz_sv_m[xyz_index,insert_index_idxs] = xyz_sv
171+
# multiply by 1000 since currently meters/milliseconds
172+
xyz_sv_m[xyz_index+3,insert_index_idxs] = vxyz_sv*1000
173+
174+
row_idx = navdata.argwhere("gnss_sv_id",gnss_sv_id)
175+
navdata["x_sv_m",row_idx] = xyz_sv_m[0,:]
176+
navdata["y_sv_m",row_idx] = xyz_sv_m[1,:]
177+
navdata["z_sv_m",row_idx] = xyz_sv_m[2,:]
178+
navdata["vx_sv_mps",row_idx] = xyz_sv_m[3,:]
179+
navdata["vy_sv_mps",row_idx] = xyz_sv_m[4,:]
180+
navdata["vz_sv_mps",row_idx] = xyz_sv_m[5,:]

gnss_lib_py/utils/ephemeris_downloader.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ def load_ephemeris(file_type, gps_millis,
104104
105105
"""
106106

107+
if isinstance(file_paths, (str, os.PathLike)):
108+
file_paths = [file_paths]
109+
107110
existing_paths, needed_files = _verify_ephemeris(file_type,
108111
gps_millis,
109112
constellations,
@@ -477,6 +480,9 @@ def _valid_ephemeris_in_paths(date, possible_types, file_paths=None):
477480
for path in file_paths:
478481
if os.path.split(path)[1][10:] == os.path.split(recommended_file[1])[1][10:-3]:
479482
return True, path
483+
for path in file_paths:
484+
if os.path.split(path)[1][3:] == str(gps_week).zfill(4) + str((timetuple.tm_wday+1)%7) + ".sp3":
485+
return True, path
480486

481487
# sp3 from last two weeks
482488
elif possible_type == "sp3_rapid_GFZ":
@@ -498,6 +504,9 @@ def _valid_ephemeris_in_paths(date, possible_types, file_paths=None):
498504
for path in file_paths:
499505
if os.path.split(path)[1][10:] == os.path.split(recommended_file[1])[1][10:-3]:
500506
return True, path
507+
for path in file_paths:
508+
if os.path.split(path)[1][3:] == str(gps_week).zfill(4) + str((timetuple.tm_wday+1)%7) + ".sp3":
509+
return True, path
501510

502511
# sp3 if longer than two weeks ago
503512
elif possible_type == "sp3_final_CODE":
@@ -519,6 +528,9 @@ def _valid_ephemeris_in_paths(date, possible_types, file_paths=None):
519528
for path in file_paths:
520529
if os.path.split(path)[1][10:] == os.path.split(recommended_file[1])[1][10:-3]:
521530
return True, path
531+
for path in file_paths:
532+
if os.path.split(path)[1][3:] == str(gps_week).zfill(4) + str((timetuple.tm_wday+1)%7) + ".sp3":
533+
return True, path
522534

523535
# clk from last three days
524536
elif possible_type == "clk_rapid_CODE":
@@ -540,6 +552,9 @@ def _valid_ephemeris_in_paths(date, possible_types, file_paths=None):
540552
for path in file_paths:
541553
if os.path.split(path)[1][10:] == os.path.split(recommended_file[1])[1][10:-3]:
542554
return True, path
555+
for path in file_paths:
556+
if os.path.split(path)[1][3:] == str(gps_week).zfill(4) + str((timetuple.tm_wday+1)%7) + ".clk":
557+
return True, path
543558

544559
# clk from last two weeks
545560
elif possible_type == "clk_rapid_GFZ":
@@ -561,6 +576,9 @@ def _valid_ephemeris_in_paths(date, possible_types, file_paths=None):
561576
for path in file_paths:
562577
if os.path.split(path)[1][10:] == os.path.split(recommended_file[1])[1][10:-3]:
563578
return True, path
579+
for path in file_paths:
580+
if os.path.split(path)[1][3:] == str(gps_week).zfill(4) + str((timetuple.tm_wday+1)%7) + ".clk":
581+
return True, path
564582

565583
# clk if longer than two weeks ago
566584
elif possible_type == "clk_final_CODE":
@@ -582,6 +600,9 @@ def _valid_ephemeris_in_paths(date, possible_types, file_paths=None):
582600
for path in file_paths:
583601
if os.path.split(path)[1][10:] == os.path.split(recommended_file[1])[1][10:-3]:
584602
return True, path
603+
for path in file_paths:
604+
if os.path.split(path)[1][3:] == str(gps_week).zfill(4) + str((timetuple.tm_wday+1)%7) + ".clk":
605+
return True, path
585606

586607
else:
587608
raise RuntimeError(possible_type,"invalid possible_type "\

0 commit comments

Comments
 (0)