Skip to content

Commit f1c762b

Browse files
committed
- added mode to use L1C/B instead of L1C/A
- scan mode for RTCM decoder
1 parent 659eb4d commit f1c762b

File tree

4 files changed

+194
-47
lines changed

4 files changed

+194
-47
lines changed

receiver/decode_jps.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,6 +1011,9 @@ def main():
10111011
parser.add_argument("-j", "--jobs", default=int(mp.cpu_count() / 2),
10121012
type=int, help='Max. number of parallel processes')
10131013

1014+
parser.add_argument("--useL1CB", action='store_true',
1015+
help="use L1C/B as like L1C/A for QZS")
1016+
10141017
# Retrieve all command line arguments
10151018
#
10161019
args = parser.parse_args()
@@ -1033,6 +1036,8 @@ def main():
10331036

10341037
opt.flg_gpslnav = True
10351038

1039+
opt.useL1CB = args.useL1CB
1040+
10361041
# Start processing pool
10371042
#
10381043
with mp.Pool(processes=args.jobs) as pool:

receiver/decode_lgr.py

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
@author Rui Hirokawa
99
"""
1010

11+
from skyfield.framelib import itrs
12+
from skyfield.api import load
1113
import argparse
1214
from glob import glob
1315
import multiprocessing as mp
@@ -16,7 +18,8 @@
1618
from pathlib import Path
1719
import struct as st
1820
from crccheck.crc import Crc24LteA
19-
from cssrlib.gnss import uGNSS, uTYP, prn2sat, Obs, rSigRnx, gpst2time, uSIG, rCST, gtime_t
21+
from cssrlib.gnss import uGNSS, uTYP, prn2sat, Obs, rSigRnx, gpst2time, \
22+
uSIG, rCST, gtime_t, time2gpst
2023
from cssrlib.rawnav import rcvDec, rcvOpt
2124

2225

@@ -83,6 +86,11 @@ def __init__(self, opt=None, prefix='', gnss_t='GE'):
8386

8487
self.nav = navRec()
8588

89+
self.fn = open('lgr-nav.txt', 'w')
90+
91+
self.fn.write("# week, tow, nsat, x, y, z, vx, vy, vz, cb, cd, " +
92+
"ggto, sigp, sigv, sigt, pdop, hdop, vdop\n")
93+
8694
def sync(self, buff, k):
8795
return buff[k] == 0x71 # 'q'
8896

@@ -170,6 +178,18 @@ def decode_nav(self, buff, k=10):
170178
self.nav.std = np.array([stdp, stdv, stdt])
171179
self.nav.dops = np.array([gdop, pdop, hdop, vdop, tdop])
172180

181+
return self.nav
182+
183+
def output_nav(self, nav):
184+
f = self.fn
185+
week, tow = time2gpst(nav.t)
186+
f.write(f"{week:4d} {tow:6.1f} {nav.nsat:2d} ")
187+
f.write(f"{nav.pos[0]:11.1f} {nav.pos[1]:11.1f} {nav.pos[2]:11.1f} ")
188+
f.write(f"{nav.vel[0]:8.1f} {nav.vel[1]:8.1f} {nav.vel[2]:8.1f} ")
189+
f.write(f"{nav.clkb:10.1f} {nav.clkd:8.1f} {nav.ggto:4.1f} ")
190+
f.write(f"{nav.std[0]:8.1f} {nav.std[1]:8.1f} {nav.std[2]:8.1f} ")
191+
f.write(f"{nav.dops[1]:8.1f} {nav.dops[2]:8.1f} {nav.dops[3]:8.1f}\n")
192+
173193
def decode_obs(self, buff, k=10):
174194
""" decode RAW message """
175195
obs = Obs()
@@ -281,7 +301,8 @@ def decode(self, buff, len_, sys=[], prn=[]):
281301
self.re.rnx_obs_header(obs.time, self.fh_rnxobs)
282302
self.re.rnx_obs_body(obs, self.fh_rnxobs)
283303
elif mt == b'NAV':
284-
self.decode_nav(buff)
304+
nav = self.decode_nav(buff)
305+
self.output_nav(nav)
285306
elif mt == b'IQS':
286307
self.decode_iqs(buff)
287308
return 0
@@ -333,6 +354,61 @@ def decode(f, opt, args):
333354
lgrdec.file_close()
334355

335356

357+
def ecef_to_lunar_fixed(x_m, y_m, z_m, target_time=None):
358+
"""
359+
Converts ECEF (ITRS) coordinates to the Lunar-fixed coordinate system (Selenocentric).
360+
"""
361+
# 1. Load ephemeris and timescale data
362+
# DE421 is a standard JPL ephemeris; DE440 is a more recent alternative.
363+
eph = load('de440.bsp')
364+
earth = eph['earth']
365+
moon = eph['moon']
366+
ts = load.timescale()
367+
368+
if target_time is None:
369+
t = ts.now()
370+
else:
371+
t = target_time
372+
373+
# 2. Define ECEF coordinates as Earth-fixed (ITRS)
374+
# This creates a position object relative to the Earth's center in the ITRS frame.
375+
ecef_pos = itrs.at(t, x_m=x_m, y_m=y_m, z_m=z_m)
376+
377+
# 3. Get the point's position in the Inertial Coordinate System (ICRF)
378+
# Calling .at(t) on an ITRS position transforms it into the inertial ICRF frame.
379+
pos_icrf = ecef_pos
380+
381+
# 4. Get the Moon's center position in the ICRF frame
382+
moon_icrf = moon.at(t)
383+
384+
# 5. Calculate the relative vector from the Moon's center to the point
385+
# Relative Vector = Point Position (ICRF) - Moon Position (ICRF)
386+
relative_vec_icrf = pos_icrf - moon_icrf
387+
388+
# 6. Transform to the Lunar-fixed frame (considering rotation and libration)
389+
# Using the "Mean Earth/Polar Axis" (ME) frame via the moon_pa_de421 kernel.
390+
# Note: .frame_xyz() rotates the inertial vector into the specified body-fixed frame.
391+
lunar_fixed_pos = relative_vec_icrf.frame_xyz(load('moon_pa_de421.tf'))
392+
393+
# Retrieve the coordinates in meters
394+
x_l, y_l, z_l = lunar_fixed_pos.m
395+
396+
return x_l, y_l, z_l
397+
398+
399+
# --- Example Usage ---
400+
# Example: ECEF coordinates for a point on Earth (e.g., Tokyo area)
401+
x_ecef = -3957224.0
402+
y_ecef = 3310210.0
403+
z_ecef = 3737512.0
404+
405+
lx, ly, lz = ecef_to_lunar_fixed(x_ecef, y_ecef, z_ecef)
406+
407+
print(f"Time (UTC): {load.timescale().now().utc_strftime()}")
408+
print(f"Input ECEF (m): X={x_ecef}, Y={y_ecef}, Z={z_ecef}")
409+
print(f"Output Lunar Fixed (m): X={lx:.2f}, Y={ly:.2f}, Z={lz:.2f}")
410+
411+
336412
def main():
337413

338414
# Parse command line arguments

receiver/decode_rtcm.py

Lines changed: 83 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import os
1515
from pathlib import Path
1616

17-
from cssrlib.gnss import uGNSS, uTYP, rSigRnx, Obs, gtime_t, timediff
17+
from cssrlib.gnss import uGNSS, uTYP, rSigRnx, Obs, gtime_t, timediff, sat2prn
1818
from cssrlib.rawnav import rcvDec, rcvOpt
1919

2020
from cssrlib.rtcm import rtcm
@@ -41,11 +41,11 @@ def __init__(self, opt=None, prefix='', gnss_t='GECJ'):
4141

4242
if 'G' in gnss_t:
4343
self.sig_tab[uGNSS.GPS] = {
44-
uTYP.C: [rSigRnx('GC1C'), rSigRnx('GC1W'), rSigRnx('GC2W'),
44+
uTYP.C: [rSigRnx('GC1C'), rSigRnx('GC2W'),
4545
rSigRnx('GC2L'), rSigRnx('GC5Q'), rSigRnx('GC1L')],
46-
uTYP.L: [rSigRnx('GL1C'), rSigRnx('GL1W'), rSigRnx('GL2W'),
46+
uTYP.L: [rSigRnx('GL1C'), rSigRnx('GL2W'),
4747
rSigRnx('GL2L'), rSigRnx('GL5Q'), rSigRnx('GL1L')],
48-
uTYP.S: [rSigRnx('GS1C'), rSigRnx('GS1W'), rSigRnx('GS2W'),
48+
uTYP.S: [rSigRnx('GS1C'), rSigRnx('GS2W'),
4949
rSigRnx('GS2L'), rSigRnx('GS5Q'), rSigRnx('GS1L')],
5050
}
5151

@@ -108,16 +108,18 @@ def __init__(self, opt=None, prefix='', gnss_t='GECJ'):
108108

109109
self.rtcm = rtcm(foutname=foutname)
110110
self.time_p = gtime_t()
111-
self.obs = None
112111

113112
if opt is not None:
114113
self.init_param(opt=opt, prefix=prefix)
115114

116115
def add_obs(self, obs):
116+
sys = list(obs.sig)[0]
117+
if sys not in self.sig_tab:
118+
return
119+
117120
self.obs.sat = np.hstack((self.obs.sat, obs.sat))
118121
nsat = len(obs.sat)
119122

120-
sys = list(obs.sig)[0]
121123
if sys not in self.obs.sig:
122124
self.obs.sig[sys] = obs.sig[sys]
123125

@@ -132,14 +134,30 @@ def add_obs(self, obs):
132134
obs.L[np.isnan(obs.L)] = 0.0
133135

134136
for k, sig in enumerate(obs.sig[sys][uTYP.L]):
137+
135138
if sig in self.sig_tab[sys][uTYP.L]:
136139
idx = self.sig_tab[sys][uTYP.L].index(sig)
140+
137141
obs_.P[:, idx] = obs.P[:, k]
138142
obs_.L[:, idx] = obs.L[:, k]
139143
obs_.S[:, idx] = obs.S[:, k]
140144
obs_.D[:, idx] = obs.D[:, k]
141145
obs_.lli[:, idx] = obs.lli[:, k]
142146

147+
if self.useL1CB and sys == uGNSS.QZS: # QZS L1E -> L1C
148+
k = obs_.P[:, 1] != 0
149+
obs_.P[k, 0] = obs_.P[k, 1]
150+
obs_.L[k, 0] = obs_.L[k, 1]
151+
obs_.S[k, 0] = obs_.S[k, 1]
152+
obs_.D[k, 0] = obs_.D[k, 1]
153+
obs_.lli[k, 0] = obs_.lli[k, 1]
154+
155+
obs_.P[k, 1] = 0.0
156+
obs_.L[k, 1] = 0.0
157+
obs_.S[k, 1] = 0.0
158+
obs_.D[k, 1] = 0.0
159+
obs_.lli[k, 1] = 0
160+
143161
self.obs.P = np.vstack((self.obs.P, obs_.P))
144162
self.obs.L = np.vstack((self.obs.L, obs_.L))
145163
self.obs.S = np.vstack((self.obs.S, obs_.S))
@@ -167,10 +185,19 @@ def init_obs(self, time):
167185
self.obs.sat = np.empty(0, dtype=int)
168186
self.obs.sig = {}
169187

170-
def decode(self, buff, len_, sys=[], prn=[]):
188+
def decode(self, buff, len_, sys=[], prn=[], scanmode=False):
171189
""" decode RTCM binary messages """
172190

173-
_, obs, eph, geph, seph = self.rtcm.decode(buff, len_)
191+
_, obs, eph, geph, seph = self.rtcm.decode(buff, scanmode=scanmode)
192+
193+
if scanmode:
194+
self.re.anttype = self.rtcm.ant_desc
195+
self.re.rectype = self.rtcm.rcv_type
196+
self.re.rec = self.rtcm.rcv_serial
197+
self.re.recver = self.rtcm.firm_ver
198+
if self.rtcm.pos_arp is not None:
199+
self.re.pos = self.rtcm.pos_arp
200+
self.re.glo_bias = self.rtcm.glo_bias
174201

175202
if self.flg_rnxobs and obs is not None:
176203
self.time = obs.time
@@ -188,42 +215,36 @@ def decode(self, buff, len_, sys=[], prn=[]):
188215

189216
if not self.re.rnx_obs_header_sent:
190217
for sys in obs.sig:
191-
self.re.sig_tab[sys] = obs.sig[sys]
218+
if sys in self.sig_tab:
219+
self.re.sig_tab[sys] = obs.sig[sys]
192220

193221
self.add_obs(obs)
194222

195223
self.time_p = self.time
196224

197-
if eph is not None:
198-
self.re.rnx_nav_body(eph, self.fh_rnxnav)
199-
200-
if geph is not None:
201-
self.re.rnx_gnav_body(geph, self.fh_rnxnav)
202-
203-
if seph is not None:
204-
self.re.rnx_snav_body(seph, self.fh_rnxnav)
225+
if self.flg_rnxnav:
205226

227+
if eph is not None:
228+
sys, prn = sat2prn(eph.sat)
229+
if sys in self.sig_tab:
230+
self.re.rnx_nav_body(eph, self.fh_rnxnav)
206231

207-
def decode(f, opt, args):
208-
209-
print("Decoding {}".format(f))
232+
if geph is not None:
233+
sys, prn = sat2prn(geph.sat)
234+
if sys in self.sig_tab:
235+
self.re.rnx_gnav_body(geph, self.fh_rnxnav)
210236

211-
bdir, fname = os.path.split(f)
237+
if seph is not None:
238+
sys, prn = sat2prn(seph.sat)
239+
if sys in self.sig_tab:
240+
self.re.rnx_snav_body(seph, self.fh_rnxnav)
212241

213-
prefix = fname[4:].removesuffix('.rtcm3')+'_'
214-
prefix = str(Path(bdir) / prefix) if bdir else prefix
215-
rtcmdec = rtcmDec(opt=opt, prefix=prefix, gnss_t=args.gnss)
216-
rtcmdec.monlevel = 2
217242

218-
rtcmdec.rtcm.week = args.weekref
219-
220-
path = str(Path(bdir) / fname) if bdir else fname
221-
blen = os.path.getsize(path)
243+
def rtcm_decode(rtcmdec, path, blen, scanmode=False):
222244

223245
with open(path, 'rb') as f:
224246
msg = f.read(blen)
225247
maxlen = len(msg)-5
226-
# maxlen = 400000
227248
k = 0
228249
for _ in range(maxlen):
229250
if k > maxlen:
@@ -237,9 +258,29 @@ def decode(f, opt, args):
237258
continue
238259

239260
len_ = rtcmdec.rtcm.len+3
240-
rtcmdec.decode(msg[k:k+len_], len_)
261+
rtcmdec.decode(msg[k:k+len_], len_, scanmode=scanmode)
241262
k += rtcmdec.rtcm.dlen
242263

264+
265+
def decode(f, opt, args):
266+
267+
bdir, fname = os.path.split(f)
268+
269+
prefix = fname[4:].removesuffix('.rtcm3')+'_'
270+
prefix = str(Path(bdir) / prefix) if bdir else prefix
271+
rtcmdec = rtcmDec(opt=opt, prefix=prefix, gnss_t=args.gnss)
272+
rtcmdec.monlevel = 2
273+
274+
rtcmdec.rtcm.week = args.weekref
275+
276+
path = str(Path(bdir) / fname) if bdir else fname
277+
blen = os.path.getsize(path)
278+
279+
print(f"Pre-scanning {f}")
280+
rtcm_decode(rtcmdec, path, blen, True)
281+
print(f"Decoding {f}")
282+
rtcm_decode(rtcmdec, path, blen)
283+
243284
rtcmdec.file_close()
244285

245286
# python decode_rtcm.py ..\data\doy2025-298\CL07298j.rtc --weekref=2389
@@ -270,6 +311,9 @@ def main():
270311
parser.add_argument("-j", "--jobs", default=int(mp.cpu_count() / 2),
271312
type=int, help='Max. number of parallel processes')
272313

314+
parser.add_argument("--useL1CB", action='store_true',
315+
help="use L1C/B as like L1C/A for QZS")
316+
273317
# Retrieve all command line arguments
274318
#
275319
args = parser.parse_args()
@@ -278,10 +322,18 @@ def main():
278322

279323
opt.flg_rnxobs = True
280324
opt.flg_rnxnav = True
325+
opt.useL1CB = args.useL1CB
326+
327+
# args.weekref = 2397 # 2025/352
328+
# args.inpFileName = '..\data\doy2025-352\sept352a.rtc'
329+
# args.inpFileName = '../data/doy2025-352/tr92352a.rtc'
330+
# args.gnss = 'GJ'
331+
# opt.useL1CB = True
281332

282333
s = args.inpFileName
283334
opt.foutname = s[:s.rfind('.')]+'.log'
284335

336+
# decode(args.inpFileName, opt, args)
285337
# Start processing pool
286338
#
287339
with mp.Pool(processes=args.jobs) as pool:

0 commit comments

Comments
 (0)