From 1885e3e47ab5a8f5a8880baeddf168f683401f11 Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Tue, 4 Nov 2025 22:44:29 +0900 Subject: [PATCH 01/48] - fixed initializer of class. - added comment for classs/functions. --- src/cssrlib/gnss.py | 332 +++++++++++++++++++++++--------------------- 1 file changed, 171 insertions(+), 161 deletions(-) diff --git a/src/cssrlib/gnss.py b/src/cssrlib/gnss.py index 7945676..6a1c53a 100644 --- a/src/cssrlib/gnss.py +++ b/src/cssrlib/gnss.py @@ -16,30 +16,30 @@ class rCST(): """ class for constants """ - CLIGHT = 299792458.0 - MU_GPS = 3.9860050E14 - MU_GLO = 3.9860044E14 - MU_GAL = 3.986004418E14 - MU_BDS = 3.986004418E14 - J2_GLO = 1.0826257E-3 - GME = 3.986004415E+14 - GMS = 1.327124E+20 - GMM = 4.902801E+12 - OMGE = 7.2921151467E-5 - OMGE_GLO = 7.292115E-5 - OMGE_GAL = 7.2921151467E-5 - OMGE_BDS = 7.2921150E-5 - RE_WGS84 = 6378137.0 - FE_WGS84 = (1.0/298.257223563) - RE_GLO = 6378136.0 - AU = 149597870691.0 - D2R = 0.017453292519943295 - R2D = 57.29577951308232 - AS2R = D2R/3600.0 - DAY_SEC = 86400.0 - WEEK_SEC = 604800.0 - HALFWEEK_SEC = 302400.0 - CENTURY_SEC = DAY_SEC*36525.0 + CLIGHT = 299792458.0 # speed of light (m/s) + MU_GPS = 3.9860050E14 # gravitational constant for GPS + MU_GLO = 3.9860044E14 # gravitational constant for GLONASS + MU_GAL = 3.986004418E14 # gravitational constant for Galileo + MU_BDS = 3.986004418E14 # gravitational constant for BeiDou + J2_GLO = 1.0826257E-3 # J2 harmonic for GLONASS + GME = 3.986004415E+14 # [m^3/s^2] Earth gravitational constant + GMS = 1.327124E+20 # [m^3/s^2] Sun gravitational constant + GMM = 4.902801E+12 # [m^3/s^2] Moon gravitational constant + OMGE = 7.2921151467E-5 # [rad/s] Earth rotation rate + OMGE_GLO = 7.292115E-5 # [rad/s] Earth rotation rate for GLONASS + OMGE_GAL = 7.2921151467E-5 # [rad/s] Earth rotation rate for Galileo + OMGE_BDS = 7.2921150E-5 # [rad/s] Earth rotation rate for BeiDou + RE_WGS84 = 6378137.0 # Earth radius in WGS84 + FE_WGS84 = (1.0/298.257223563) # Earth flattening in WGS84 + RE_GLO = 6378136.0 # Earth radius for GLONASS + AU = 149597870691.0 # astronomical unit (m) + D2R = 0.017453292519943295 # deg to rad + R2D = 57.29577951308232 # rad to deg + AS2R = D2R/3600.0 # arc sec to rad + DAY_SEC = 86400.0 # seconds in one day + WEEK_SEC = 604800.0 # seconds in one week + HALFWEEK_SEC = 302400.0 # seconds in half week + CENTURY_SEC = DAY_SEC*36525.0 # seconds in one century PI = 3.1415926535898 HALFPI = 1.5707963267949 @@ -129,7 +129,7 @@ class rCST(): P2_60 = 8.673617379884035E-19 P2_66 = 1.355252715606880E-20 P2_68 = 3.388131789017201E-21 - SC2RAD = 3.1415926535898 + SC2RAD = 3.1415926535898 # semi-circle to radian class uGNSS(IntEnum): @@ -320,6 +320,7 @@ class uIonoModel(IntEnum): class rSigRnx(): + """ class for RINEX signal representation """ def __init__(self, *args, **kwargs): """ Constructor """ @@ -615,34 +616,41 @@ def __init__(self, time=0, sec=0.0): self.time = time self.sec = sec - def __gt__(self, other): + def __gt__(self, other): # greater than operator return self.time > other.time or \ (self.time == other.time and self.sec > other.sec) class STOParam(): """ System Time and UTC Office """ - sbas = 0 # SBAS ID - prm = [0, 0] # System time offset parameter - t_ot = None # reference epoch - t_t = 0.0 # transmission time of message (Time of week [sec]) - a = np.zeros(3) # a0, a1, a2 + + def __init__(self): + self.iod = 0 + self.sbas = 0 # SBAS ID + self.prm = np.zeros(2) # System time offset parameter + self.t_ot = None # reference epoch + self.a = np.zeros(3) # a0, a1, a2 class EOPParam(): """ Earth Orientation Parameter """ - prm = np.zeros(9) - # EOP parameters (xp,dxp,ddxp,yp,dyp,ddyp,dut1,ddut1,dddut1) - t_ot = None # reference epoch - t_t = 0.0 # transmission time of message (Time of week [sec]) + + def __init__(self): + self.iod = 0 + # EOP parameters (xp,dxp,ddxp,yp,dyp,ddyp,dut1,ddut1,dddut1) + self.prm = np.zeros(9) + self.t_ot = None # reference epoch + self.t_t = 0.0 # transmission time of message (Time of week [sec]) class IONParam(): """ Ionospheric delay model Parameter """ - iod = 0 - prm = np.zeros(9) # ION parameters - t_tm = None # transmission time - region = None + + def __init__(self): + self.iod = 0 + self.prm = np.zeros(9) # ION parameters + self.t_tm = None # transmission time + self.region = None class Obs(): @@ -661,147 +669,145 @@ def __init__(self): class Eph(): """ class to define ephemeris """ - sat = 0 - iode = 0 - iodc = 0 - af0 = 0.0 - af1 = 0.0 - af2 = 0.0 - week = 0 - toc = 0 - toe = 0 - tot = 0 - wn_op = 0 - top = 0 - crs = 0.0 - crc = 0.0 - cus = 0.0 - cus = 0.0 - cis = 0.0 - cic = 0.0 - e = 0.0 - i0 = 0.0 - A = 0.0 - Adot = 0.0 - deln = 0.0 - delnd = 0.0 - M0 = 0.0 - OMG0 = 0.0 - OMGd = 0.0 - omg = 0.0 - idot = 0.0 - tgd = 0.0 - tgd_b = 0.0 - tgd_c = 0.0 - sva = 0 - svh = 0 - fit = 0 - toes = 0 - tops = 0 - l2p = 0 - sattype = 0 - sismai = 0 - code = 0 - urai = None - sisai = None - isc = None - integ = 0 - # 0:LNAV,INAV,D1/D2, 1:CNAV/CNAV1/FNAV, 2: CNAV2, 3: CNAV3, 4:FDMA, 5:SBAS - mode = 0 def __init__(self, sat=0): self.sat = sat + self.iode = 0 + self.iodc = 0 + self.af0 = 0.0 + self.af1 = 0.0 + self.af2 = 0.0 + self.week = 0 + self.toc = 0 + self.toe = 0 + self.tot = 0 + self.wn_op = 0 + self.top = 0 + self.crs = 0.0 + self.crc = 0.0 + self.cus = 0.0 + self.cus = 0.0 + self.cis = 0.0 + self.cic = 0.0 + self.e = 0.0 + self.i0 = 0.0 + self.A = 0.0 + self.Adot = 0.0 + self.deln = 0.0 + self.delnd = 0.0 + self.M0 = 0.0 + self.OMG0 = 0.0 + self.OMGd = 0.0 + self.omg = 0.0 + self.idot = 0.0 + self.tgd = 0.0 + self.tgd_b = 0.0 + self.tgd_c = 0.0 + self.sva = 0 + self.svh = 0 + self.fit = 0 + self.toes = 0 + self.tops = 0 + self.l2p = 0 + self.sattype = 0 + self.sismai = 0 + self.code = 0 + self.urai = None + self.sisai = None + self.isc = None + self.integ = 0 + # 0:LNAV,INAV,D1/D2, 1:CNAV/CNAV1/FNAV, 2: CNAV2, + # 3: CNAV3, 4:FDMA, 5:SBAS + self.mode = 0 class Geph(): """ class to define GLONASS ephemeris """ - sat = 0 - iode = 0 # IODE: 0-6bit of tb field - frq = 0 - svh = 0 - sva = 0 - age = 0.0 - toe = gtime_t() - toes = 0.0 - tof = gtime_t() - pos = np.zeros(3) - vel = np.zeros(3) - acc = np.zeros(3) - taun = 0.0 # SV clock bias [s] - gamn = 0.0 # SV clock drift [s/s] - beta = 0.0 # SV clock drift rate [s/s^2] - dtaun = 0.0 # delta between L1 and L2 [s] - mode = 0 - status = 0 # data validity - flag = 0 - - tau_c = 0.0 # GLONASS time scale correction to UTC(SU) time - dtau_c = 0.0 - tau_gps = 0.0 # correction to GPS time relative to GLONASS time - - # for CDMA - urai = np.zeros(2, dtype=int) - dpos = np.zeros(3) - - psi = 0.0 # yaw angle [rad] - sn = 0 # sign flag - win = 0.0 # angular rate [rad/s] - dw = 0.0 # angular accel [rad/s^2] - wmax = 0.0 # max angular rate[rad/s] - aode = 0 - aodc = 0 # age of data orbit/clock [days] - tin = 0.0 - tau1 = 0.0 - tau2 = 0.0 - - src = 0 # source flags (b0-1: Rt, b2-3: Re) - sattype = 0 # 0 - M(L3), 1 - K1(L3), 3 - K1(L2/L3), 2 - K2 (L1/L2/L3) - isc = np.zeros(3) # 0: ISC_L1OC, 1: ISC_L2OC, 2: ISC_L3OC def __init__(self, sat=0): self.sat = sat + self.iode = 0 # IODE: 0-6bit of tb field + self.frq = 0 + self.svh = 0 + self.sva = 0 + self.age = 0.0 + self.toe = gtime_t() + self.toes = 0.0 + self.tof = gtime_t() + self.pos = np.zeros(3) + self.vel = np.zeros(3) + self.acc = np.zeros(3) + self.taun = 0.0 # SV clock bias [s] + self.gamn = 0.0 # SV clock drift [s/s] + self.beta = 0.0 # SV clock drift rate [s/s^2] + self.dtaun = 0.0 # delta between L1 and L2 [s] + self.mode = 0 + self.status = 0 # data validity + self.flag = 0 + + self.tau_c = 0.0 # GLONASS time scale correction to UTC(SU) time + self.dtau_c = 0.0 + self.tau_gps = 0.0 # correction to GPS time relative to GLONASS time + + # for CDMA + self.urai = np.zeros(2, dtype=int) + self.dpos = np.zeros(3) + + self.psi = 0.0 # yaw angle [rad] + self.sn = 0 # sign flag + self.win = 0.0 # angular rate [rad/s] + self.dw = 0.0 # angular accel [rad/s^2] + self.wmax = 0.0 # max angular rate[rad/s] + self.aode = 0 + self.aodc = 0 # age of data orbit/clock [days] + self.tin = 0.0 + self.tau1 = 0.0 + self.tau2 = 0.0 + + self.src = 0 # source flags (b0-1: Rt, b2-3: Re) + # 0 - M(L3), 1 - K1(L3), 3 - K1(L2/L3), 2 - K2 (L1/L2/L3) + self.sattype = 0 + self.isc = np.zeros(3) # 0: ISC_L1OC, 1: ISC_L2OC, 2: ISC_L3OC class Seph(): """ class to define SBAS ephemeris """ - sat = 0 - iodn = 0 - t0 = gtime_t() - tof = gtime_t() - svh = 0 - sva = 0 - pos = np.zeros(3) - vel = np.zeros(3) - acc = np.zeros(3) - af0 = 0.0 - af1 = 0.0 - mode = 0 def __init__(self, sat=0): self.sat = sat + self.iodn = 0 + self.t0 = gtime_t() + self.tof = gtime_t() + self.svh = 0 + self.sva = 0 + self.pos = np.zeros(3) + self.vel = np.zeros(3) + self.acc = np.zeros(3) + self.af0 = 0.0 + self.af1 = 0.0 + self.mode = 0 class Alm(): """ class to define almanac """ - sat = 0 - af0 = 0.0 - af1 = 0.0 - toa = gtime_t() - toas = 0.0 - week = 0 - e = 0.0 - i0 = 0.0 - A = 0.0 - M0 = 0.0 - OMG0 = 0.0 - OMGd = 0.0 - omg = 0.0 - svh = 0 - sattype = 0 - mode = 0 def __init__(self, sat=0): self.sat = sat + self.af0 = 0.0 + self.af1 = 0.0 + self.toa = gtime_t() + self.toas = 0.0 + self.week = 0 + self.e = 0.0 + self.i0 = 0.0 + self.A = 0.0 + self.M0 = 0.0 + self.OMG0 = 0.0 + self.OMGd = 0.0 + self.omg = 0.0 + self.svh = 0 + self.sattype = 0 + self.mode = 0 class Nav(): @@ -922,6 +928,7 @@ def epoch2time(ep): return time +# leap seconds [y,m,d,h,m,s,utc-gpst] leaps_ = [[2017, 1, 1, 0, 0, 0, -18], [2015, 7, 1, 0, 0, 0, -17], [2012, 7, 1, 0, 0, 0, -16], @@ -951,6 +958,7 @@ def timeget(): def glo2time(tref: gtime_t, tod): + """ convert to UTC time from GLONASS time """ time = timeadd(gpst2utc(tref), 10800.0) week, tow = time2gpst(time) tod_p = tow % 86400.0 @@ -964,6 +972,7 @@ def glo2time(tref: gtime_t, tod): def gpst2utc(t: gtime_t): + """ convert from gps-time to utc """ for i in range(len(leaps_)): tu = timeadd(t, leaps_[i][6]) if timediff(tu, epoch2time(leaps_[i])) >= 0.0: @@ -972,6 +981,7 @@ def gpst2utc(t: gtime_t): def utc2gpst(t: gtime_t): + """ convert from utc to gps-time """ for i in range(len(leaps_)): if timediff(t, epoch2time(leaps_[i])) >= 0.0: return timeadd(t, -leaps_[i][6]) @@ -1444,9 +1454,9 @@ def tropmapf(t, pos, el, model=uTropoModel.SAAST): Tropo mapping function """ - if model == uTropoModel.SAAST: + if model == uTropoModel.SAAST: # Saastamoinen model return tropmapfNiell(t, pos, el) - elif model == uTropoModel.HOPF: + elif model == uTropoModel.HOPF: # Hopfield model return tropmapfHpf(el) else: return 0 @@ -1457,9 +1467,9 @@ def tropmodel(t, pos, el=np.pi/2, humi=0.7, model=uTropoModel.SAAST): Tropospheric delay model """ - if model == uTropoModel.SAAST: + if model == uTropoModel.SAAST: # Saastamoinen model return tropmodelSaast(t, pos, el, humi) - elif model == uTropoModel.HOPF: + elif model == uTropoModel.HOPF: # Hopfield model return tropmodelHpf() else: return 0 @@ -1558,7 +1568,7 @@ def tropmodelHpf(): def copy_buff(src, dst, ofst_s=0, ofst_d=0, blen=0): - """ copy bit-wise buffer copy """ + """ bit-wise buffer copy """ b = blen//32 r = blen-b*32 for k in range(b): From ee866f1fc76e05199b7ff653b0ee57ca87ba9563 Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sun, 9 Nov 2025 19:19:34 +0900 Subject: [PATCH 02/48] - added utils.py to include common processing routines. --- src/cssrlib/cssrlib.py | 10 +- src/cssrlib/dgps.py | 27 ++- src/cssrlib/ephemeris.py | 8 +- src/cssrlib/gnss.py | 138 ++++++++--- src/cssrlib/pntpos.py | 50 +++- src/cssrlib/sbas.py | 184 ++++++++++----- src/cssrlib/utils.py | 187 +++++++++++++++ tutorials/auth.ipynb | 482 ++++++++++++++++++++++++++++++++++++++- 8 files changed, 977 insertions(+), 109 deletions(-) create mode 100644 src/cssrlib/utils.py diff --git a/src/cssrlib/cssrlib.py b/src/cssrlib/cssrlib.py index 2a53dfe..54961be 100644 --- a/src/cssrlib/cssrlib.py +++ b/src/cssrlib/cssrlib.py @@ -14,7 +14,7 @@ from enum import IntEnum from cssrlib.gnss import gpst2time, rCST, prn2sat, uGNSS, gtime_t, rSigRnx from cssrlib.gnss import uSIG, uTYP, sat2prn, time2str, sat2id, timediff -from cssrlib.gnss import copy_buff +from cssrlib.gnss import copy_buff, time2gpst class sCSSRTYPE(IntEnum): @@ -447,6 +447,14 @@ def ssig2rsig(self, sys: sGNSS, utyp: uTYP, ssig): usig_tbl = usig_tbl_[sys] return rSigRnx(sys, utyp, usig_tbl[ssig]) + def set_time(self, t): + """ set observation time (week, tow) """ + self.time = t + week, tow = time2gpst(t) + self.week = week + self.tow0 = tow//86400*86400 + return week, tow + def sval(self, u, n, scl): """ calculate signed value based on n-bit int, lsb """ invalid = -2**(n-1) diff --git a/src/cssrlib/dgps.py b/src/cssrlib/dgps.py index d3e0ddf..e7b9179 100644 --- a/src/cssrlib/dgps.py +++ b/src/cssrlib/dgps.py @@ -10,7 +10,7 @@ import bitstruct as bs from cssrlib.cssrlib import cssr, sCSSRTYPE, prn2sat, sCType from cssrlib.gnss import uGNSS, rCST, timediff, time2str, \ - ecef2pos + ecef2pos, sat2prn, timediff from cssrlib.ewss import jmaDec, camfDec @@ -18,13 +18,26 @@ def vardgps(el, nav): """ measurement error varianvce for DGPS [1] """ c_el = np.cos(el) + el_d = el*rCST.R2D pos = ecef2pos(nav.x[0:3]) + + # multipath error component for single frequency + s_mp_sf = 0.13+0.53*np.exp(-el_d/10.0) + + # obliquity factor Fpp = 1/np.sqrt(1-(rCST.RE_WGS84*c_el/(rCST.RE_WGS84+pos[2]))**2) + # ionospheric error component s_iono = Fpp*0.004*(nav.baseline+2*100*0.07) - s_mp = 0.13+0.53*np.exp(-el/0.1745) - v_air = 0.11**2 + s_mp**2 - v_pr = (0.16+0.107*np.exp(-el/0.271))**2/1+0.08**2 + + s_noise = 0.11 # receiver noise [m] + # receiver noise error and multipath error component [m^2] + v_air = s_noise**2 + s_mp_sf**2 + + # error component of pseudorange correction value [m^2] + v_pr = (0.16+1.07*np.exp(-el_d/15.5))**2/1+0.08**2 + v_sig = v_pr + v_air + s_iono**2 + return v_sig @@ -79,6 +92,8 @@ def __init__(self, foutname=None, year=0): self.posr[13, :] = [24.370, 124.130, 100] self.posr[14, :] = [27.090, 142.190, 100] + self.posr[:, 0:2] *= rCST.D2R + self.mlen = {uGNSS.GPS: 64, uGNSS.QZS: 9, uGNSS.GLO: 36, uGNSS.GAL: 36, uGNSS.BDS: 36} self.prn_ofst = {uGNSS.GPS: 0, uGNSS.QZS: 192, uGNSS.GLO: 0, @@ -129,6 +144,8 @@ def decode_mt47(self, msg, i): self.posr[code, 1] = lon*0.005+115 self.posr[code, 2] = alt*50.0-100 + self.posr[code, 0:2] *= rCST.D2R + i += 42 return 0 @@ -275,7 +292,7 @@ def get_station(self, r): idx = np.where(self.posr[:, 0]**2+self.posr[:, 1]**2 > 0)[0] if len(idx) == 0: return -1, -1 - dpos = np.deg2rad(self.posr[idx, 0:2])-pos[0:2] + dpos = self.posr[idx, 0:2]-pos[0:2] rng = np.sqrt(dpos[:, 0]**2+(dpos[:, 1]*c_lat)**2)*rCST.RE_WGS84 kmin = np.argmin(rng) refid = idx[kmin] diff --git a/src/cssrlib/ephemeris.py b/src/cssrlib/ephemeris.py index 9624454..5a1c699 100644 --- a/src/cssrlib/ephemeris.py +++ b/src/cssrlib/ephemeris.py @@ -500,8 +500,8 @@ def satposs(obs, nav, cs=None, orb=None): contains GNSS measurements nav : Nav() contains coarse satellite orbit and clock offset information - cs : cssr_has() - contains precise SSR corrections for satellite orbit and clock offset + cs : cssr() or derivatives + contains SSR corrections for satellite orbit and clock offset obs : peph() contains precise satellite orbit and clock offset information @@ -707,6 +707,10 @@ def satposs(obs, nav, cs=None, orb=None): if cs.cssrmode in (sc.PVS_PPP, sc.SBAS_L1, sc.SBAS_L5, sc.DGPS) and sys == uGNSS.GPS: dts[i] -= eph.tgd + # eph_c = findeph(nav.eph, t, sat, mode=1) # L5 CNAV + # if eph_c is not None: + # gam15 = (154/115)**2 + # dts[i] += (eph_c.isc[3]-gam15*eph_c.isc[0])/(1-gam15) ers = vnorm(rs[i, :]-nav.x[0: 3]) dorb_ = -ers@dorb_e diff --git a/src/cssrlib/gnss.py b/src/cssrlib/gnss.py index 6a1c53a..7495bd8 100644 --- a/src/cssrlib/gnss.py +++ b/src/cssrlib/gnss.py @@ -7,7 +7,8 @@ from math import floor, sin, cos, sqrt, asin, atan2, fabs, tan import numpy as np from datetime import datetime, timezone -import bitstruct.c as bs +import bitstruct as bs +import yaml gpst0 = [1980, 1, 6, 0, 0, 0] # GPS system time reference gst0 = [1999, 8, 22, 0, 0, 0] # Galileo system time reference @@ -16,30 +17,30 @@ class rCST(): """ class for constants """ - CLIGHT = 299792458.0 # speed of light (m/s) + CLIGHT = 299792458.0 # speed of light (m/s) MU_GPS = 3.9860050E14 # gravitational constant for GPS - MU_GLO = 3.9860044E14 # gravitational constant for GLONASS - MU_GAL = 3.986004418E14 # gravitational constant for Galileo - MU_BDS = 3.986004418E14 # gravitational constant for BeiDou - J2_GLO = 1.0826257E-3 # J2 harmonic for GLONASS - GME = 3.986004415E+14 # [m^3/s^2] Earth gravitational constant - GMS = 1.327124E+20 # [m^3/s^2] Sun gravitational constant - GMM = 4.902801E+12 # [m^3/s^2] Moon gravitational constant - OMGE = 7.2921151467E-5 # [rad/s] Earth rotation rate - OMGE_GLO = 7.292115E-5 # [rad/s] Earth rotation rate for GLONASS - OMGE_GAL = 7.2921151467E-5 # [rad/s] Earth rotation rate for Galileo - OMGE_BDS = 7.2921150E-5 # [rad/s] Earth rotation rate for BeiDou + MU_GLO = 3.9860044E14 # gravitational constant for GLONASS + MU_GAL = 3.986004418E14 # gravitational constant for Galileo + MU_BDS = 3.986004418E14 # gravitational constant for BeiDou + J2_GLO = 1.0826257E-3 # J2 harmonic for GLONASS + GME = 3.986004415E+14 # [m^3/s^2] Earth gravitational constant + GMS = 1.327124E+20 # [m^3/s^2] Sun gravitational constant + GMM = 4.902801E+12 # [m^3/s^2] Moon gravitational constant + OMGE = 7.2921151467E-5 # [rad/s] Earth rotation rate + OMGE_GLO = 7.292115E-5 # [rad/s] Earth rotation rate for GLONASS + OMGE_GAL = 7.2921151467E-5 # [rad/s] Earth rotation rate for Galileo + OMGE_BDS = 7.2921150E-5 # [rad/s] Earth rotation rate for BeiDou RE_WGS84 = 6378137.0 # Earth radius in WGS84 - FE_WGS84 = (1.0/298.257223563) # Earth flattening in WGS84 + FE_WGS84 = (1.0/298.257223563) # Earth flattening in WGS84 RE_GLO = 6378136.0 # Earth radius for GLONASS - AU = 149597870691.0 # astronomical unit (m) - D2R = 0.017453292519943295 # deg to rad - R2D = 57.29577951308232 # rad to deg + AU = 149597870691.0 # astronomical unit (m) + D2R = 0.017453292519943295 # deg to rad + R2D = 57.29577951308232 # rad to deg AS2R = D2R/3600.0 # arc sec to rad DAY_SEC = 86400.0 # seconds in one day - WEEK_SEC = 604800.0 # seconds in one week - HALFWEEK_SEC = 302400.0 # seconds in half week - CENTURY_SEC = DAY_SEC*36525.0 # seconds in one century + WEEK_SEC = 604800.0 # seconds in one week + HALFWEEK_SEC = 302400.0 # seconds in half week + CENTURY_SEC = DAY_SEC*36525.0 # seconds in one century PI = 3.1415926535898 HALFPI = 1.5707963267949 @@ -129,7 +130,7 @@ class rCST(): P2_60 = 8.673617379884035E-19 P2_66 = 1.355252715606880E-20 P2_68 = 3.388131789017201E-21 - SC2RAD = 3.1415926535898 # semi-circle to radian + SC2RAD = 3.1415926535898 # semi-circle to radian class uGNSS(IntEnum): @@ -305,6 +306,7 @@ class uTropoModel(IntEnum): NONE = -1 SAAST = 0 HOPF = 1 + SBAS = 2 class uIonoModel(IntEnum): @@ -616,7 +618,7 @@ def __init__(self, time=0, sec=0.0): self.time = time self.sec = sec - def __gt__(self, other): # greater than operator + def __gt__(self, other): # greater than operator return self.time > other.time or \ (self.time == other.time and self.sec > other.sec) @@ -845,6 +847,8 @@ def __init__(self, nf=2): # 3: SSR-CG, 4: PREC self.rmode = 0 # 0: IF not applied, 1: IF for L1/L2, 2: IF for L1/L5 + self.cssrmode = 0 + # 0:float-ppp,1:continuous,2:instantaneous,3:fix-and-hold self.armode = 0 self.thresar = 3.0 # AR acceptance threshold @@ -1441,10 +1445,11 @@ def satazel(pos, e): def interpc(coef, lat): """ linear interpolation (lat step=15) """ i = int(lat/15.0) + m = coef.shape[1]-1 if i < 1: return coef[:, 0] - if i > 4: - return coef[:, 4] + if i > m: + return coef[:, m] d = lat/15.0-i return coef[:, i-1]*(1.0-d)+coef[:, i]*d @@ -1454,10 +1459,12 @@ def tropmapf(t, pos, el, model=uTropoModel.SAAST): Tropo mapping function """ - if model == uTropoModel.SAAST: # Saastamoinen model + if model == uTropoModel.SAAST: # Saastamoinen model return tropmapfNiell(t, pos, el) - elif model == uTropoModel.HOPF: # Hopfield model + elif model == uTropoModel.HOPF: # Hopfield model return tropmapfHpf(el) + elif model == uTropoModel.SBAS: # SBAS model + return tropmapfSBAS(el) else: return 0 @@ -1467,10 +1474,12 @@ def tropmodel(t, pos, el=np.pi/2, humi=0.7, model=uTropoModel.SAAST): Tropospheric delay model """ - if model == uTropoModel.SAAST: # Saastamoinen model + if model == uTropoModel.SAAST: # Saastamoinen model return tropmodelSaast(t, pos, el, humi) - elif model == uTropoModel.HOPF: # Hopfield model + elif model == uTropoModel.HOPF: # Hopfield model return tropmodelHpf() + elif model == uTropoModel.SBAS: # SBAS model + return tropmodelSBAS(t, pos, el) else: return 0 @@ -1537,9 +1546,9 @@ def meteoHpf(): """ Hopfield atmosphere model """ - pres = 1010.0 - temp = 291.1 - e = 10.4 + pres = 1010.0 # pressure [hPa] + temp = 291.1 # temperature [K] + e = 10.4 # water vapor pressure [hPa] return pres, temp, e @@ -1551,7 +1560,7 @@ def tropmapfHpf(el): mapfh = 1.0/(np.sin(np.sqrt(el**2+(np.pi/72.0)**2))) mapfw = 1.0/(np.sin(np.sqrt(el**2+(np.pi/120.0)**2))) - return mapfh, mapfw, + return mapfh, mapfw def tropmodelHpf(): @@ -1567,6 +1576,65 @@ def tropmodelHpf(): return trop_dry, trop_wet, None +def tropmapfSBAS(el): + """ + Tropospheric mapping functions for Hopfield model + """ + + s_el = np.sin(el) + mf = 1.001/np.sqrt(0.002001+s_el**2) + + return mf, mf + + +def tropmodelSBAS(t, pos, el=np.pi/2): + """ SBAS tropospheric delay model """ + + # average of P[mbar], T[K], e[mbar], beta[K/m], lam + tbl_a = np.array([ + [1013.25, 299.65, 26.31, 6.30e-3, 2.77], # 15deg or less + [1017.25, 294.15, 21.79, 6.05e-3, 3.15], # 30deg + [1015.75, 283.15, 11.66, 5.58e-3, 2.57], # 45deg + [1011.75, 272.15, 6.78, 5.39e-3, 1.81], # 60deg + [1013.00, 263.65, 4.11, 4.53e-3, 1.55], # 75deg or greater + ]) + + # seasonal variation of P[mbar], T[K], e[mbar], beta[K/m], lam + tbl_v = np.array([ + [0.00, 0.00, 0.00, 0.00e-3, 0.00], + [-3.75, 7.00, 8.85, 0.25e-3, 0.33], + [-2.25, 11.00, 7.24, 0.32e-3, 0.46], + [-1.75, 15.00, 5.36, 0.81e-3, 0.74], + [-0.50, 14.50, 3.39, 0.62e-3, 0.30], + ]) + + lat = pos[0]*rCST.R2D + hgt = pos[2] + dmin = 28 if lat >= 0 else 211 + y = (time2doy(t)-dmin)/365.25 + cosy = np.cos(2.0*np.pi*y) + + c = interpc(np.c_[tbl_a, tbl_v].T, np.abs(lat)) + pres, temp, e, beta, lam = c[0:5]-c[5:10]*cosy + + Rd, gm = 287.054, 9.784 + # zero-altitude zenith delay term + z_hyd = 77.604e-6*Rd*pres/gm + z_wet = 0.382*Rd*e/((gm*(lam+1.0)-beta*Rd)*temp) + + g = 9.80665 + d_hyd = np.pow(1-beta*hgt/temp, g/(Rd*beta))*z_hyd + d_wet = np.pow(1-beta*hgt/temp, (lam+1)*g/(Rd*beta)-1)*z_wet + + # mapping function + s_el = np.sin(el) + mf = 1.001/np.sqrt(0.002001+s_el**2) + + trop_hyd, trop_wet = d_hyd*mf, d_wet*mf + + return trop_hyd, trop_wet, None + + def copy_buff(src, dst, ofst_s=0, ofst_d=0, blen=0): """ bit-wise buffer copy """ b = blen//32 @@ -1578,3 +1646,9 @@ def copy_buff(src, dst, ofst_s=0, ofst_d=0, blen=0): fmt = 'u'+str(r) d = bs.unpack_from(fmt, src, b*32+ofst_s)[0] bs.pack_into(fmt, dst, b*32+ofst_d, d) + + +def load_config(file_path): + with open(file_path, 'r') as file: + config = yaml.safe_load(file) + return config diff --git a/src/cssrlib/pntpos.py b/src/cssrlib/pntpos.py index 0e2edb9..e924c3a 100644 --- a/src/cssrlib/pntpos.py +++ b/src/cssrlib/pntpos.py @@ -4,11 +4,11 @@ import numpy as np from cssrlib.gnss import rCST, ecef2pos, geodist, satazel, \ tropmodel, tropmapf, sat2prn, uGNSS, uTropoModel, uIonoModel, \ - timediff, time2gpst, dops, uTYP, Obs, time2str + timediff, time2gpst, dops, uTYP, Obs, time2str, pos2ecef from cssrlib.cssrlib import sCSSRTYPE from cssrlib.ephemeris import satposs from cssrlib.pppssr import pppos -from cssrlib.sbas import ionoSBAS +from cssrlib.sbas import ionoSBAS, varsbas from cssrlib.dgps import vardgps from math import sin, cos @@ -73,9 +73,12 @@ def __init__(self, nav, pos0=np.zeros(3), logfile=None, trop_opt=0, self.Ps_ = {} self.cs_t0 = {} + self.nav.cssrmode = sCSSRTYPE.STDPOS + # Select tropospheric model # - self.nav.trpModel = uTropoModel.SAAST + # self.nav.trpModel = uTropoModel.SAAST + self.nav.trpModel = uTropoModel.SBAS # Select iono model # @@ -129,6 +132,8 @@ def __init__(self, nav, pos0=np.zeros(3), logfile=None, trop_opt=0, self.nav.elmin = np.deg2rad(10.0) + self.nav.obad = None + self.nsat = 0 self.dop = None @@ -201,9 +206,12 @@ def csmooth(self, obs: Obs, sat, Pm, Lm, ns=100, dt_th=1, cs_th=10): self.cs_t0[sat] = obs.t return self.Ps_[sat] - def varerr(self, nav, el, f): + def varerr(self, nav, el, f, e=None, sat=None): """ variation of measurement """ if nav.smode == 2: # DGPS + # if nav.cssrmode in [sCSSRTYPE.SBAS_L1, sCSSRTYPE.SBAS_L5]: + # v_sig = varsbas(el, nav, e, sat) + # else: v_sig = vardgps(el, nav) else: s_el = max(np.sin(el), 0.1*rCST.D2R) @@ -269,6 +277,15 @@ def zdres(self, obs, cs, bsx, rs, vs, dts, x, rtype=1): trop_hs, trop_wet, _ = tropmodel(obs.t, pos, model=self.nav.trpModel) + if cs.cssrmode == sCSSRTYPE.DGPS: + trop_hs, trop_wet, _ = tropmodel(obs.t, pos, + model=self.nav.trpModel) + posr = cs.posr[cs.inet-1, :] + rr_r = pos2ecef(posr) + + trop_hs_r, trop_wet_r, _ = tropmodel(obs.t, posr, + model=self.nav.trpModel) + for i in range(n): sat = obs.sat[i] @@ -297,7 +314,21 @@ def zdres(self, obs, cs, bsx, rs, vs, dts, x, rtype=1): # trop = mapfh*trop_hs + mapfw*trop_wet else: - trop = 0.0 + # Differential Tropospheric Delay Correction (DTDC) + if cs.cssrmode == sCSSRTYPE.DGPS: + _, e_ = geodist(rs[i, :], rr_r) + _, el_r = satazel(posr, e_) + + mapfh, mapfw = tropmapf(obs.t, pos, el[i], + model=self.nav.trpModel) + mapfh_r, mapfw_r = tropmapf(obs.t, posr, el_r, + model=self.nav.trpModel) + + trop_r = mapfh*trop_hs + mapfw*trop_wet + trop_b = mapfh_r*trop_hs_r + mapfw_r*trop_wet_r + trop = trop_r - trop_b + else: + trop = 0.0 if self.nav.iono_opt == 0: # use model # Ionospheric delay @@ -409,7 +440,8 @@ def sdres(self, obs, x, y, e, sat, el): H[nv, 0:3] = -e[j, :] H[nv, self.ICB()] = 1.0 - Rj[nv] = self.varerr(self.nav, el[j], f) + Rj[nv] = self.varerr(self.nav, el[j], f, e=e[j, :], + sat=sat[j]) nb[b] += 1 # counter for single-differences per signal nv += 1 # counter for single-difference observations @@ -444,6 +476,12 @@ def process(self, obs, cs=None, orb=None, bsx=None, obsb=None): self.nav.smode = 2 # DGPS self.nav.baseline = cs.set_dgps_corr(self.nav.x[0:3]) + if cs is not None and cs.cssrmode in \ + [sCSSRTYPE.SBAS_L1, sCSSRTYPE.SBAS_L5]: + self.nav.obad = cs.obad + self.nav.obad.t0 = cs.lc[0].t0 + self.nav.cssrmode = cs.cssrmode + # GNSS satellite positions, velocities and clock offsets # for all satellite in RINEX observations # diff --git a/src/cssrlib/sbas.py b/src/cssrlib/sbas.py index 712be5c..005b3c8 100644 --- a/src/cssrlib/sbas.py +++ b/src/cssrlib/sbas.py @@ -1,6 +1,18 @@ """ SBAS correction data decoder + [1] DO-229F, Minimum Operational Performance Standards for Global + Positioning System/Satellite-Based Augmentation System Airborne + Equipment, Revision F, RTCA, 2020 + + [2] ED-259, Minimum Operational Performance Standards for Galileo – + Global Positioning System –Satellite-Based Augmentation System + Airborne Equipment, EUROCAE, 2019 + + [3] DO-401, Minimum Operational Performance Standards (MOPS) + for Dual-Frequency Multi-Constellation Satellite-Based Augmentation + System Airborne Equipment, RTCA, 2023 + """ import numpy as np @@ -8,50 +20,62 @@ from cssrlib.cssrlib import cssr, sCSSR, sCSSRTYPE, prn2sat, sCType from cssrlib.cssrlib import sat2id from cssrlib.gnss import uGNSS, rCST, timediff, time2str, \ - time2gpst, Seph, Eph, Alm, tod2tow, gtime_t, ionppp + time2gpst, Seph, Eph, Alm, tod2tow, gtime_t, ionppp, sat2prn MAXBAND = 11 class Salm(): """ class to define SBAS almanac """ - sat = 0 - t0 = gtime_t() - svh = 0 - pos = np.zeros(3) - vel = np.zeros(3) - mode = 0 def __init__(self, sat=0): self.sat = sat + self.t0 = gtime_t() + self.svh = 0 + self.pos = np.zeros(3) + self.vel = np.zeros(3) + self.mode = 0 class SBASService(): """ structure SBAS service information for MT27 """ - iods = -1 - nmsg = 0 - snum = 0 - nreg = 0 - priority = 0 - dUDRE_in = 0 - dUDRE_out = 0 - lat1 = np.zeros(5) - lon1 = np.zeros(5) - lat2 = np.zeros(5) - lon2 = np.zeros(5) - shape = 0 + + def __init__(self): + self.iods = -1 + self.nmsg = 0 + self.snum = 0 + self.nreg = 0 + self.priority = 0 + self.dUDRE_in = 0 + self.dUDRE_out = 0 + self.lat1 = np.zeros(5) + self.lon1 = np.zeros(5) + self.lat2 = np.zeros(5) + self.lon2 = np.zeros(5) + self.shape = 0 class paramOBAD(): - tid = 0 - dt_mt32 = 0 - dt_mt39 = 0 - Cer = 0 - Ccov = 0 - Icorr = np.zeros(6) - Ccorr = np.zeros(6) - Rcorr = np.zeros(6) - dfrei_tbl = np.zeros(16) + """ Old But Active Data (OBAD) parameters """ + + def __init__(self): + self.tid = 0 + self.dt_mt32 = 0 + self.dt_mt39 = 0 + self.Cer = 0 + self.Ccov = 0 + self.Icorr = {} + self.Ccorr = {} + self.Rcorr = {} + self.dfrei_tbl = np.zeros(16) + + # covarience parameters from MT32 + self.t0 = {} + self.cov = {} + self.udrei = {} + self.dfrei = {} + self.dRcorr = {} + self.scl = {} def searchIGP(t, posp, cs): @@ -172,11 +196,57 @@ def ionoSBAS(t, pos, az, el, cs): return diono, var +def varsbas(el, nav, e, sat): + """ error variance of pseudorange measurement """ + + if nav.obad.dfrei[sat] == 15: # do not use for SBAS + return -1.0 + + el_d = el*rCST.R2D + + # multipath error component for single frequency + s_mp_sf = 0.13+0.53*np.exp(-el_d/10.0) + + s_el = np.sin(el) + m_el = 1.001/np.sqrt(0.002001+s_el**2) # mapping function + s_tve = 0.12 # tropospheric vertical error [m] + s_trop = s_tve*m_el + s_uire = 40.0/(261.4+el_d**2)+0.018 + s_noise = 0.11 + s_mp_df = 2.59*s_mp_sf + v_air = s_noise**2+s_mp_df**2 + + v_dfc = 0.0 + + sys, _ = sat2prn(sat) + + I = np.r_[-e, 1] + + if sys in nav.obad.Ccorr: + + eps_c = nav.obad.Ccorr[sys]*nav.obad.scl[sat] + C = nav.obad.cov[sat] + d_dfre = np.sqrt(I.T@C@I)+eps_c + dt = timediff(nav.t, nav.obad.t0[sat][sCType.CLOCK]) + if dt < nav.obad.Icorr[sys]: + Rcorr_sv = nav.obad.Rcorr[sys]*nav.obad.dRcorr[sat] + else: + Rcorr_sv = nav.obad.Rcorr[sys] + e_corr = dt*(nav.obad.Ccorr[sys]/nav.obad.Icorr[sys] + Rcorr_sv*1e-3) + + sig_dfre = nav.obad.dfrei_tbl[nav.obad.dfrei[sat]] + v_dfc = (sig_dfre*d_dfre)**2+e_corr**2 # error in df correction + + v_sig = v_dfc + s_uire**2 + s_trop**2 + v_air + + return v_sig + + class sbasDec(cssr): - def __init__(self, foutname=None): + def __init__(self, foutname=None, nf=1): super().__init__(foutname) self.MAXNET = 1 - self.cssrmode = sCSSRTYPE.SBAS_L1 + self.cssrmode = sCSSRTYPE.SBAS_L1 if nf == 1 else sCSSRTYPE.SBAS_L5 self.nsig_max = 0 self.sat_n = [] self.sat = [] @@ -206,10 +276,6 @@ def __init__(self, foutname=None): self.t0_igp = gtime_t() # intrgrity information - self.cov = {} - self.udrei = {} - self.dfrei = {} - self.dRcorr = {} self.ai = {} self.deg_prm = np.zeros(16) @@ -390,7 +456,7 @@ def decode_sbas_fast_corr(self, msg, i): j = type_*13+k if j >= len(self.sat): break - self.udrei[self.sat[j]] = udrei + self.obad.udrei[self.sat[j]] = udrei self.set_t0(sat=self.sat[j], ctype=sCType.HCLOCK, t=self.time) self.lc[0].cstat |= (1 << sCType.HCLOCK) @@ -403,7 +469,7 @@ def decode_sbas_integrity(self, msg, i): for k in range(51): if k >= len(self.sat): break - self.udrei[self.sat[k]] = bs.unpack_from('u4', msg, i)[0] + self.obad.udrei[self.sat[k]] = bs.unpack_from('u4', msg, i)[0] i += 4 return i @@ -668,12 +734,14 @@ def decode_cov(self, msg, i): scale, E11, E22, E33, E44, E12, E13, E14, E23, E24, E34 = \ bs.unpack_from('u3u9u9u9u9s10s10s10s10s10s10', msg, i) i += 99 + + scl = 2**(scale-5) R = np.array([[E11, E12, E13, E14], [0, E22, E23, E23], [0, 0, E33, E34], - [0, 0, 0, E44]])*(2**(scale-5)) + [0, 0, 0, E44]])*scl C = R.T@R - return i, C + return i, C, scl def decode_sbas_cov(self, msg, i): """ Type 28 clock-ephemeris covariance matrix """ @@ -686,9 +754,10 @@ def decode_sbas_cov(self, msg, i): for k in range(2): slot = bs.unpack_from('u6', msg, i)[0] i += 6 - i, C = self.decode_cov(msg, i) + i, C, scl = self.decode_cov(msg, i) sat = self.sat[slot] - self.cov[sat] = C + self.obad.cov[sat] = C + self.obad.scl[sat] = scl return i @@ -816,18 +885,27 @@ def decode_dfmc_obad(self, msg, i): self.obad.Cer = Cer*0.5 self.obad.Ccov = Ccov*0.1 + sys_t = [uGNSS.GPS, uGNSS.GLO, uGNSS.GAL, uGNSS.BDS, uGNSS.SBS, + uGNSS.QZS] # QZS is TBD + + self.obad.Icorr = {} + self.obad.Ccorr = {} + self.obad.Rcorr = {} + for k in range(6): Icorr, Ccorr, Rcorr = bs.unpack_from('u5u8u8', msg, i) i += 21 - self.obad.Icorr[k] = Icorr*6.0 - self.obad.Ccorr[k] = Ccorr*0.01 - self.obad.Rcorr[k] = Rcorr*0.2 + sys = sys_t[k] + self.obad.Icorr[sys] = Icorr*6.0 + self.obad.Ccorr[sys] = Ccorr*0.01 + self.obad.Rcorr[sys] = Rcorr*0.2 for k in range(15): scl = self.scl_dfrei_t[k] self.obad.dfrei_tbl[k] = bs.unpack_from('u4', msg, i)[0]*scl i += 4 + # time reference identifier self.obad.tid = bs.unpack_from('u3', msg, i)[0] i += 5 return i @@ -879,13 +957,14 @@ def decode_dfmc_eph_mt40(self, msg, i): eph.toe = tod2tow(te*16.0, self.time) eph.toc = eph.toe - i, C = self.decode_cov(msg, i) + i, C, scl = self.decode_cov(msg, i) dfrei, dRcorr = bs.unpack_from('u4u4', msg, i) i += 8 - self.cov[eph.sat] = C - self.udrei[eph.sat] = dfrei - self.dRcorr[eph.sat] = dRcorr + self.obad.cov[eph.sat] = C + self.obad.dfrei[eph.sat] = dfrei + self.obad.dRcorr[eph.sat] = dRcorr/15.0 + self.obad.scl[eph.sat] = scl return i @@ -961,7 +1040,7 @@ def decode_cssr_orb(self, msg, i, inet=0): self.time0 = tod2tow(t0*16.0, self.time) - i, C = self.decode_cov(msg, i) + i, C, scl = self.decode_cov(msg, i) dfrei, dRcorr = bs.unpack_from('u4u4', msg, i) i += 8 @@ -975,9 +1054,10 @@ def decode_cssr_orb(self, msg, i, inet=0): format(time2str(self.time), slot)) return i - self.cov[sat] = C - self.dfrei[sat] = dfrei - self.dRcorr[sat] = dRcorr + self.obad.cov[sat] = C + self.obad.dfrei[sat] = dfrei + self.obad.dRcorr[sat] = dRcorr/15.0 + self.obad.scl[sat] = scl # self.iodssr = 0 @@ -1077,5 +1157,5 @@ def decode_cssr(self, msg, i=0, src=0, prn=0): elif mt == 63: # Null pass - if self.monlevel > 3: + if self.monlevel > 1: self.fh.write("{:s} mt={:2d} \n".format(time2str(self.time), mt)) diff --git a/src/cssrlib/utils.py b/src/cssrlib/utils.py new file mode 100644 index 0000000..84593f3 --- /dev/null +++ b/src/cssrlib/utils.py @@ -0,0 +1,187 @@ +""" +Utility functions for cssrlib + +@author: ruihi +""" + +from sys import stdout +from copy import deepcopy +import matplotlib.pyplot as plt +import numpy as np +from cssrlib.gnss import time2str, sys2str, ecef2pos, ecef2enu, timediff +from cssrlib.gnss import gtime_t, char2sys, rSigRnx, uGNSS, uTYP, uSIG +from cssrlib.peph import atxdec, searchpcv +from cssrlib.plot import plot_enu + + +class process: + """ class to process the positioning """ + + def __init__(self, nav=None, rnx=None, config=None, nep=0, xyz_ref=[]): + self.nav = nav + self.rnx = rnx + self.config = config + self.nep = nep + self.t0 = gtime_t() + + if len(xyz_ref) > 0: + self.xyz_ref = xyz_ref + self.pos_ref = ecef2pos(self.xyz_ref) + + if nep > 0: + # Initialize data structures for results + # + self.t = np.zeros(nep) + self.enu = np.ones((nep, 3))*np.nan + self.ztd = np.zeros((nep, 1)) + self.smode = np.zeros(nep, dtype=int) + + def init_time(self, t): + """ initialize time """ + self.nav.t = deepcopy(t) + self.t0 = deepcopy(t) + self.t0.time = self.t0.time//30*30 + self.nav.time_p = self.t0 + + def init_sig(self, sig_t): + """ convert gnss/signals table to signals """ + # sig_t = {'G': ['1C', '5Q'], 'J': ['1C', '5Q']} + sigs = [] + nf = 0 + for gnss, sig_g in sig_t.items(): + nf = len(sig_g) + for sig in sig_g: + sig_ = rSigRnx(gnss+'C'+sig) + sigs.append(sig_) + sig_ = rSigRnx(gnss+'L'+sig) + sigs.append(sig_) + sig_ = rSigRnx(gnss+'S'+sig) + sigs.append(sig_) + + return sigs, nf + + def prepare_signal(self, obsfile=None): + """ prepare signals """ + + self.rnx.autoSubstituteSignals() # auto-substitute signals + + # Initialize navigation parameters + # + self.nav.elmin = np.deg2rad(self.config['elmin']) + self.nav.csmooth = self.config['nav']['csmooth'] + self.nav.pmode = self.config['nav']['pmode'] + + # Logging level + # + self.nav.monlevel = self.config['nav']['monlevel'] + + self.nav.glo_ch = self.rnx.glo_ch + + # Get equipment information + # + if obsfile is not None: + self.nav.fout.write("FileName: {}\n".format(obsfile)) + + self.nav.fout.write("Start : {}\n".format(time2str(self.rnx.ts))) + if self.rnx.te is not None: + self.nav.fout.write("End : {}\n".format(time2str(self.rnx.te))) + self.nav.fout.write("Receiver: {}\n".format(self.rnx.rcv)) + self.nav.fout.write("Antenna : {}\n".format(self.rnx.ant)) + self.nav.fout.write("\n") + + if 'UNKNOWN' in self.rnx.ant or self.rnx.ant.strip() == "": + self.nav.fout.write( + "ERROR: missing antenna type in RINEX OBS header!\n") + + # Load ANTEX data for satellites and stations + # + atx = atxdec() + atx.readpcv(self.config['atxfile']) + + # Set PCO/PCV information + # + self.nav.sat_ant = atx.pcvs + self.nav.rcv_ant = searchpcv(atx.pcvr, self.rnx.ant, self.rnx.ts) + if self.nav.rcv_ant is None: + self.nav.fout.write("ERROR: missing antenna type <{}> in ANTEX file!\n" + .format(self.rnx.ant)) + + # Print available signals + # + self.nav.fout.write("Available signals\n") + for sys, sigs in self.rnx.sig_map.items(): + txt = "{:7s} {}\n".format(sys2str(sys), ' '. + join([sig.str() for sig in sigs.values()])) + self.nav.fout.write(txt) + self.nav.fout.write("\n") + + self.nav.fout.write("Selected signals\n") + for sys, tmp in self.rnx.sig_tab.items(): + txt = "{:7s} ".format(sys2str(sys)) + for _, sigs in tmp.items(): + txt += "{} ".format(' '.join([sig.str() for sig in sigs])) + self.nav.fout.write(txt+"\n") + self.nav.fout.write("\n") + + def save_output(self, t, ne, ppp=None): + """ output result """ + + self.t[ne] = timediff(self.nav.t, self.t0)/86400.0 + + sol = self.nav.xa[0:3] if self.nav.smode == 4 else self.nav.x[0:3] + self.enu[ne, :] = ecef2enu(self.pos_ref, sol-self.xyz_ref) + self.smode[ne] = self.nav.smode + + if ppp is not None: + self.ztd[ne] = self.nav.xa[ppp.IT(self.nav.na)] \ + if self.nav.smode == 4 else self.nav.x[ppp.IT(self.nav.na)] + + enu = self.enu[ne, :] + smode = self.smode[ne] + + pos_h = np.sqrt(enu[0]**2+enu[1]**2) + + self.nav.fout.write("{} {:14.4f} {:14.4f} {:14.4f} " + "ENU {:7.3f} {:7.3f} {:7.3f}, 2D {:6.3f}, mode {:1d}\n" + .format(time2str(t), + sol[0], sol[1], sol[2], + enu[0], enu[1], enu[2], pos_h, smode)) + + # Log to standard output + # + stdout.write('\r {} ENU {:7.3f} {:7.3f} {:7.3f}, ' + '2D {:6.3f}, mode {:1d}' + .format(time2str(t), + enu[0], enu[1], enu[2], pos_h, smode)) + + def close(self): + """ finish processing """ + + # Send line-break to stdout + # + stdout.write('\n') + + # Close RINEX observation file + # + self.rnx.fobs.close() + + # Close output file + # + if self.nav.fout is not None: + self.nav.fout.close() + + def plot(self, ttl='test', fig_type=1, ylim=1, ylim_v=2, gfmt='png', + dpi=300): + """ plot utility """ + + if fig_type == 1: + plot_enu(self.t, self.enu, self.smode, ylim=ylim, ylim_v=ylim_v) + elif fig_type == 2: + plot_enu(self.t, self.enu, self.smode, figtype=fig_type, ylim=ylim) + + plotFileFormat = gfmt + plotFileName = '.'.join((ttl, plotFileFormat)) + + plt.savefig(plotFileName, format=plotFileFormat, + bbox_inches='tight', dpi=dpi) + # plt.show() diff --git a/tutorials/auth.ipynb b/tutorials/auth.ipynb index 1ab4004..f9011e7 100644 --- a/tutorials/auth.ipynb +++ b/tutorials/auth.ipynb @@ -12,7 +12,17 @@ "cell_type": "markdown", "id": "5d0e0f68", "metadata": {}, - "source": [] + "source": [ + "In this notebook, the satellite based navigation message authentication provided by Galileo OSNMA and QZSS QZNMA will be demonstrated.\n", + "\n", + "Please note that the public key information need to be prepared by user. \n", + "\n", + "For Galileo OSNMA, `OSNMA_MerkleTree*.xml` should be downloaded from [EUSPA web site](https://www.gsc-europa.eu/) (registration is required), and copied to `../data/pubkey/onsma/`.\n", + "\n", + "For QZSS QZNMA, the public Keys are distributed directly by Signal Authentication System Operator upon receiving the [requests](https://qzss.go.jp/en/inquiry/) to the [MICHIBIKI website](https://qzss.go.jp/en/) of Cabinet Office. Please copy the public keys `*.der` to `../data/pubkey/qznma/`.\n", + "\n", + "\n" + ] }, { "cell_type": "code", @@ -63,13 +73,13 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 19, "id": "98ee0559", "metadata": {}, "outputs": [], "source": [ "tofst = -2 # time offset to synchronize tow\n", - "mt_file = 'OSNMA_MerkleTree_20240115100000_newPKID_1.xml'\n", + "mt_file = 'OSNMA_MerkleTree_20240115100000_newPKID_1.xml' # update if necessary\n", "\n", "if not os.path.exists('../data/pubkey/osnma/'+mt_file):\n", " print('please install OSNMA_MerkleTree*.xml from EUSPA.')\n", @@ -5130,7 +5140,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "id": "20a28e12", "metadata": {}, "outputs": [], @@ -5152,7 +5162,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "id": "168382c0", "metadata": {}, "outputs": [], @@ -5164,7 +5174,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "id": "eda16091", "metadata": {}, "outputs": [], @@ -5200,7 +5210,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "id": "a4de4b9e", "metadata": {}, "outputs": [], @@ -5227,10 +5237,449 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "id": "cd4e671d", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "#tow=370816 mt=1 sys=0 prn=10 signature OK.\n", + "#tow=370827 mt=1 sys=0 prn=24 signature OK.\n", + "#tow=370836 mt=1 sys=0 prn=15 signature OK.\n", + "#tow=370837 mt=1 sys=0 prn=25 signature OK.\n", + "#tow=370846 mt=1 sys=0 prn=32 signature OK.\n", + "#tow=370857 mt=1 sys=0 prn=23 signature OK.\n", + "#tow=370866 mt=1 sys=0 prn=12 signature OK.\n", + "#tow=370866 mt=1 sys=0 prn=18 signature OK.\n", + "#tow=370867 mt=5 sys=1 prn= 4 signature OK.\n", + "#tow=370877 mt=5 sys=1 prn=11 signature OK.\n", + "#tow=370886 mt=5 sys=1 prn=21 signature OK.\n", + "#tow=370897 mt=5 sys=1 prn=33 signature OK.\n", + "#tow=370897 mt=5 sys=1 prn=27 signature OK.\n", + "#tow=370906 mt=5 sys=1 prn=19 signature OK.\n", + "#tow=370906 mt=2 sys=0 prn=10 signature OK.\n", + "#tow=370916 mt=5 sys=1 prn=12 signature OK.\n", + "#tow=370916 mt=2 sys=0 prn=25 signature OK.\n", + "#tow=370917 mt=2 sys=0 prn=32 signature OK.\n", + "#tow=370936 mt=2 sys=0 prn=23 signature OK.\n", + "#tow=370946 mt=2 sys=0 prn=18 signature OK.\n", + "#tow=370947 mt=2 sys=0 prn=24 signature OK.\n", + "#tow=370967 mt=4 sys=1 prn=11 signature OK.\n", + "#tow=370976 mt=4 sys=1 prn=12 signature OK.\n", + "#tow=370977 mt=4 sys=1 prn=21 signature OK.\n", + "#tow=370987 mt=4 sys=1 prn=19 signature OK.\n", + "#tow=370997 mt=4 sys=1 prn= 4 signature OK.\n", + "# LNAV 370998, 194 signature OK.\n", + "# LNAV 370998, 199 signature OK.\n", + "# LNAV 370998, 195 signature OK.\n", + "# LNAV 370998, 196 signature OK.\n", + "# LNAV 370998, 200 signature OK.\n", + "#tow=371006 mt=4 sys=1 prn=33 signature OK.\n", + "#tow=371006 mt=4 sys=1 prn=27 signature OK.\n", + "#tow=371116 mt=1 sys=0 prn=12 signature OK.\n", + "#tow=371117 mt=1 sys=0 prn=15 signature OK.\n", + "#tow=371127 mt=1 sys=0 prn=18 signature OK.\n", + "#tow=371136 mt=1 sys=0 prn=10 signature OK.\n", + "#tow=371146 mt=2 sys=0 prn=10 signature OK.\n", + "#tow=371147 mt=1 sys=0 prn=24 signature OK.\n", + "#tow=371157 mt=2 sys=0 prn=32 signature OK.\n", + "#tow=371166 mt=1 sys=0 prn=32 signature OK.\n", + "#tow=371167 mt=2 sys=0 prn=24 signature OK.\n", + "#tow=371186 mt=1 sys=0 prn=23 signature OK.\n", + "#tow=371196 mt=2 sys=0 prn=25 signature OK.\n", + "#tow=371197 mt=5 sys=1 prn=11 signature OK.\n", + "#tow=371206 mt=1 sys=0 prn=25 signature OK.\n", + "#tow=371216 mt=5 sys=1 prn=12 signature OK.\n", + "#tow=371216 mt=5 sys=1 prn=33 signature OK.\n", + "#tow=371217 mt=5 sys=1 prn=10 signature OK.\n", + "#tow=371226 mt=5 sys=1 prn=21 signature OK.\n", + "#tow=371236 mt=5 sys=1 prn= 4 signature OK.\n", + "#tow=371236 mt=5 sys=1 prn=19 signature OK.\n", + "# LNAV 371238, 194 signature OK.\n", + "# LNAV 371238, 195 signature OK.\n", + "# LNAV 371238, 199 signature OK.\n", + "# LNAV 371238, 196 signature OK.\n", + "# LNAV 371238, 200 signature OK.\n", + "#tow=371256 mt=5 sys=1 prn=27 signature OK.\n", + "#tow=371256 mt=2 sys=0 prn=18 signature OK.\n", + "#tow=371257 mt=2 sys=0 prn=23 signature OK.\n", + "#tow=371257 mt=4 sys=1 prn= 4 signature OK.\n", + "#tow=371266 mt=4 sys=1 prn=10 signature OK.\n", + "#tow=371267 mt=4 sys=1 prn=11 signature OK.\n", + "#tow=371287 mt=4 sys=1 prn=19 signature OK.\n", + "#tow=371297 mt=4 sys=1 prn=12 signature OK.\n", + "#tow=371306 mt=4 sys=1 prn=33 signature OK.\n", + "#tow=371306 mt=4 sys=1 prn=21 signature OK.\n", + "#tow=371307 mt=4 sys=1 prn=27 signature OK.\n", + "#tow=371416 mt=1 sys=0 prn=10 signature OK.\n", + "#tow=371417 mt=1 sys=0 prn=18 signature OK.\n", + "#tow=371417 mt=1 sys=0 prn=15 signature OK.\n", + "#tow=371436 mt=1 sys=0 prn=12 signature OK.\n", + "#tow=371436 mt=1 sys=0 prn=32 signature OK.\n", + "#tow=371446 mt=1 sys=0 prn=24 signature OK.\n", + "#tow=371457 mt=1 sys=0 prn=23 signature OK.\n", + "#tow=371467 mt=1 sys=0 prn=25 signature OK.\n", + "#tow=371476 mt=5 sys=1 prn=12 signature OK.\n", + "#tow=371477 mt=5 sys=1 prn= 4 signature OK.\n", + "# LNAV 371478, 194 signature OK.\n", + "# LNAV 371478, 199 signature OK.\n", + "# LNAV 371478, 195 signature OK.\n", + "# LNAV 371478, 196 signature OK.\n", + "# LNAV 371478, 200 signature OK.\n", + "#tow=371486 mt=5 sys=1 prn=19 signature OK.\n", + "#tow=371496 mt=5 sys=1 prn=33 signature OK.\n", + "#tow=371507 mt=5 sys=1 prn=10 signature OK.\n", + "#tow=371516 mt=2 sys=0 prn=23 signature OK.\n", + "#tow=371517 mt=2 sys=0 prn=25 signature OK.\n", + "#tow=371526 mt=5 sys=1 prn=27 signature OK.\n", + "#tow=371536 mt=2 sys=0 prn=24 signature OK.\n", + "#tow=371537 mt=2 sys=0 prn=10 signature OK.\n", + "#tow=371537 mt=5 sys=1 prn=11 signature OK.\n", + "#tow=371547 mt=2 sys=0 prn=18 signature OK.\n", + "#tow=371547 mt=5 sys=1 prn=21 signature OK.\n", + "#tow=371556 mt=2 sys=0 prn=32 signature OK.\n", + "#tow=371567 mt=4 sys=1 prn= 4 signature OK.\n", + "#tow=371567 mt=4 sys=1 prn=12 signature OK.\n", + "#tow=371576 mt=4 sys=1 prn=10 signature OK.\n", + "#tow=371576 mt=4 sys=1 prn=21 signature OK.\n", + "#tow=371587 mt=4 sys=1 prn=11 signature OK.\n", + "#tow=371596 mt=4 sys=1 prn=27 signature OK.\n", + "#tow=371597 mt=4 sys=1 prn=33 signature OK.\n", + "#tow=371606 mt=4 sys=1 prn=19 signature OK.\n", + "#tow=371717 mt=1 sys=0 prn=10 signature OK.\n", + "# LNAV 371718, 194 signature OK.\n", + "# LNAV 371718, 195 signature OK.\n", + "# LNAV 371718, 199 signature OK.\n", + "# LNAV 371718, 196 signature OK.\n", + "# LNAV 371718, 200 signature OK.\n", + "#tow=371727 mt=1 sys=0 prn=18 signature OK.\n", + "#tow=371746 mt=1 sys=0 prn=23 signature OK.\n", + "#tow=371747 mt=2 sys=0 prn=25 signature OK.\n", + "#tow=371757 mt=1 sys=0 prn=32 signature OK.\n", + "#tow=371766 mt=1 sys=0 prn=25 signature OK.\n", + "#tow=371767 mt=2 sys=0 prn=32 signature OK.\n", + "#tow=371776 mt=2 sys=0 prn=10 signature OK.\n", + "#tow=371777 mt=1 sys=0 prn=24 signature OK.\n", + "#tow=371786 mt=2 sys=0 prn=18 signature OK.\n", + "#tow=371796 mt=1 sys=0 prn=15 signature OK.\n", + "#tow=371796 mt=5 sys=1 prn=11 signature OK.\n", + "#tow=371806 mt=2 sys=0 prn=24 signature OK.\n", + "#tow=371826 mt=1 sys=0 prn=12 signature OK.\n", + "#tow=371827 mt=5 sys=1 prn=33 signature OK.\n", + "#tow=371836 mt=2 sys=0 prn=23 signature OK.\n", + "#tow=371846 mt=5 sys=1 prn=19 signature OK.\n", + "#tow=371846 mt=5 sys=1 prn=27 signature OK.\n", + "#tow=371847 mt=5 sys=1 prn=21 signature OK.\n", + "#tow=371847 mt=5 sys=1 prn= 4 signature OK.\n", + "#tow=371856 mt=5 sys=1 prn=12 signature OK.\n", + "#tow=371857 mt=5 sys=1 prn=10 signature OK.\n", + "#tow=371867 mt=4 sys=1 prn= 4 signature OK.\n", + "#tow=371876 mt=4 sys=1 prn=10 signature OK.\n", + "#tow=371877 mt=4 sys=1 prn=11 signature OK.\n", + "#tow=371887 mt=4 sys=1 prn=33 signature OK.\n", + "#tow=371896 mt=4 sys=1 prn=19 signature OK.\n", + "#tow=371896 mt=4 sys=1 prn=21 signature OK.\n", + "#tow=371906 mt=4 sys=1 prn=27 signature OK.\n", + "#tow=371907 mt=4 sys=1 prn=12 signature OK.\n", + "# LNAV 371958, 194 signature OK.\n", + "# LNAV 371958, 195 signature OK.\n", + "# LNAV 371958, 199 signature OK.\n", + "# LNAV 371958, 196 signature OK.\n", + "# LNAV 371958, 200 signature OK.\n", + "#tow=372026 mt=1 sys=0 prn=15 signature OK.\n", + "#tow=372037 mt=1 sys=0 prn=32 signature OK.\n", + "#tow=372046 mt=1 sys=0 prn=10 signature OK.\n", + "#tow=372047 mt=1 sys=0 prn=23 signature OK.\n", + "#tow=372056 mt=1 sys=0 prn=18 signature OK.\n", + "#tow=372057 mt=1 sys=0 prn=24 signature OK.\n", + "#tow=372057 mt=1 sys=0 prn=25 signature OK.\n", + "#tow=372066 mt=1 sys=0 prn=12 signature OK.\n", + "#tow=372077 mt=5 sys=1 prn=11 signature OK.\n", + "#tow=372087 mt=5 sys=1 prn=12 signature OK.\n", + "#tow=372087 mt=5 sys=1 prn=27 signature OK.\n", + "#tow=372097 mt=5 sys=1 prn= 4 signature OK.\n", + "#tow=372107 mt=5 sys=1 prn=19 signature OK.\n", + "#tow=372116 mt=2 sys=0 prn=18 signature OK.\n", + "#tow=372117 mt=2 sys=0 prn=25 signature OK.\n", + "#tow=372127 mt=5 sys=1 prn=21 signature OK.\n", + "#tow=372136 mt=2 sys=0 prn=24 signature OK.\n", + "#tow=372137 mt=5 sys=1 prn=33 signature OK.\n", + "#tow=372147 mt=2 sys=0 prn=23 signature OK.\n", + "#tow=372147 mt=2 sys=0 prn=32 signature OK.\n", + "#tow=372156 mt=2 sys=0 prn=10 signature OK.\n", + "#tow=372157 mt=5 sys=1 prn=10 signature OK.\n", + "#tow=372166 mt=4 sys=1 prn= 4 signature OK.\n", + "#tow=372177 mt=4 sys=1 prn=12 signature OK.\n", + "#tow=372186 mt=4 sys=1 prn=11 signature OK.\n", + "#tow=372186 mt=4 sys=1 prn=19 signature OK.\n", + "#tow=372196 mt=4 sys=1 prn=27 signature OK.\n", + "#tow=372196 mt=4 sys=1 prn=33 signature OK.\n", + "#tow=372197 mt=4 sys=1 prn=10 signature OK.\n", + "# LNAV 372198, 194 signature OK.\n", + "# LNAV 372198, 199 signature OK.\n", + "# LNAV 372198, 195 signature OK.\n", + "# LNAV 372198, 196 signature OK.\n", + "# LNAV 372198, 200 signature OK.\n", + "#tow=372206 mt=4 sys=1 prn=21 signature OK.\n", + "#tow=372316 mt=1 sys=0 prn=10 signature OK.\n", + "#tow=372327 mt=1 sys=0 prn=24 signature OK.\n", + "#tow=372327 mt=1 sys=0 prn=18 signature OK.\n", + "#tow=372337 mt=1 sys=0 prn=15 signature OK.\n", + "#tow=372347 mt=2 sys=0 prn=24 signature OK.\n", + "#tow=372356 mt=1 sys=0 prn=12 signature OK.\n", + "#tow=372366 mt=1 sys=0 prn=23 signature OK.\n", + "#tow=372366 mt=1 sys=0 prn=32 signature OK.\n", + "#tow=372377 mt=1 sys=0 prn=25 signature OK.\n", + "#tow=372386 mt=2 sys=0 prn=18 signature OK.\n", + "#tow=372387 mt=2 sys=0 prn=23 signature OK.\n", + "#tow=372396 mt=2 sys=0 prn=10 signature OK.\n", + "#tow=372396 mt=2 sys=0 prn=25 signature OK.\n", + "#tow=372397 mt=2 sys=0 prn=32 signature OK.\n", + "#tow=372416 mt=5 sys=1 prn=12 signature OK.\n", + "#tow=372417 mt=5 sys=1 prn=19 signature OK.\n", + "#tow=372427 mt=5 sys=1 prn=33 signature OK.\n", + "#tow=372437 mt=5 sys=1 prn= 4 signature OK.\n", + "# LNAV 372438, 194 signature OK.\n", + "# LNAV 372438, 199 signature OK.\n", + "# LNAV 372438, 195 signature OK.\n", + "# LNAV 372438, 196 signature OK.\n", + "# LNAV 372438, 200 signature OK.\n", + "#tow=372447 mt=5 sys=1 prn=11 signature OK.\n", + "#tow=372456 mt=5 sys=1 prn=10 signature OK.\n", + "#tow=372456 mt=5 sys=1 prn=27 signature OK.\n", + "#tow=372466 mt=5 sys=1 prn=21 signature OK.\n", + "#tow=372466 mt=4 sys=1 prn= 4 signature OK.\n", + "#tow=372476 mt=4 sys=1 prn=12 signature OK.\n", + "#tow=372477 mt=4 sys=1 prn=10 signature OK.\n", + "#tow=372486 mt=4 sys=1 prn=27 signature OK.\n", + "#tow=372486 mt=4 sys=1 prn=21 signature OK.\n", + "#tow=372496 mt=4 sys=1 prn=33 signature OK.\n", + "#tow=372497 mt=4 sys=1 prn=11 signature OK.\n", + "#tow=372506 mt=4 sys=1 prn=19 signature OK.\n", + "#tow=372607 mt=1 sys=0 prn=10 signature OK.\n", + "#tow=372627 mt=1 sys=0 prn=24 signature OK.\n", + "#tow=372637 mt=1 sys=0 prn=15 signature OK.\n", + "#tow=372637 mt=1 sys=0 prn=32 signature OK.\n", + "#tow=372656 mt=1 sys=0 prn=12 signature OK.\n", + "#tow=372657 mt=1 sys=0 prn=25 signature OK.\n", + "#tow=372657 mt=1 sys=0 prn=18 signature OK.\n", + "#tow=372667 mt=1 sys=0 prn=23 signature OK.\n", + "#tow=372677 mt=5 sys=1 prn=10 signature OK.\n", + "# LNAV 372678, 194 signature OK.\n", + "# LNAV 372678, 199 signature OK.\n", + "# LNAV 372678, 195 signature OK.\n", + "# LNAV 372678, 196 signature OK.\n", + "# LNAV 372678, 200 signature OK.\n", + "#tow=372687 mt=5 sys=1 prn=19 signature OK.\n", + "#tow=372696 mt=5 sys=1 prn=27 signature OK.\n", + "#tow=372697 mt=5 sys=1 prn=12 signature OK.\n", + "#tow=372706 mt=5 sys=1 prn=33 signature OK.\n", + "#tow=372706 mt=5 sys=1 prn=21 signature OK.\n", + "#tow=372716 mt=2 sys=0 prn=24 signature OK.\n", + "#tow=372717 mt=2 sys=0 prn=10 signature OK.\n", + "#tow=372726 mt=2 sys=0 prn=32 signature OK.\n", + "#tow=372726 mt=2 sys=0 prn=23 signature OK.\n", + "#tow=372736 mt=5 sys=1 prn= 4 signature OK.\n", + "#tow=372737 mt=2 sys=0 prn=25 signature OK.\n", + "#tow=372746 mt=2 sys=0 prn=18 signature OK.\n", + "#tow=372756 mt=5 sys=1 prn=11 signature OK.\n", + "#tow=372767 mt=4 sys=1 prn= 4 signature OK.\n", + "#tow=372776 mt=4 sys=1 prn=12 signature OK.\n", + "#tow=372777 mt=4 sys=1 prn=11 signature OK.\n", + "#tow=372786 mt=4 sys=1 prn=27 signature OK.\n", + "#tow=372787 mt=4 sys=1 prn=33 signature OK.\n", + "#tow=372796 mt=4 sys=1 prn=10 signature OK.\n", + "#tow=372806 mt=4 sys=1 prn=19 signature OK.\n", + "#tow=372816 mt=4 sys=1 prn=21 signature OK.\n", + "#tow=372917 mt=1 sys=0 prn=15 signature OK.\n", + "# LNAV 372918, 194 signature OK.\n", + "# LNAV 372918, 199 signature OK.\n", + "# LNAV 372918, 195 signature OK.\n", + "# LNAV 372918, 196 signature OK.\n", + "# LNAV 372918, 200 signature OK.\n", + "#tow=372927 mt=1 sys=0 prn=23 signature OK.\n", + "#tow=372937 mt=1 sys=0 prn=12 signature OK.\n", + "#tow=372946 mt=1 sys=0 prn=18 signature OK.\n", + "#tow=372956 mt=2 sys=0 prn=10 signature OK.\n", + "#tow=372956 mt=2 sys=0 prn=18 signature OK.\n", + "#tow=372957 mt=1 sys=0 prn=10 signature OK.\n", + "#tow=372966 mt=2 sys=0 prn=32 signature OK.\n", + "#tow=372976 mt=2 sys=0 prn=25 signature OK.\n", + "#tow=372977 mt=1 sys=0 prn=25 signature OK.\n", + "#tow=372986 mt=2 sys=0 prn=23 signature OK.\n", + "#tow=372987 mt=1 sys=0 prn=24 signature OK.\n", + "#tow=372987 mt=2 sys=0 prn=24 signature OK.\n", + "#tow=373007 mt=5 sys=1 prn=21 signature OK.\n", + "#tow=373016 mt=5 sys=1 prn=27 signature OK.\n", + "#tow=373017 mt=5 sys=1 prn=33 signature OK.\n", + "#tow=373027 mt=5 sys=1 prn=10 signature OK.\n", + "#tow=373036 mt=5 sys=1 prn=12 signature OK.\n", + "#tow=373037 mt=1 sys=0 prn=32 signature OK.\n", + "#tow=373037 mt=5 sys=1 prn=19 signature OK.\n", + "#tow=373046 mt=5 sys=1 prn=11 signature OK.\n", + "#tow=373066 mt=5 sys=1 prn= 4 signature OK.\n", + "#tow=373076 mt=4 sys=1 prn=11 signature OK.\n", + "#tow=373077 mt=4 sys=1 prn=19 signature OK.\n", + "#tow=373086 mt=4 sys=1 prn= 4 signature OK.\n", + "#tow=373087 mt=4 sys=1 prn=27 signature OK.\n", + "#tow=373087 mt=4 sys=1 prn=12 signature OK.\n", + "#tow=373097 mt=4 sys=1 prn=10 signature OK.\n", + "#tow=373116 mt=4 sys=1 prn=21 signature OK.\n", + "#tow=373116 mt=4 sys=1 prn=33 signature OK.\n", + "# LNAV 373158, 194 signature OK.\n", + "# LNAV 373158, 199 signature OK.\n", + "# LNAV 373158, 195 signature OK.\n", + "# LNAV 373158, 196 signature OK.\n", + "# LNAV 373158, 200 signature OK.\n", + "#tow=373207 mt=1 sys=0 prn=10 signature OK.\n", + "#tow=373226 mt=1 sys=0 prn=12 signature OK.\n", + "#tow=373227 mt=1 sys=0 prn=25 signature OK.\n", + "#tow=373237 mt=1 sys=0 prn=32 signature OK.\n", + "#tow=373247 mt=1 sys=0 prn=23 signature OK.\n", + "#tow=373256 mt=1 sys=0 prn=18 signature OK.\n", + "#tow=373256 mt=1 sys=0 prn=24 signature OK.\n", + "#tow=373267 mt=1 sys=0 prn=15 signature OK.\n", + "#tow=373276 mt=5 sys=1 prn= 4 signature OK.\n", + "#tow=373286 mt=5 sys=1 prn=11 signature OK.\n", + "#tow=373296 mt=5 sys=1 prn=19 signature OK.\n", + "#tow=373296 mt=5 sys=1 prn=27 signature OK.\n", + "#tow=373297 mt=5 sys=1 prn=21 signature OK.\n", + "#tow=373297 mt=5 sys=1 prn=12 signature OK.\n", + "#tow=373316 mt=2 sys=0 prn=23 signature OK.\n", + "#tow=373317 mt=2 sys=0 prn=24 signature OK.\n", + "#tow=373317 mt=5 sys=1 prn=10 signature OK.\n", + "#tow=373326 mt=2 sys=0 prn=10 signature OK.\n", + "#tow=373327 mt=2 sys=0 prn=32 signature OK.\n", + "#tow=373336 mt=2 sys=0 prn=18 signature OK.\n", + "#tow=373366 mt=2 sys=0 prn=25 signature OK.\n", + "#tow=373367 mt=5 sys=1 prn=33 signature OK.\n", + "#tow=373367 mt=4 sys=1 prn= 4 signature OK.\n", + "#tow=373386 mt=4 sys=1 prn=10 signature OK.\n", + "#tow=373396 mt=4 sys=1 prn=19 signature OK.\n", + "#tow=373397 mt=4 sys=1 prn=11 signature OK.\n", + "# LNAV 373398, 194 signature OK.\n", + "# LNAV 373398, 199 signature OK.\n", + "# LNAV 373398, 196 signature OK.\n", + "# LNAV 373398, 200 signature OK.\n", + "#tow=373406 mt=4 sys=1 prn=21 signature OK.\n", + "#tow=373407 mt=4 sys=1 prn=33 signature OK.\n", + "#tow=373416 mt=4 sys=1 prn=27 signature OK.\n", + "#tow=373416 mt=4 sys=1 prn=12 signature OK.\n", + "#tow=373516 mt=1 sys=0 prn=12 signature OK.\n", + "LNAV 373518, 195 signature NG.\n", + "#tow=373526 mt=1 sys=0 prn=24 signature OK.\n", + "#tow=373537 mt=1 sys=0 prn=25 signature OK.\n", + "#tow=373547 mt=1 sys=0 prn=18 signature OK.\n", + "#tow=373556 mt=2 sys=0 prn=18 signature OK.\n", + "#tow=373556 mt=1 sys=0 prn=32 signature OK.\n", + "#tow=373557 mt=2 sys=0 prn=25 signature OK.\n", + "#tow=373567 mt=1 sys=0 prn=23 signature OK.\n", + "#tow=373567 mt=2 sys=0 prn=23 signature OK.\n", + "#tow=373576 mt=1 sys=0 prn=10 signature OK.\n", + "#tow=373577 mt=2 sys=0 prn=10 signature OK.\n", + "#tow=373596 mt=2 sys=0 prn=32 signature OK.\n", + "#tow=373596 mt=5 sys=1 prn=10 signature OK.\n", + "#tow=373597 mt=5 sys=1 prn=11 signature OK.\n", + "#tow=373626 mt=5 sys=1 prn=21 signature OK.\n", + "#tow=373637 mt=5 sys=1 prn=19 signature OK.\n", + "#tow=373637 mt=1 sys=0 prn=15 signature OK.\n", + "# LNAV 373638, 194 signature OK.\n", + "# LNAV 373638, 199 signature OK.\n", + "# LNAV 373638, 196 signature OK.\n", + "# LNAV 373638, 200 signature OK.\n", + "#tow=373646 mt=2 sys=0 prn=24 signature OK.\n", + "#tow=373656 mt=5 sys=1 prn= 4 signature OK.\n", + "#tow=373666 mt=5 sys=1 prn=33 signature OK.\n", + "#tow=373666 mt=5 sys=1 prn=12 signature OK.\n", + "#tow=373667 mt=5 sys=1 prn=27 signature OK.\n", + "#tow=373676 mt=4 sys=1 prn=10 signature OK.\n", + "#tow=373686 mt=4 sys=1 prn=12 signature OK.\n", + "#tow=373687 mt=4 sys=1 prn=21 signature OK.\n", + "#tow=373687 mt=4 sys=1 prn= 4 signature OK.\n", + "#tow=373707 mt=4 sys=1 prn=11 signature OK.\n", + "#tow=373716 mt=4 sys=1 prn=33 signature OK.\n", + "#tow=373717 mt=4 sys=1 prn=27 signature OK.\n", + "#tow=373726 mt=4 sys=1 prn=19 signature OK.\n", + "LNAV 373758, 195 signature NG.\n", + "#tow=373807 mt=1 sys=0 prn=12 signature OK.\n", + "#tow=373827 mt=1 sys=0 prn=23 signature OK.\n", + "#tow=373836 mt=1 sys=0 prn=24 signature OK.\n", + "#tow=373847 mt=1 sys=0 prn=32 signature OK.\n", + "#tow=373856 mt=1 sys=0 prn=15 signature OK.\n", + "#tow=373857 mt=1 sys=0 prn=10 signature OK.\n", + "#tow=373857 mt=1 sys=0 prn=18 signature OK.\n", + "#tow=373866 mt=1 sys=0 prn=25 signature OK.\n", + "# LNAV 373878, 194 signature OK.\n", + "# LNAV 373878, 199 signature OK.\n", + "# LNAV 373878, 196 signature OK.\n", + "# LNAV 373878, 200 signature OK.\n", + "#tow=373886 mt=5 sys=1 prn=10 signature OK.\n", + "#tow=373886 mt=5 sys=1 prn=11 signature OK.\n", + "#tow=373896 mt=5 sys=1 prn=19 signature OK.\n", + "#tow=373897 mt=5 sys=1 prn=12 signature OK.\n", + "#tow=373897 mt=5 sys=1 prn=33 signature OK.\n", + "#tow=373906 mt=5 sys=1 prn= 4 signature OK.\n", + "#tow=373907 mt=2 sys=0 prn=10 signature OK.\n", + "#tow=373916 mt=2 sys=0 prn=18 signature OK.\n", + "#tow=373917 mt=2 sys=0 prn=23 signature OK.\n", + "#tow=373917 mt=2 sys=0 prn=24 signature OK.\n", + "#tow=373936 mt=5 sys=1 prn=27 signature OK.\n", + "#tow=373937 mt=2 sys=0 prn=32 signature OK.\n", + "#tow=373946 mt=2 sys=0 prn=25 signature OK.\n", + "#tow=373946 mt=5 sys=1 prn=21 signature OK.\n", + "#tow=373976 mt=4 sys=1 prn= 4 signature OK.\n", + "#tow=373987 mt=4 sys=1 prn=21 signature OK.\n", + "#tow=373996 mt=4 sys=1 prn=19 signature OK.\n", + "#tow=373996 mt=4 sys=1 prn=10 signature OK.\n", + "#tow=373997 mt=4 sys=1 prn=33 signature OK.\n", + "#tow=373997 mt=4 sys=1 prn=11 signature OK.\n", + "LNAV 373998, 195 signature NG.\n", + "#tow=374007 mt=4 sys=1 prn=27 signature OK.\n", + "#tow=374017 mt=4 sys=1 prn=12 signature OK.\n", + "#tow=374116 mt=1 sys=0 prn=10 signature OK.\n", + "#tow=374117 mt=1 sys=0 prn=12 signature OK.\n", + "# LNAV 374118, 194 signature OK.\n", + "# LNAV 374118, 199 signature OK.\n", + "# LNAV 374118, 196 signature OK.\n", + "# LNAV 374118, 200 signature OK.\n", + "#tow=374127 mt=1 sys=0 prn=23 signature OK.\n", + "#tow=374147 mt=2 sys=0 prn=10 signature OK.\n", + "#tow=374147 mt=2 sys=0 prn=18 signature OK.\n", + "#tow=374157 mt=1 sys=0 prn=15 signature OK.\n", + "#tow=374167 mt=1 sys=0 prn=25 signature OK.\n", + "#tow=374187 mt=1 sys=0 prn=32 signature OK.\n", + "#tow=374196 mt=2 sys=0 prn=32 signature OK.\n", + "#tow=374197 mt=2 sys=0 prn=25 signature OK.\n", + "#tow=374197 mt=5 sys=1 prn=11 signature OK.\n", + "#tow=374216 mt=1 sys=0 prn=18 signature OK.\n", + "#tow=374217 mt=2 sys=0 prn=23 signature OK.\n", + "#tow=374217 mt=5 sys=1 prn=33 signature OK.\n", + "#tow=374227 mt=5 sys=1 prn=21 signature OK.\n", + "#tow=374236 mt=2 sys=0 prn=24 signature OK.\n", + "#tow=374237 mt=5 sys=1 prn=19 signature OK.\n", + "LNAV 374238, 195 signature NG.\n", + "#tow=374246 mt=5 sys=1 prn=12 signature OK.\n", + "#tow=374247 mt=5 sys=1 prn= 4 signature OK.\n", + "#tow=374247 mt=1 sys=0 prn=24 signature OK.\n", + "#tow=374256 mt=5 sys=1 prn=27 signature OK.\n", + "#tow=374257 mt=5 sys=1 prn=10 signature OK.\n", + "#tow=374276 mt=4 sys=1 prn= 4 signature OK.\n", + "#tow=374286 mt=4 sys=1 prn=11 signature OK.\n", + "#tow=374287 mt=4 sys=1 prn=12 signature OK.\n", + "#tow=374287 mt=4 sys=1 prn=19 signature OK.\n", + "#tow=374296 mt=4 sys=1 prn=33 signature OK.\n", + "#tow=374307 mt=4 sys=1 prn=27 signature OK.\n", + "#tow=374317 mt=4 sys=1 prn=10 signature OK.\n", + "#tow=374326 mt=4 sys=1 prn=21 signature OK.\n", + "# LNAV 374358, 194 signature OK.\n", + "# LNAV 374358, 199 signature OK.\n", + "# LNAV 374358, 196 signature OK.\n", + "# LNAV 374358, 200 signature OK.\n" + ] + } + ], "source": [ "# tow_ = np.unique(v['tow'])\n", "tow_ = np.arange(v['tow'][0], v['tow'][-1])\n", @@ -5263,10 +5712,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "id": "290616a3", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjgAAAGxCAYAAABvIsx7AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjcsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvTLEjVAAAAAlwSFlzAAAPYQAAD2EBqD+naQAAXftJREFUeJzt3Qd8U+X6B/Bf2qZ0UFqgCC20UJYyVYZeQBmyFEQQcQGCCweoKOLAv8hwgHJVuA7UqwyvMkQQRUGBK0NkCyiyBFkFitwCbYHuJv/P86aJ3W2ac5KT5Pf9fELSkzQ9vDnJefK8z/u+JqvVagURERGRDwnw9A4QERERaY0BDhEREfkcBjhERETkcxjgEBERkc9hgENEREQ+hwEOERER+RwGOERERORzguDlLBYLTp06hYiICJhMJk/vDhEREVWATMN34cIFxMbGIiBA+3yL1wc4EtzExcV5ejeIiIioEhITE1GvXj1ozesDHMnciCNHjqBGjRqe3h2vlpOTg5UrV6JXr14wm82e3h2vxXZkWxoRj0u2o9GcO3cOCQkJjvO41rw+wLF3S0kDVatWzdO74/UfgGFhYaodGeCwHY2AxyTb0mh4TGrblkKv8hIWGRMREZHPYYBDREREPocBDhEREfkcBjhERETkcxjgEBERkc9hgENEREQ+hwEOERER+RwGOERERORzGOAQERGRz2GAQ0RERD7H4wFOXl4exo8fr9ajCA0NRaNGjfDyyy+rVUaJiIiIvHItqtdffx0zZ87E3Llz0aJFC2zfvh333XcfIiMj8cQTT3h690gHSakZOJJ8CeHBgbiUnaeuj59LV+uRxFUPLXFbefcbadvR/13EzmQT6p5IRbYFhtkvb2pX++06EcE4mGrCrydSkZSW5fH98qY2LLpN2lKOS+vu00ioVdUw++VNbcj3d7qmr3dVZPl2gLNx40b0798fffv2VT83aNAA8+fPx9atWz29a6SDhduOY9yS3bD4fIIuEHMObvH0TviIQLy7l22pVVvi4G8aPZc/4/tbC5asdPh0gNOxY0d89NFH+OOPP9C0aVP8+uuv2LBhA956660SH5+VlaUudmlpaY5VSe0rk1Ll2NtPr3ZMSs30k+CGiIg8zeMBzvPPP6+ClCuuuAKBgYGqJufVV1/FkCFDSnz8lClTMGnSpGLb16xZg7CwMDfsse9btWqVLs8rXQ0Wa6Auz01ERGSoAOeLL77A559/jnnz5qkanF27duHJJ59EbGwshg8fXuzx48aNw5gxYxw/S3AUFxeHbt26oWbNmm7ee98imRsJbnr27Amz2axLBuf9feuZwSEiIt8PcJ555hmVxbnrrrvUz61atcKxY8dUpqakAKdKlSrqUpSckPU4KfsjvdoyPtqMKQNb4bnFuzV/biIiIkMFOOnp6QgIKDxaXbqqLBaLx/aJ9HNn+3i8t+YQjp/LwKsDWqBhrQiEBQcg8VwGTCagXvVQpGdbim0r734jbTuafBE7d+zEzTd0RI7FZJj98qZ2td+uHRGMtT9tRNfrO+J0WrbH98ub2rDoNmnLb3/ciKuvvhoNalU1zH55Uxvy/Z2h6esdjix0m67f+cZk9fCEM/feey9Wr16NDz/8UHVR7dy5Ew899BDuv/9+NYS8PNJFJUPKk5OT2UWlQRfV8uXL0adPH12zYZ3fWKOGCS5+tCPa1q8OX+OudvQHbEu2pdHwmNTO2bNnER0djdTUVFSrVg0+l8F555131ER/I0eOxJkzZ1TtzcMPP4yXXnrJ07tGOsnNs2XnzIEmtjEREenC4wFOREQEpk+fri7kH3Lyx4kHFemaJCIi0grPMOR2zOAQEZHeGOCQ2+Xm5WdwAnn4ERGRPniGIbfLyR8hFxTAGhwiItIHAxzyWAbHzAwOERHphAEOuZXMSpBrLzLmKCoiItIJAxxyK3twI8wcRUVERDphgEMe6Z4SzOAQEZFeGOCQW2XnT/InGOAQEZFeGOCQR+bAEeyiIiIivTDAIY/U4MgI8QAOEyciIp0wwCG3ysnP4HCSPyIi0hMDHPLMHDjM3hARkY4Y4JBb5dpnMeYkf0REpCMGOORWOY5ZjLlMAxER6YcBDnlmoU1O8kdERDpigEMeWWjTHMQMDhER6YcBDnmoyJiHHhER6YdnGfLIRH+cxZiIiPTEAIfcKse+kjgzOEREpCMGOOSRDA5HURERkZ4Y4JBHholzHhwiItITAxzyzER/nMmYiIh0xACHPDOKijMZExGRjhjgkIcW2+Q8OEREpB8GOORWuRxFRUREbsAAh9yKo6iIiMgdGOCQW3EUFRERuQMDHPLIKCozR1EREZGOGOCQhzI4LDImIiL9MMAhjwwT50R/RESkJwY45FbsoiIiIr8IcBo0aACTyVTsMmrUKE/vGumARcZEROQOQfCwbdu2IS8vz/Hz77//jp49e+L222/36H6RvsPEWYOjkdSTwLk/AXM4kHLMti2qPpBzqWLbKvM77thmvx1RF9EX9gKn6gAXTnp+v7ypDYtui6iL2PObYdqbDdRsaJz98qY2jKoP09k/EXt+l+2YtGQZZr+8ql1T8m8jCj4d4NSqVavQz1OnTkWjRo3QpUsXj+0T6T/RnznA48lD77fjU2DZaMBqCxp9kXxAdQJgPTTV07viE23ZXm4c9fSe+EY7Wme/5+ld8XpBWVZ9nx8Gkp2djc8++wxjxoxR3VQlycrKUhe7tLQ0dZ2Tk6MuVHn29tOzHTNzctV1AKw++3q5ox2RdgpBy0bD5MPBjbB/CnDMnXZtSdpgexq/DQ0V4CxduhQpKSm49957S33MlClTMGnSpGLb16xZg7CwMJ330D+sWrVKt+c+clQyNwH489AfWJ55AL5Mz3aUbptOPh7cEBG5wmS1WvXNETmhd+/eCA4OxrJly0p9TEkZnLi4OCQlJaFmzZpu2lPfJBkHOSlLDZTZbNblbzzz5W4s/TUJz/ZughHXJcAXuaMdVQbn3at8PoNDRL4rLcuKyKkXkJqaimrVqvluBufYsWNYvXo1lixZUubjqlSpoi5FyYlEt5OJn9GzLfPyk5JV/OD10vWYrFkfuG4M8NM/4cvk25epwDW53pakDban6/TOrhgmwJk9ezYuu+wy9O3b19O7QjriYpsaatTNFuBUqwfc8SmQcty2PSoeyEkHzGHlb6vM77hjW/7t3IhYbPlpDa69vhvMF055fr+8qQ2LbJO23Lnma7S5ug2CaiYYZr+8qQ1lW+7Zw9ixcyeu7tYfZku2YfbLq9o1Jf+YRCQwtTv0YoguKovFgoSEBNx9991qFJUzpIsqMjISycnJ7KLSoGtl+fLl6NOnj26ZhwfnbsfqfX/htVtbYfC18fBF7mhH5eAq4PNBQMyVwMPr4Yvc1pZ+gG3JdjSas2fPIjo6WrcuKkOM1ZWuqePHj+P+++/39K6Qm2Yy5jw4GpBvRkK+HRERkfG6qHr16gUDJJLIjWtRmbnYputyMmzX5lANnoyIyLcYIoND/iMnfyZjcyAPPdcbkxkcIqLS8CxDHpnJOIgzGbuOGRwiolIxwCG34igqPTI47KIiIiqKAQ65FVcT16MGJ1zLZyUi8gkMcMgjo6jMAZxyzGXZzOAQEZWGAQ55ZBRVEIuMXcciYyKiUjHAIbfK4Tw4GjYmh4kTEZWGAQ55Zh4cjqJyHYuMiYhKxQCHPFRkzBoc7TI4nMmYiKgoBjjkmSJjBjiuYxcVEVGpGOCQZ4qM2UXlOhYZExGVigEOeWSpBnZRadGYLDImIioNAxzyyFINXItKA6zBISIqFQMcchtZMT7PsRYVi4xdxlFURESlYoBDbh9BJTjRnxYNyi4qIqLSMMAht4+gEhxF5SKrlUXGRERlYIBDnsngcBSVa3KzJMqx3eZq4kRExTDAIbfJzR9BJZjB0aj+RjUmJ/ojIiqKAQ65PYMTGGCCycQiY00CnMBgIDBIg1eHiMi3MMAh98+BwxFUGjQmC4yJiMrCAIfchnPgaIizGBMRlYkBDrm9BoezGGuAGRwiojIxwCH3ryTOEVTa1eCwwJiIqEQMcMhtuJK4hpjBISIqEwMccn8GJ5AjqFxvTBYZExGVhQEOub0Gx8wuKtexi4qIqEwMcMjto6iYwdEAMzhERGVigENunwfHHMjDzvXGZJExEVFZeKYht8l11ODwsHMZMzhERGXimYbcP4qKMxlrmMEJ1eDJiIh8DwMcchuOotJjFBUX2iQiMmyAc/LkSQwdOhQ1a9ZEaGgoWrVqhe3bt3t6t0i3eXAMcdh5t2zW4BARlcXjyxCfP38enTp1Qrdu3bBixQrUqlULBw8eRPXq1T29a1SGpNQMHEm+hPDgQFzKzlPXx8+lq1XC46qHlrhtX9IF9bsXMnPU78dE+nn3SupJ4NyfgDkcyLlku045Zrsvqn7Z25L/sG3Lzfbc/hMRGZjHA5zXX38dcXFxmD17tmNbQkKCR/eJyrZw23GMW7Ib+aO+nbYrMRWdpv6IKQNb4c728f7Z3Ds+BZaNBqy2rFalrX0VqFYHaDNMqz0jIvIJHg9wvvnmG/Tu3Ru333471q1bh7p162LkyJEYMWJEiY/PyspSF7u0tDR1nZOToy5Uefb2K6sdk1IzXQpu7OT35Xk6JFRHTGQI/Kod004haNlomFwNbvJZl41Gbv0uQLVY+OMxSWxLd+IxqR2939cmq9Xq4qnKNSEhtpPbmDFjVJCzbds2jB49Gh988AGGDx9e7PETJ07EpEmTim2fN28ewsJYcKm3g6kmvLs3ULPne6x5HppEevQQdLvoC3vR6dBUTZ9zQ+NxOBvRTNPnJCLSU3p6OgYPHozU1FRUq1bN9wKc4OBgtGvXDhs3bnRse+KJJ1Sgs2nTpgplcKSLKykpSRUpk2vR9KpVq9CzZ0+YzeZSMzhd31zvcgZHyGjxtU939skMTpntKBmcd6/SLoNjCkDuY7t8NoNT3jFJbEt34jGpnbNnzyImJka3AMfjXVTyn2vevHmhbc2aNcPixYtLfHyVKlXUpSj58OMHoDbKasv4aLOqnXlu8W6X/kagyYTXBrZEfHQE/K4da9YH+s0Avnnc9T9iMsHUbwbM8pw+jO9vtqXR8Jh0nd7nbI8HODKC6sCBA4W2/fHHH6hf37c/sL2ZFAbP/vko9p++gBf7XoEWsVEICw5A4rkMOd+iXvVQpGdbSt0m1w2iw/x7FJUUBW/9GDj9K9DzZSD2atucNinHbfdHxdsm8ytvW9w1QGRdz/0/iIgMyuMBzlNPPYWOHTvitddewx133IGtW7fio48+Uhcyrrz8PqrmsZHo0MjWNXhlXPGh/SVto3zWPNt1nVZAwvW22/XaFm+eim4jIiIHj8+41r59e3z11VeYP38+WrZsiZdffhnTp0/HkCFDPL1rVIaMHNvJOdSsXcGx3+GCmUREvpvBETfffLO6kPfItAc4wQxwKo0LZhIR+W4Gh7xTRjYzOC5jBoeISDcMcMhpMrMAu6i0XE/Kj4utiYiM0kV14cIFNQ9NdHS0Y9vnn3+Offv2oXv37mpNKfJt2XkWxzw4Ieyiqpy8HMCSP4snAxwiIs9ncGTV7/Hjxzt+njx5Mu655x4187BMxvXFF19ovY9kMJnZf09QxyJjF+tvRHC4y68JERG5GODIDMO9evVydFW89957eOGFF5CcnKxmIJ42bZqzT0leJj0nV12bA00wB7KX06UAxxQABAZr+OoQEZFw+ux07tw5R/fUL7/8ogKb+++/X/18yy23FJu0j3y3wDiEQ8S1KTCWmRCJiMizAU7t2rWxd+9edfu7775DgwYN0LBhQ/XzpUuXEBRkiJHnpCMWGGuAQ8SJiHTldDQisw0/++yzWL16NZYvX47nnnvOcd/OnTvRpEkTrfeRDIZz4GiAAQ4RkbECnClTpiAiIkLV4owdOxbPP/+84z7pspIAiHxbRn6RMQuMXcA5cIiIjBXgSBfUSy+9VOJ9suQC+U8XFWtwXMAMDhGRripdMCPz3mzfvh2JiYmqyLhOnTo4dOiQqtGRDA/5LtbgaIAZHCIiYwU46enpePDBB7Fw4UIEBATAYrHgxhtvVAHOuHHjkJCQgDfeeEOfvSVDyLQv08BJ/jQIcDiLMRGRIUZRSd3Njz/+iBUrViAtLU3NhWPXp08ffP/991rvIxkMMzgaYIBDRGSsDM6XX36pJvOTyf7y8mzf5O1kyPjRo0e13D8yINbgaFmDE6bFsxERkasZnIsXLyImJqbE+2QeHPKjlcSDOYtxpbHImIhIV06foVq3bo3FixeXeJ9M/NeuXTst9ou8YR4czmRceSwyJiIyVheVLLTZv39/VWx8++23w2QyYevWrZg/fz5mzZqlJv8j38YaHA0wg0NEZKwMTt++fbFgwQJs2LABAwYMUEXGI0eOVKOqPv/8c3Tv3l2fPSXDSHd0UXFZjkpjBoeISFeVOkMNGjRIXf744w+12GaNGjVwxRVXaL93ZPAMDmtwKo1FxkREunL6DDV58mScOnVK3W7atCk6duzoCG6SkpLU/eTbOA+OBthFRURkrABn0qRJOHHiRIn3SeAj95Nv4zBxDbCLiojIWAGO1NxIYXFJJIMTFRWlxX6RgbHIWAPZnMmYiMjjNTgyQkouQoKbp59+ulggk5mZqdam6tSpkz57SgacByfQ07vivZjBISLyfICTnZ2NCxcuODI4MqFfYGDhk1twcDCGDRuGZ599Vp89JcPgPDgaYA0OEZHnA5zhw4eri+jWrRvef/99NGvWTN89I8NiDY4GGOAQERlrmPiaNWv02RPyGuyi0gC7qIiIPB/gvPXWWxgyZAhq166tbpdFanSeeuoprfaPDCgzx6KuuVSDC5jBISLyfIAzduxYXHfddSrAkdtlYYDj23LzLMjOY4DjEosFyOVq4kREHg9wLPKBXMJt8j+ZuX+//hxFVUm5mX/fNoe6/JoQEVFxnGufKlV/I6oE8fBxqXtKMMAhIvJcBmfHjh1OPWmbNm0quz/kRUPES5vwkSpYYBxYBQjgXEJERB4LcNq1a1ehk5l9luO8vL+/5Zdn4sSJxZZ3uPzyy7F///4KPwe5fyXxME7y53qAExymzYtCRESVC3D0HhreokULrF692vFzUFClFjmnCkhKzcCR5EsIDw7Epew8dX38XLoKTOtEBGNnsgnW3aeRUKtqsfvjqofitxOp6nlM+c8VE8kaEqSeBBK32Bo4qj5MZ/9E7PldwKk6gCULMIcDKccc9+P0r7bbpkDb70bW5bFLujh96TR2ndmlbtcOrY3dWbsRdCwI8ZHxyMjNQGhQKE5ePKnur1u1rse2GWEfKrrteOpx7M7ejfiz8cix5hhmv7ypXU/m3w7PDYeeKhRJdOnSRd+dCApCnTp1dP0bBCzcdhzjluyGxVpWawQCB38rt7mSL2Wj09QfMWVgK9zZPt5/m3fHp8A3T0j+stCbqr1smf1e2b+bngxMbwn0mwG0Gab/vpJfWXJwCSZunAhrgWNTLPx5ocf2yZcs/IHt6Kq8jIr39lSGIVIlBw8eRGxsLEJCQtChQwdMmTIF8fElnzSzsrLUxS4tLU1d5+TkqAuVLCk1swLBjXPkueQ5OyRUR0xkiP81fdopBH3zuMpmlaRCFUpWC6zLnkRu/S5AtVht988H2N/TfG8756/0vzBh4wRdXhMib1GhACciIsKpglJ70FER1157LebMmaPqbmQ1cqnHuf766/H777+rv1uUBD9Fa3bs3WhhYaxpKM3BVBMsVu0LWiXI+WL5GjSJ1DBy8hLRF/ZCi6VlTdY8bFkxH2cjuPxJaVatWqVBS/uPwzmHPb0LRB5nskplcAUKgZ0JcCZMqPw3h5SUFNSvX1/NmPzAAw9UKIMTFxengqOaNWtW+u/6Qwan65vrNc3giAATsPbpzv6bwXmndcUyNWWwmgKR+9hOZnBKIJkbCW569uwJs9nsYkv7VwbnpqU3eXo3iMrtotr36D6kpqaiWrVq8EgGRwIcd4mKikLTpk1x6NChEu+vUqWKuhQlH378ACxdfLRZ1cs8t3i3Zq9VoMmE1wa2RHx08UybX6hZH0joAhxZV+LdEkuWG/yYAmHqNx1meS4qFd/fzqkXWQ8dYjpgU9ImHlXkt1yqwUlMTFSXK6+8EuHh2lRDX7x4EX/++SfuueceTZ6P/ibFwC8s2Y08KzBreDuEBgchLDgAiecyIAm62hHB+PbHjbj66qvRoFZVpGdbCt1fr3qoY5tcN4gO4yiqmo1sAU6zAUCLAUBUPHLPHsaOnTtxdbf+MFuyAXMYkHLc9iJExduGics2ua7RkKOoSBeNohqpAKdbXDf0TeiLy0Ivw3frv1PzlMVFxiEzLxMhgSE4demUisZjq8Z6bJsR9qGi2xJTE7Fj5w70vb4vcpFrmP3ypnY9lX87LDcMndFZv08AayV8+OGH1tjYWKvJZLIGBARYf/nlF7V9wIAB1unTpzv1XE8//bR17dq11iNHjlh//vlna48ePazR0dHWM2fOVOj3U1NT5YuyNTk5uTL/Fb+SnZtnrf/ct+qScim7+P3Z2dalS5eqa6qgpaOs1gnVrNZ109iOOuAxWXlTtkyxtpzT0jrjlxlsSw3xmNSOnLfl/C3ncT04Pdf+9OnT8fjjj2PYsGFYuXKlmtzPrmvXrli0aJFTz3fixAncfffdqsj4jjvuUHU0mzdvRq1atZzdNSpHRv4sxCIkmMssaMKSa7sOZH0IGYvFals3LsDE9zr5J6e7qN555x2MHz8eL774YrEZiyVIOXDggFPPt2DBAmd3gSopM38WYikMDg7kh54m8vKnJghggEPGwgCH/J3TZ7mTJ0+iY8eOpRYCSg0NGTuDw3WkNGTJD3CYwSGDsWfXuWYc+SunAxwZwr1169YS79uyZYsaAUUGD3C4jpR28vK7qAIMMWcmkYMF+V1Uzn/ME/kEp4/8ESNG4JVXXsEnn3xSaBbh7777DtOmTcPDDz+sx36SBjLyu6hCzFzBWjPM4JDBMziswSF/5fTXzrFjx+L48eN46KGHHMFMp062+VxHjhypLmTsDA5XAtcQa3DI4DU47KIif1WpvPq//vUvPPnkk2oF8OTkZNSoUQPdu3dHkyZNtN9D0jyDIzU4pPUoKnZRkbGwyJj8ndOfyuvXr1cTRTVs2FBlcQq6dOkSfvnlF3TurOPEPeRyBoddVBpiBocMyr6KOGtwyF85XYPTrVs37N27t8T79u/fr+4ng2dwWGSsHdbgkEGxi4r8ndMBTllrc0oGJzQ01NV9Ip1kFhgmThrJy7Zdcx4cMhh2UZG/q1AXlcwsvHHjRsfP8+bNw4YNGwo9JjMzE19//TWaNWum/V6S5vPgkMbDxFmDQwbDUVTk7yoU4Pzwww+YNGmSoyJfioxLmuRPgpv3339f+70kTWRk20ZVhLCLSvsuKmZwyKDz4JjKX9OeyH+7qCZMmACLxaIu8q1AMjr2n+2XrKws7Nq1q9RZjsnzmMHRsciYMxmTwbCLivyd06OoJJgh78QaHB2HiXMmYzIYdlGRv6v05B1Sc3P48GF1XZQMIyfj4SgqHTCDQwbFUVTk75wOcLKzs/Hoo4/is88+Q25u/rfXIoquMk7GwHlwdMAaHDIorkVF/s7pYeJSbLxy5UrMmTNHpUDfffddzJ49W81k3KBBAyxbtkyfPSWXsQZHz1FUZj2enajS2EVF/s7pAGfRokWYOHEi7rjjDvXzNddcg2HDhqmg57rrrmOA4w01OMFcXVgznOiPDIpdVOTvnD7TnThxAk2bNkVgYCBCQkJw/vx5x31Dhw5VARAZE9ei0gGXaiCjd1GZ+IWG/JPTR35MTAxSUlLU7YSEBKxdu9Zx3x9//KHt3pGm0h1LNXBhSE3IrN7W/HozdlGRQbuoOA8O+Sunz3Rdu3bFTz/9hH79+mHEiBEYO3Ys9u3bh+DgYCxduhSDBw/WZ0/JZRwmrlP2RnCYOBkM58Ehf+d0gPPqq68iOTlZ3X7yySfVt4Qvv/wSGRkZeOKJJ/DSSy/psZ+kARYZ61R/I5jBIYNhkTH5O6cDnDp16qiL3VNPPaUu5EUBDouMdcjgcBQVGXSpBhOXaiD/pEn12c8//4yPP/4YBw4c0OLpSOci4xAutqntLMaCGRwyaheVNh/zRL6fwZEamypVqqi5b8QHH3yAkSNHqtuy/dtvv1Vz4pCxWCxWZOXaPvC4mrjGGRxToHxN1upZiTTBLiryd06H9hs2bMBNN93k+HnKlCl48MEHkZaWhkGDBjlWHSdjycz9e3bpUK4mrg3OgUMGxnlwyN85HeD873//U0PFxZ49e5CYmIjRo0ejatWqGD58OHbv3q3HfpJG3VMiJCiQ7akFzoFDBsalGsjfOR3g1KxZE8eOHVO3v//+exXstGjRwrEGFVcbN3aBcZWgAAQEsDtF0xqcQM4rRMbDLiryd05/Mkv31HPPPYdff/1VrUd1zz33OO77/fff1eR/ZORlGpi90QwzOGRg7KIif+d0gPPPf/5TZWoke9OnT59CNTdfffUVbrzxRq33kTSQkc0CY82xBocMjBP9kb9zOsCJjIzErFmzSi1AJmPiJH86riTOWYzJgKywLdXAYeLkrzhBgp8FOJwDR0PM4JCBsYuK/B0DHH9bSZw1ONphDQ4ZGLuoyN8ZKsCZOnWqmlZc1rgibWXk2LpTwhjg6JDB4SgqMh6OoiJ/Z5hP5m3btuHDDz9E69atPb0rhpeUmoEjyZcQHhyIS9l56vr4uXQVHMZVDy1x2+8n09Tvpmfnqd+PiQz19H/Dh2pwuA4VGXgtKnBaCPJPFQpwBg4ciDfeeAONGzfGp59+ir59+6r5cLRy8eJFDBkyBP/+97/xyiuvaPa8vmjhtuMYt2Q3LLb6Qaf9cuw8Ok39EVMGtsKd7eO13j3/whocMjBmcMjfVSjA+eabb9TcNxLg3Hfffdi0aZOmAc6oUaNU0NSjR49yA5ysrCx1sZMlIkROTo66+LKk1EyXghs7+X15ng4J1RETGeLYbm8/X29HrZiyM9QbyGIKRF6BNmM7aodtWXl5FlvdnSXPUujzke9vHpNGofexWKEAp27duli2bBlq166tvhWcPn0ax48fL/Xx8fEVzwwsWLAAO3bsUF1UFSFrX5W03tWaNWsQFhYGX3Yw1QSLVZuJ+iTI+WL5GjSJLB4trVq1SpO/4evqnduOtgCSz6di0/Llxe5nO2qHbem8i+kX1fXmTZtxMugk21JjPCZdl56eDj2ZrPY8ZhnefvttjB07ttwnk6eSmg+ZCLAiZB2rdu3aqQPFXnvTtWtXXHXVVZg+fXqFMzhxcXFISkrSNKtk1AxO1zfXu5zBEbJaw9qnOxfL4Mhr0bNnT5jNrCspj+nXeQj69glYGvVA3l0L2I464DFZeX2/7oukS0n4tNenaBndkm3JY9Jwzp49q5Z7Sk1NRbVq1TyTwXnqqafQr18/7N+/H7fccgtef/11NG3a1OU//ssvv+DMmTNo06aNY5sER+vXr8e7776rApnAwMIZiypVqqhLUXJC9vWTcny0WdXOPLfYtQVNA00mvDawJeKjI0q83x/aUhu2Is6AoGAElNBebEftsC0rP9FfsDm40PuZbclj0ij0Ps9UeBSV1N/IRVYMHzRokCZrTnXv3r3Y6uNS43PFFVeomp+iwQ1BFQb/678HcTIlE69LkFKzKsKCA5B4LgMmE1CveijSsy2lbpPrBtFhHEWl5WKbnMmYDIgT/ZG/c3qY+OzZsx23MzIykJKSgqioKISGOj/sOCIiAi1btiy0LTw8XHU1Fd1Of8vJs30za1kvCi1iI9XtK+OqF2uikraRDhP9BTLbRcbDUVTk7yo10d+3336L9u3bqwClXr166lp+Xl5CoSVpj+tKGWyYOOfBISNncDgPDvkppzM4S5cuxW233YZ//OMfeOutt9TIKhlVtWjRIlWfs3jxYvTv37/SO7R27dpK/66/yMxfV4rLLhglg2OY+TKJii+2aTLUhPVEbuP0J7MM0b777rvx2WefFdo+evRoDB06FBMnTnQpwKGy5cicFvldVKFm1igZogYnMNiz+0FUAq5FRf7O6dBeRlINGzasxPvuuecedT/pn70RXBncw7jYJhkYi4zJ3zkd4NSoUQMHDhwo8T7ZLveT/vU3Mo9NlSCmnj2KSzWQNxQZG2tNZSLjdlHdeeedeOGFF9SoKRkuLiOoZJIeqcF58cUXMWLECH32lJTMbIuje0omVSQjLLbJGhwy7mKbrMEhf+X0J7MslXDs2DE89NBDePjhh9VEPTLbqHxbkEU5X3vtNX32lJT0HNtJlQXGBsAMDhkYu6jI3zkd4MgswjJSSibo++mnn3D+/HnVLXXdddehVatW+uwlOWRk27qoWH9jAKzBIQPjPDjk7yqdW5dghgGN+3EOHCNmcNhFRQYeRcUaHPJTrD7zMpwDx4g1OJzJmIxbg8NaPfJXDHC8TEZ+kTG7qAyANThkYOyiIn/HAMfLsIvKiDU47KIi4+FEf+TvGOB4GQY4RpzJmF1UZLzsjX2pBq5FRf7KqQAnMzNTrTe1fv16/faIyn4N8kdRcZi4AXAUFRmUPbgRnAeH/JVTAU5ISAjWrVuHvLy/lwsgz2RwWINjAKzBIYN3TwkGOOSvnO6i6tWrF1auXKnP3lC52EVlIJzJmAxeYCw4ior8ldPVkffdd5+awfjChQvo06cPateuXewN1KZNGy33kUqY6C8smCuJexwzOGTwIeKC8+CQv3I6wLn55pvV9fvvv68uBYMb+dYgP7MLSz+cB8dAWINDBsUuKqJKBDhr1qxhu3kQa3AMhDMZkxd0UbEGh/yV0wFOly5d9NkTcqqLSlYTJw/jTMZkUMzgELmwFtW+ffuwfft2JCYm4v7770edOnVw6NAhVZMTERHBttW7yDiYUxh5XF627Zrz4JCBa3BYZEz+yukAJz09HQ8++CAWLlyIgIAAWCwW3HjjjSrAGTduHBISEvDGG2/os7fEDI4Ru6i4FhUZuYuK87mSn3I6DTB27Fj8+OOPWLFiBdLS0gq9kWRU1ffff6/1PlIBrMExYBcVVxMng2EXFVElMjhffvklpk2bpubDKTpaqkGDBjh69CjbVUecB8dAmMEhLwhw2EVF/srpDM7FixcRExNT4n2XLl3SYp+oDFyqwYDDxFmDQwZdqoEjqMifOR3gtG7dGosXLy7xvu+++w7t2rXTYr+oFMzgGHCxTa4mTkZdSZz1N+THnO6iGj9+PPr376+KjW+//XaV/ty6dSvmz5+PWbNmYfny5frsKSmswTEQZnDI4AEOu6fInzmdwenbty8WLFiADRs2YMCAAarIeOTIkWpU1eeff47u3bvrs6cEi8WKzBzbBxdXEzcA1uCQQdkHf7CLivxZpebBGTRokLr88ccfSE5ORo0aNXDFFVdov3dUSFbu34WDXIvKwywWwF7IyRocMug8OAxwyJ9VeqI/0bRpU3Uh93ZPiZAgzmRsiOyNYIBDRu2iQuGFkIn8SaWmw92zZw+GDBmCxo0bIzw8XF0PHTpUbSf9A5wqQQEICOAHlyHqbwQn+iODYRcVUSUyODJSauDAgahXr56qwZGlGf766y8sXboUbdq0wZIlS1SdDum4DlUwszcexwwOGRiLjIkqEeA888wzammGr776Si3VYCeT/8noKrmfAY4+Mu3rUHGhTePMYiw4TJwMhjU4RJXoojpy5AgeffTRQsGNkJ9HjRql7nfGzJkz1dw61apVU5cOHTqoZSCouHSuJG7AEVRBMhbX03tDVHIXFefBIT/mdAZHgpHSghjZ3rJlS6eeT7q6pk6diiZNmqg35dy5c1UmaOfOnWjRooWzu+cXNTh5FiuSUjMQExnq6V3yXqkngXN/AuZwIOeS7TrlmO2+qPrlb7v0v/wnCrA9V2Rdj/1XiIpiFxVRJQKc9957D3fddRfCwsJUDU5kZCRSU1NVl9Wbb76pJvxzRr9+/Qr9/Oqrr6qszubNmxngFLF671/q+ti5dHSa+iOmDGyFO9vH8zh21o5PgWWj/x7m7QpLNjC9JdBvBtBmGF8LMtZMxqZKjSMh8p8AJyIiotCMmNnZ2bj//vvVxWw2IyfHlq6X2zLRn6wyXhmyeOeiRYvUmlbSVVWSrKwsdbGz/y3ZB/t++KKk1Ex8tvlYgQ8wYNyS3eiQUB0xkSGa/A17+/lyOyLtFIKWjYZJi+DGzmqBddmTyK3fBagW6x/t6CZsy0q2W26OY5h40Tbkcclj0ij0PhYrFOA8/fTTuk75vXv3bhXQZGZmomrVqiob1Lx58xIfO2XKFEyaNKnY9jVr1qiskq86mGqCFYVHT0mQ88XyNWgSaetv18qqVavgq6Iv7EUnLYObfCZrHrasmI+zEc38oh3djW3pnFO5p9R1VmZWseVz2JY8Jo1ClnzSk8lqr0bzIMkIHT9+XHV1ffnll/j444+xbt26EoOckjI4cXFxSEpKQs2aNeHLGZwu/1yfv0awjUyFs/bpzppmcOTDr2fPniob57MZnHev0jaDI0kcUyByH9vpyOD4fDu6Cduycvae3YuhPwxFnbA6WD7AFuCwLXlMGs3Zs2cRExOjzv0yyMhQMxlrJTg4WE0WKNq2bYtt27ZhxowZ+PDDD4s9tkqVKupSlJxIfPlkEh9tRq8WtfHDHlsdTqDJhNcGtkR8dITmf8un27JmfVu9zDePa/ecpkCY+k2HWZ7bX9rRzdiWzgkItNXeBAYEFjsG2ZY8Jo1C78/HSgU4stim1MokJiaqbqWifvvtN5d2ymKxFMrSkE3T2hEqwOndojYm3tKCo6gqS4qB178JpBwFbp4O1GwMmMOAlOO2+6PigZz0im+r0ZCjqMiQ8+BwqQbyZ04HOC+88IIa1i2ZFlmHSrIvrhg3bhxuuukmxMfH48KFC5g3bx7Wrl2LH374waXn9eWZjBOiqzK40Woem9irgNirbbfrtS3+uIpuIzIQLtVAVIkAZ9asWZg8eTJefPFFTdrvzJkzGDZsmKqhkSHnMs+OBDdSv0Alz4PDmYw1IJkXIRkZIh/DYeJEleyiuvbaazVru08++YSvg7MBTjDntnBZTobt2szJEsn3cKI/okos1fDggw+qbiRyP65FpRGLBcjNrx1jBod8kDV/vCWXaiB/5nQG5+WXX8bo0aPRqVMnNalfVFRUoftlvpynnnpKy32kIjU4IVxs0zW5+dkbwQwO+SBmcIgqEeD8+OOPar0oKQjetGlTsfsZ4Liji6rwhH9Uye4pEcQuKvI9eVbbZwWXaiB/5nQXlawY3q5dOzX7sAzlliHdBS+y3ALpm8FhkbFGBcYS3ASwnol8D0dREVUigyNz37zzzjtcCNMDOIpKI9n2EVTM3pCPd1FBvyV2iIzO6a+v1113HQ4cOKDP3lCFApwQdlG5hkPEyV+KjLmaOPkxpzM4r732GoYPH64m+OvRo0exImNRo0YNrfaPCsjItn0rYxeVizhEnHwc58EhqkSA0759e3X9yCOPlLrCOOtw9MFh4hphgEM+jqOoiCo5k3FpgQ3pWzRo76IKYxeVa9hFRf5SZOx8FQKR/wY49957rz57QmXKybMiz2L70GINjouYwSE/WWyTNTjkzxjeewl79kawBsdFzOCQj2MXFVElMjgJCQnldlEdPnyYbatT/U1QgAnmQMalLmEGh3wc58EhqkSA079//2IBzvnz57Fu3Tr1pho4cCDbVQec5E+PDA7nwSEfH0XFJD35MacDnOnTp5e4PTs7GwMGDFAZHtIe58DRI4MTpuWzEhmuBocDQsifadbXIfPiPPbYY5g2bZpWT0kFcBZjDbGLinwcu6iINC4yTk5OVotwkvYyuQ6VdlhkTD6ORcZEleiiWrJkSYndU/v27cO7776LG264ge2qA3ZRaYgZHPJxrMEhqkSAM2jQoBK3m81mVWAsC3GS9tIdGRyOoHJZziXbdTBrcMg3cS0qokoEOEeOHCm2LSQkBJdddhkL2nTEGhwNsciYfBy7qIgqEeDUr1+f7ebBeXDCgp1+yagodlGRj2MXFVElAhz7YppbtmzBiRMnkJmZWez+YcOGsW11mgcnxBzItnUVi4zJx3EUFVElApwdO3aoWpvExETHm6ggmXeBAY6OXVTBrMFxGTM45OM4Dw5RJQKcRx99FJGRkZg7dy6aN2+u5r8h/bEGR0PM4JC/dFGZ+IWI/JfTAc6ePXuwaNEidOnSRZ89ohJxHhwNMYND/tJFxaUayI85Hd43bdoUaWlp+uwNlYrz4GiIAQ75OI6iIqpEgPP2229jypQp2L9/P9vPjTJybCnnUBYZu0a+2bKLinwc58EhqkQXlaw3dfr0abRs2RKxsbGIiooqVmT866+/sm01xtXENZKXDeTXJ3A1cfJVrMEhqkSA07ZtW07o58F5cEKDOUzcJfbsjeBq4uTrXVQweXpXiLwnwJkzZ44+e0IVq8FhF5U29TcBQUCgmUcd+SR2URFpvJo46YddVBrhMg3kB9hFRcQAxwsn+mMXlUscBcahGrwqRMbEUVREBghwZERW+/btERERoRbsHDBgAA4cOODp3TIcZnA0km0PcLiSOPkuzoNDVMm1qLS0bt06jBo1SgU5ubm5eOGFF9CrVy/s3bsX4eHh8EVJqRnYfvScKtaOqx6KS9l5CA8OxPFz6aVuu5CVo373Ylaup3ffOFJPAuf+BMzhQM4l23XKMdt9UfVL3pa45e/h4vL7kXU9t/9EOuFSDUQGCHC+//77YkXMksn55Zdf0LlzZ/iahduO4/nFu1F8Fa+KGfzvzZgysBXubB8Pv7bjU2DZ6L+HfDsr5SgwvSXQbwbQhovDkm9hDQ6RAQKcolJTU9V1jRo1Srw/KytLXezssyrn5OSoi5ElpWa6FNwIixUYt2Q3OiRUR0xkiIZ7Z2vDgteGlXYKQctGw1TZ4MbOaoF12ZPIrd8FqBbrf+3oBdiWlZObl5/ptRRvQx6XPCaNQu9j0WQtaUlwD7FYLLjllluQkpKCDRs2lPiYiRMnYtKkScW2z5s3D2Fhxq6rOJhqwrt7tSkSfqx5HppEGualc6voC3vR6dBUzZ5vQ+NxOBvRTLPnI/K0lRkrsT5rPTpW6Yg+oX08vTtEJUpPT8fgwYNVYqNatWrw6QBHVipfsWKFCm7q1atX4QxOXFwckpKSULNmTRg9g9P5n+tdfp4AE7D26c66ZHBWrVqFnj17wmw2GzuD8+5VrmdwJIljCkTuYzs1z+B4RTt6AbZl5czYOQNz983F0CuGYkybMWxLHpOGdPbsWcTExOgW4Bimi0qWgPj222+xfv36UoMbUaVKFXUpSk4kRj+ZxEebcVf7OCzYlljp5wg0mfDawJaIj46AXgzfljXr22pnvnlCTWlWaaZAmPpNh1mezx/b0YuwLZ1jkm9B8gEfGFTsGGRb8pg0Cr0/Hz0e4EgC6fHHH8dXX32FtWvXIiEhAb7syrgoFeC0jI3Eo10bol71UKRnWxAWHIDEcxkwmVDmtgbRYYiJ5BwuqjB41zzg+Cag6zigfifb0O+U47aGjoq3zXlT2ja5rtGQo6jIJ3EeHCIDBDgyRFzqZ77++ms1F44s5CkiIyMRGhrqs/PZJNQKR9/WhbtFroyrXuzxJW2jfPbe1cuaAwnX227Xa1u8eUraRuQPo6g8P9UZkcd4/OifOXOm6n/r2rWr6ouzXxYuXAifnpHY7PGm96FZiY1dXE7kblyLisgAGRwD1Ti7d1VwLpqp4bpSvpfpI3IFu6iIDJDB8Tf2LqoQrinlOgY4RGUGOIEmrl1H/osBjse6qPjB4zJ2URGVmRmXZV6I/BUDHDdjgKMhZnCIylyLikXG5M8Y4HioBieMXVSusViA3PwanGDfXJSVyOXVxE38iCf/xaPfzdLtNTjsonKNPbgRLDImKoRFxkQMcDxWZBzKDI423VMiiKOoiAriauJEDHDcjsPENS4wDgoBApiIJCpxHhwm6cmP8czgZiwy1ggLjIlKxS4qIgY4HgtwOA+OizhEnKhU7KIiYoDjdhnZtuGbnAfHRczgEJWKo6iIGOC4HWtwtM7gsMCYqLR5cEzgRH/kv1iD4+ZvVY4aHI6i0iiDw4U2iYpiFxURAxy3ysmzIs9iG93AeXBcbUwutElUGgY4RAxw3MqevRGswXERi4yJSsVRVEQMcDxSfxMYYII5kH3jLmEGh6j8ImNWIZAfYw2OJ2YxNgdylV9XsciYqPzFNrkWFfkxBjiemAOH61C5jkXGRKViFxURAxyPLLTJlcQ1kG0fJs5RVERFcR4cIgY4bsU5cDTEImOi8jM4nAeH/FiQp3fAH2twuEyDBlhkTORzNTh5eXnIycmBkcn+BQUFITMzU+0vlc5sNiMwMBCewgDHIwtteteHjiGxyJjIZ7qoZH9Pnz6NlJQUeMO+1qlTB4mJiRwsUgFRUVGqvUwm948cZoDjRlxJXEMsMibymSJje3Bz2WWXISwszND7bbFYcPHiRVStWhUBAd4RQHoqEExPT8eZM2fUzzExMW7fBwY4nqjB4TINrmMXFVH5Mxl7wUBZ6eaxBzc1a9aE0UmAk52djZCQEAY45QgNta0VKEGOvL7u7q4y/tHvizU4HCbuOhYZE5XKCu/porLX3EjmhnxPWP7r6onaKuMf/T6EXVQaYgaHyGe6qLxtX8k7XlcGOG7EAEdDzOAQlYpLNRAxwHGrTPtSDazBcR0zOESl4mriRAxw3IpLNWiIAQ5RufPgsNvHPSPARo8ejcaNG6vC49q1a6NTp06YOXOmGkVk16BBA/V6yCU8PBxt2rTBokWLHPfLY8eNG4dGjRqp56lVqxa6dOmCr7/+utS/PWfOHDUMuzT33nuv+ntTp04ttH3p0qWlHhtXXHEFqlSpov5fdv369cONN95Y4uN/+ukn9Vy//fYbjIZdVG6UkWNxLLZJLpA5PthFRVTGW8R7ioy1lpSagY1/JqtrvR0+fBhXX301Vq5ciddeew07d+7Epk2b8Oyzz+Lbb7/F6tWrCz1+8uTJSEpKUo9r37497rzzTmzcuFHd98gjj2DJkiV45513sH//fnz//fcYNGgQzp4969I+hoSE4PXXX8f58+fLfeyGDRuQkZGh/u7cuXMd2x944AGsWrUKJ06cKPY7s2fPRrt27dC6dWsYDYeJe2I1cXZRuSYvB7DmzyBqtg1DJCLvHCZeWoBmz3g7Y/EvJzDhmz2wWCW4Aybd0gK3ta3n1HPIF9CKZr5GjhypZjXevn27ysrYNWzYEP3793cEmnYRERFq0ju5vPfee/jss8+wbNkydOzYEd988w1mzJiBPn36ODI+bdu2hat69OiBQ4cOYcqUKXjjjTfKfOwnn3yCwYMHq8yRZKWee+45tf3mm29WGSXJGL344ouOx8t8QJKFmjZtGoyIAY4bcS0qjdizN4KLbRL5XBeVBDfNX/rBpeeQIGf813vUxRl7J/dGWHD5p0bJrNgzNwWDm4LKan8JjGQpA5lTR0jQs3z5cgwcOFAFQloJDAxU+yiByxNPPIF69UoO+C5cuKCClS1btqhuqtTUVNX9dP3116t9HTZsmApw/u///s/x/5LHyzxGd999N4zI4+H9+vXrVf9ebGysajTpG/RV6dm56poZHI0CnIAgICjY5deFyNf4cxeVu0hWRNr58ssvL7Q9OjpazXIsF3sGpCgJaiSjIkHEDTfcoLZ99NFHqrtKJjuU7qunnnoKP//8syb7euutt+Kqq67ChAkTSn3MggUL0KRJE7Ro0UIFRXfddZfK6Njdf//9+PPPP7Fu3bpC3VO33XYbIiMjYUQez+BcunQJV155pWo8iVyNTPp0tx89pwKxuOqhuJSdh/DgQBw/l17mNvvtc5eyC3VV+b3Uk8C5PwFzOJByzNYcUfWBnEvFtxW8ffag7XZgMNRzRNb1+6Yk8qVRVNJNJJkUZ5xOzUSPt9apzI2ddFOtHtMFdSJDnPrbrti6daua7XjIkCHIysoqdJ8EPNLFIwt1SgAkxb99+/ZV93Xu3FnV9GzevFkFOv/9739Vl9WkSZMwfvx4uOr1119XwdTYsWNLvH/WrFkYOnSo42e5LV1VUhMkGSXJ6khXmjyua9euKsCTDI/UFRmVxwOcm266SV2MbuG243h+8e78+UFd89QXu5CVm4c728fDb+34FFg2Gsj/IK50Jmd6S6DfDKDNMC33jsg3JvqDd3ZRyRfCinQTFdSwVlVMGdgKLyz5HXlWKwJNJrw2sKXargcZNSX7eeDAgcL70bBhoWUKCnrmmWfUyCYJbmS0VdEuLOmyki4huUgw9Morr6gAQm4HB7uWre7cuTN69+6tRmrJPhS0d+9eFVhJcFYw6yTdT5LZGTFihKPY+PHHH1f1Q5K9kRFfEgQZlccDHGdJRFwwKk5LS3NMA63XVNBJqZl4bvFuzZ5PssfjluxGh4TqiHHim4Xe7O2n+5TaaacQtGw0TK4EN3ZWC6zLnkRu/S5AtVj4VTv6AbalawGOJc9SrA2NdlzK/khXj2Q95OKK29vWw3WNa+LY2XTUrxmGmMhQl5+ztO6/GjVqqALed999F6NGjSqxDsf+/7KT7id7ACT3FS1CLkqyJrm5uWoIudTBFGV/7tL+j9b8v2G/X2pxZHh606ZNC/3exx9/rAIgydYUJDU30k0lgY2Q0VVSfCzF0Z9++qka+VXe/0P+htwvr3PRtaj0Pha9LsCRfktJ2RW1Zs0a3dYyOZgqUba2Q7sljfrF8jVoEqlFTkhbMhxQT9EX9qKTFsFNPpM1D1tWzMfZiGbwp3b0J2xL51y8dFFdb9m8BUlBSYZuSzlxS4GtjMixF9y6ItwENI82y+kTaWn6nUClKFe6fWR+GBkmLZkPqV+RFcZ37NiBffv2oWXLlo4v4XKil64p+89FyUglqWeRYecSPMlQcenOkmyOKOn35Pkky1K0VkeyPZdffrkKICRAsv9u/fr1cfvttzsCGdkuj/nPf/6jMjvx8YV7FWQY+9tvv60Kj5s1a+ao53nhhRfU/1/KSkr7/9jJaypDz6XeVvaloILzBOnB6wIceRHGjBnj+FkaNy4uDt26ddNtJVrJ4Ly7d72mzyl9w3f06Wa4DI58+PXs2VOlSnWTdhWs776hTQZHvqWYAnHtTXcbKoPjlnb0A2zLypn59UzgElTNROvo1oZuSzlJJyYmqm4bmbPF6CQbISd3qUuR+lEJZuSLt3QnyTwxMkle8+bNVa3Lo48+6vjiLYGP/P+qVatW4vPK8HAZlSTPIyd+GXgj9TlSf1Pa78jzSWAo2ZeCpOvojz/+UK+zBJAFf1+yOF999ZW6LdsXL16Mc+fOqZFQRf+OFDtLYPPFF1/gzTffVNsefvhhFRBJaUnRAuvSXl/prpN9LPr6ujrHj88FOHLwyKUoeSH1etPGR5vRpUk01h1M1uT57H3D8dHaDQXUkp5tqdSsD3R6Cthge8O4xBQIU7/pMMtz+ls7+hG2ZeVWEw8OCi52DBqtLSUDIbUoEgDIxejs3Tr2fa5bt67qpirP0aNHy7xfsiJycYYMzpFLaeYWmKzPTrrICpZ5SEZHLqWR+pyCZJbm8rrWCpI2krYq6bjT+zj0ugDHU+pHhwMHk3Fji9rod2Us6lUPRXq2BWHBAUg8lwGpFSttW9H7G0Tb+ob9WsMutgBHRkUNmg2kHLdtj4q3FQ/L/DYFt5V0v1zXaMhRVESlzIPjraOoiHwiwJH0mgw3szty5Ah27dql+iCL9gd6kn1od+u4KPRtXbgr5Mq46sUeX3BbSff7PftaUuHRQL22tktRBbeVdD8RlT2Kyksn+iPyiQBHpriW+hk7e33N8OHDVQW3UdinDec6UhrhWlJEuuFEf0QGCHBkwiBn+vM8hcssaIyrgRPpxtvnwSHSAjtonc3gcKFMjTM4fl6LRKRjkTFrcMifMcBxdiVwF6fxpnzsoiLSjbcv1UCkBR79FZSRY/vAYAZHI+yiItINi4yJGOBUGGtwNMYMDpH+Rcb8Dkt+jBmcCkrPtk0xHcIuKo0zOPosr0HkzzgPDhEDHOdrcFhkrA0WGRPphl1URAxwKizTXoPDDI42mMEh0g3nwSFigFMhuXkWZOcxwNEUi4yJ9B9F5Y9VCKkngSPrbdducPr0aYwePRqNGzdWi0nWrl1brdc0c+bMElfLloU5AwMDMW3atGL3yeS2UVFRFf7ba9euVbNVp6SkFNq+bt06tQi1uPfee9Vjpk6dWugxS5cuLXWm6yuuuEKt+Sj/N7t+/fqpldNL8tNPP6nn+u2332Akfnj0Oy8z9+9Vr9lFpREWGRPpXoPjtUs1SJF09iXnL1v/DUxvCcztZ7uWn519Dicmnj18+DCuvvpqrFy5Uq3SvXPnTmzatAnPPvssvv32W6xevbrY78yaNUvdL9d6+frrr1VAYieB1+uvv47z58+X+7sbNmxARkYGBg0aVGixzgceeECtRi8rphc1e/ZstGvXDq1b21auNwqPz2TsTfU3okoQY0JNMINDpBuv76KSL0CvFV7zz2mSxVo+1nZxxgungODwCj105MiRCAoKUksOhYeHF1qxu3///sVm6ZfMigQPkydPxqeffoqNGzeiY8eO0No333xTaIXzHj16qDUfJXv0xhtvlPm7n3zyCQYPHowuXbqozNRzzz2ntt98882oVauWyjK9+OKLhdaTXLRoUYkZKU/z0qPfc0PEvfYbkdGwyJhIN5zoT39nz55VmZtRo0YVCm4KKnq+kODh7rvvhtlsVtfys9b27NmDM2fO4IYbbnBsky4xyTC98847JWZg7C5cuKCClaFDh6Jnz55ITU1V3U9CArlhw4apAKdg4CaPz8vLU/8fo2EGx4llGsI4gko72falGjhMnEhLcvKxL9XgtWtRyeeCZFKckXYKeO8aW+bGzhQIjNoCVHMiG1TBzyTJiEhbX3755YW2R0dHIzMzU92W4Ee6htTupaXhyy+/VF1YQoKI66+/HjNmzEDVqlWhZfdU7969ERwcXGj7rbfeiquuugoTJkwoNbBasGABmjRpghYtWqif77rrLvVY2U9x//33q0yNZKJkHUl799Rtt92GyMhIGA0zOE50UXEOHA2xi4pIF/bgxqu7qCTzId1EzlyimwD9ZtiCGvUcgUC/6bbtzjyPi1n6rVu3YteuXSpIyMrKcmyfP38+GjVqhCuvvFL9LMFG/fr1sXDhQmhJApxbbrmlxPsk2JK6mn379pV4v9QFSeBlJ7clQyOZHXvxsXSp2euHJMiTDI/U5xiRlx797sWFNnXAImMiXbunvDrAqaw2w4AndwPDv7Vdy886kVFT0gV14MCBQtul/kbuCw0tvJCwZEKk+0i6euyXvXv3alpsnJSUpAqd+/btW+L9nTt3VtmdcePGFbtP9mXz5s2qANq+f//4xz/USDDJ7NhJMLN48WIV9Ej2RoI2qdcxIj87+l0McDgHjnaYwSHSRcH6CL+sGYysCyRcb7vWUc2aNVWdihTzXrp0qczH7t69WxUiy7Buye7YL/KzdFnt379fk31atmyZyrDUqFGj1MfIcHF5nL2rrGAAJgHQr7/+Wmgfx4wZU6hL64477kBAQADmzZunCqWl28qoxxlrcCogkyuJa8uSB+Tlp25Zg0OkyxBxv50Hx43ef/99NeeNDJGeOHGiGiYtJ/9t27apoKVt27bqcRIgXHPNNSqAKKp9+/bqfvsoJCnYlcCiIJmTplmzZhUaPVVa95Rdq1atMGTIEPzrX/9ybMvJycF//vMfNbqrZcuWKOjBBx/EW2+9pbJP0u0m9UJ33nmnygJJXZHMs2NUPPqdyOCEsMhY2+yNMBdO4xKRa/y6i8rNpHtGuoRkGLac8KW+RoIdGa00duxYvPzyy8jOzsZnn32mCnFLItslEyJBhn3YtcytU/BScE6b0kgW6b///W+5AY6QQMZisRQKjGRUmBQiFyWBlVwKZnGkm0rm1JHurthYF4fz64gZnApId2Rw+GGhCQY4RLrx+y4qN4uJiVEBjVxKk5ycXOp9UvMiFyHZEGcyIjKSyf56L1myBAkJCar+p6A5c+YU+70GDRoUKoCWIEsyR6WR+pyCOnToUGyOHyPiGdvJeXBI4wJjg/bdEnkrZnD8k3Qd2Yekkw0zOBXAlcQ1xgJjIt2wBsc/9erVy9O7YDjM4DhTg8MMjjY4RJxIN+yiIrJhgFMBHCauMS7TQKSbPOvftRQsMiZ/xgDHiRocLtWgEXZREbmlBsdrl2og0gADnArgUg0aYxcVke5dVBLcGHUCNiJ3YIBTAVyqQWPM4BDphiuJE9kwwKmAjBxbypfDxDXCDA6RbhwriTN7Q36OAU4FcKkGjTGDQ6R7BifQvqo2kZ9igFMBXKpBYxxFRaQbdlER2TDAqQAOE9crgxOm9TMT+b2CRcb+6PSl09iatFVdu0NiYqJaUVvWZAoODkb9+vUxevRotbZTweUSpMuwtMt9993neOy///1vtaaVzEwcFRWl1qKaMmVKqX//6NGj6jmKLtBZ9G/feOONKCglJUVtlxXNi3r44YcRGBiIRYsWOba9+eabqF69OjIzM4s9Pj09HdWqVSu0gKcRMMBxZiZjTvSnDXZREek+k7E3z4EjQVp6TrrTlwX7F6D3l73xwMoH1LX87OxzOLPG0uHDh9XimgcPHsT8+fNx6NAhfPDBB2rRS1mv6dy5c+pxsvp2UlJSscv48eNVUDRixAj1uFmzZuHJJ5/EE088oQKWn3/+Wa1TJQtwuiIoKAirV6/GmjVryn2sBCsLFixQf1f2x+6ee+5RC3rKmldFffnll2pR0aFDh8JIuFSDM2tRcTVxbbDImEj3LipvLjLOyM3AtfOudTnQe3XLq+rijC2DtyCsgtnlUaNGqQBl5cqVCA0NVdvi4+NV1kVWGv+///s/zJw5U91nv99u3bp1KjMj93fs2NGxqvcdd9yhVuu2a9GiBVwVHh6unvf555/Hli1bynysZG2aN2+uHitZKclQxcXF4bLLLlOrmkvQM3jw4EK/I9sGDBiAGjVqwEgME+K/9957aoXTkJAQXHvttdi6dSuMt5o4i/Y0kV1gsU0i0pQ9A+HNGRxvINmZH374ASNHjiwWvNSpUwdDhgzBwoULS8wIHTt2DLfffrvqCnrwwQcL/d7mzZvV/VqbOHEidu/erbItZfnkk09UJiYyMhI33XRTodXIJfD68ccfC+2fZLHWr19fKCgzCkNkcOQgGDNmjErtSXAzffp09O7dGwcOHFBRY0Ws2rIA5zKOqp7nBrWb40JGCiJCo3D0rz0ub6sXGIyQ4HT8vi8Ze/LkhbWibs3myMhORWhwJE6e3VfmtvLuN8q22lFNceL8Cqz6eSfiarXQ7++d+x0IC0XdiyeQkbQVoUGhOHnxpHod61atq769Fd1W3v1G2nY89Th2Z+9G/Nl45FhzDLNf3tSu9tu1Q2vjcM5h/H72d/yV8ZfH98sb2tBee5JnyVO364TXgbeR/4tkUpzxV/pfGLB0QOHFRk0BWNp/KWqH1Xbqb1eEdEtJ8NKsWbMS75ft58+fx//+979C5zHpApJsh2Rm5FxX0IQJEzBw4ED1Zb9p06aqm6tPnz4YNGgQAgJcC1hjY2NVbZBkleTvl/Z/kgDL3g0lgY6cm1988UWVEZTzsjzP7NmzVcAkJACSDE/37t1hNIYIcN566y3VB2kvtJJA57vvvlNpL0mTVcTkY+8iMCz/v3NhueRn5auM7drVbQ1st589uPLvbYetxR9X1rbK/I67t9lvH9sJHP1Sv78XbAJq1wJOfQskfVeh19cbLfxhoad3wWfM+uHvWgCqmIs5F9F7cW9M6DABA5sM9Kpmk5NpRbuJ7BIiEzCh4wRM2jRJddNJcCP/d9mup/JqdqQLqyDJdEiB76pVq1RtTEExMTHYtGkTfv/9d5UV2bhxI4YPH46PP/4Y33//vctBznPPPYcPP/xQnVuly6oo2S5BTHR0tPpZgit71kYCGCk8lv2RoEaCMfm/z507V527Xd03nwxwpDDpl19+wbhx4xzbpKF69OihXuiisrKy1MUuLS3NdqNgf7P9tpG2GWEfjLqvRKQLOdHLCf+ay65BDbOtPiInJ8dQrS37IydKi8WiLq4Y0GgAOtTpgOMXjiM+Ih61w2u7/JylBTRSYyOB2N69e9G/f/9ij5PttWrVUqOL7PvwxhtvYNmyZfjpp59UvUpp+yY1MHJ55JFH8NBDD6FLly6qQLhbt27FHmt/jtLaz1LgftkXSRpMmjRJBS8Ffy8vL08FK6dPny4UeMl26bay/+17771X1Q5J0bL8ntToSNBT2v9FtkubyessAVJBeh+LHg9wkpOTVQPWrl04hSg/79+/v9jjpWHlxSEioooFOYtWLUJDc0P1s2QOjEROplJ7IiOF5Auvq0IRisvDLgfyCnwB1oFkZuSk//7776th4gXrcP766y/MmzdPZT/s+yDtLl09ko1JSEio8L5J94+Qrq6Sfsc+wkpGOJV0f2Zmpgow7PcNGzYMM2bMwLRp0xxdZnLfihUrcOHCBVX8XDAQ2bdvHx577DEVyEhdjgRtnTp1wkcffaSet2vXrmr4eGn/H3lNMzIyVEYqNze30H3yt306wHGWZHqkT9BOGtV+ABARUWHSVXN7z9tVBkdOsj179oTZbDZMM8kJWE6eMu+LDDIxOjmpSyAQERGhgpvrrrtODQOfPHmyClz27NmjuoKkhuaVV15R/y+pbZFMjAQ8vXr1KnZil2BJMjpSsCw1LhI41atXTw0lf/XVV1VQIV1EkoEpSp5fnDhxQo2WKqhFixaqTSXTZP9duZYkweOPP65+DgsLU9tkaLhkdSR4Keiaa65RgZlknmT/hJSUSIG0vVurpP0q+PpK8Ne5c+dir2/BuYJ8MsCRvj6JFiXiLUh+lqi+qCpVqqhLMQX7Qa1l1Jd4apsR9sGo+0pEurDXodSLrOfoDpDgxkgBjmTw5QQspQlGrOMoyt4VI/t8+eWXY9u2barg9q677sKZM2dUACSFwv/5z39U8CAkeJC6G8l6yKUo6YKSCfck+JSAQepQ5eQv50cpNJZ5dSTIKYm9zYoO3RYSONrvL9i2UjPz9ttvq2402S7ZoeXLl6usU9HXQH6+9dZb1X5JJkfICDCZq0fO3fJ/Let1k/ukrUo67vQ+Dk1WZ2Y10omMnJIo8Z133nEcQDKXgDRmeUXGksGRtNn8797FuYzjau7O+rWb4WJGGqqGVsPRv/Zpti2mejTS0/9Ufze25hXIzE5DSHA1nDq7v8xt5d1vlG2XRTXGxh3fI6FBQ8TVaq7/PsR3QmZIBEICQ3Dq0ikZbIXYqrHIzMsstq28+420LTE1ETt27kDf6/siF7mG2S9valf77ctCL8PaDWvR9bquOJN5xuP75U1tKNviIuIco6gkwJGTmHxLN1KAI9/wjxw5orIf3pDBkfOTnHcka1HSiV2Kb2XgjGTL/vGPf8DfZZbx+tqDuNTU1DKzQF4d4MgwcSlSkupuCXRk6NwXX3yhanCK1uaUFuBILU/NmjXdts++yKgfgN6G7ci2NCKjHpe+FuAIGUYtJ23JcnhDVspXAxyPd1EJ6b+UFNlLL72kKrivuuoqNSSuvOCGiIjIaAquLUWeY4gAR0h3lL1/j4iIiMgV/p07IyIiQzBAtQT52OvKAIeIiDzGXg+k95wo5Bn219UTdV+G6aIiIiL/I0ONo6Ki1BBrIUOrjbwSuhQZy+R1Ujzr7wXE5WVuJLiR11Ve36KzGLsDAxwiIvIo+5xn9iDH6CdumZlXJq8zciBmFBLclDSnnTswwCEiIo+SQEEWmpRVt422VlZRsn+y7IDMzGuk4fZGZDabPZK5sWOAQ0REhiAnQ0+eECtC9k/WVJI5XRjgGBs7EImIiMjnMMAhIiIin8MAh4iIiHxOkK9MIiTL17M/1PXiORnWJ+ussC3ZjkbAY5JtaTQ8JrUj5209JwP0+gBHFusSspAXEREReRc5j8ui2Vrz+gCnRo0a6vr48eO6NJA/kcxNXFwcEhMTdVnZ1V+wHdmWRsTjku1oNLKKeHx8vOM8rjWvD3DsM0lKcMOTsjakHdmWbEcj4THJtjQaHpPa0WtGaBYZExERkc9hgENEREQ+x+sDnCpVqmDChAnqmtiWRsBjkm1pRDwu2Y7+dkyarHqNzyIiIiLyEK/P4BAREREVxQCHiIiIfA4DHCIiIvI5DHCIiIjI53h9gPPee++hQYMGCAkJwbXXXoutW7d6epcMbeLEiTCZTIUuV1xxheP+zMxMjBo1CjVr1kTVqlVx22234a+//vLoPhvF+vXr0a9fP8TGxqp2W7p0aaH7pV7/pZdeQkxMDEJDQ9GjRw8cPHiw0GPOnTuHIUOGqEnCoqKi8MADD+DixYvwN+W15b333lvsOL3xxhsLPYZtCUyZMgXt27dHREQELrvsMgwYMAAHDhwo1E4VeU/LTPB9+/ZFWFiYep5nnnkGubm58BcVaceuXbsWOyYfeeSRQo/x93YUM2fOROvWrR0TIXbo0AErVqyAJ45Hrw5wFi5ciDFjxqhhZjt27MCVV16J3r1748yZM57eNUNr0aIFkpKSHJcNGzY47nvqqaewbNkyLFq0COvWrcOpU6cwcOBAj+6vUVy6dEkdYxJUl+SNN97Av/71L3zwwQfYsmULwsPD1fEob2g7CW727NmDVatW4dtvv1Un+oceegj+pry2FBLQFDxO58+fX+h+tiXUe1ROFps3b1bHlCwE2atXL9W+FX1P5+XlqZNJdnY2Nm7ciLlz52LOnDkqWPcXFWlHMWLEiELHpLzn7diONvXq1cPUqVPxyy+/YPv27bjhhhvQv39/9bnn9uPR6sWuueYa66hRoxw/5+XlWWNjY61Tpkzx6H4Z2YQJE6xXXnllifelpKRYzWazddGiRY5t+/btk2kErJs2bXLjXhqftMlXX33l+NlisVjr1KljnTZtWqH2rFKlinX+/Pnq571796rf27Ztm+MxK1assJpMJuvJkyet/qpoW4rhw4db+/fvX+rvsC1LdubMGdWe69atq/B7evny5daAgADr6dOnHY+ZOXOmtVq1atasrCyrPyrajqJLly7W0aNHl/o7bMfSVa9e3frxxx+7/Xj02gyORHcSIUo3QMH1LOTnTZs2eXTfjE66TaRroGHDhupbsKQDhbSnfHMp2KbSfSWLobFNy3bkyBGcPn26UNvJ+mjSbWpvO7mWbql27do5HiOPl+NWMj5U2Nq1a1V6+vLLL8ejjz6qVhy2Y1uWvnihsC9eWJH3tFy3atUKtWvXdjxGMo+yOKf9W7e/t6Pd559/jujoaLRs2RLjxo1Denq64z62Y3GSjVmwYIHKhElXlbuPR69dbDM5OVk1XsFGEPLz/v37PbZfRicnXEn3yUlDUqyTJk3C9ddfj99//12doIODg9VJuGibyn1UOnv7lHQ82u+TazlhFxQUFKQ+RNm+xbunJG2dkJCAP//8Ey+88AJuuukm9eEXGBjItiyBxWLBk08+iU6dOqkTsP2YK+89LdclHbcFj2t/b0cxePBg1K9fX305/O233/Dcc8+pOp0lS5ao+9mOf9u9e7cKaKR7XupsvvrqKzRv3hy7du1y6/HotQEOVY6cJOykEEwCHnnTfvHFF6owlsgI7rrrLsdt+TYnx2qjRo1UVqd79+4e3TejkhoS+aJSsKaOtGvHgrVyckzKYAI5FiUAl2OT/iZfoCWYkUzYl19+ieHDh6t6G3fz2i4qSRPKN7mi1dfyc506dTy2X95GIummTZvi0KFDqt2k6y8lJaXQY9im5bMfc2Udj3JdtABeRgbIaCAes2WT7lR5z8txyrYs7rHHHlNF62vWrFFFngWPy/Le03Jd0nFb8Lj293YsiXw5FAWPSbajjWRpGjdujLZt26oRajKgYMaMGW4/HgO8uQGl8f773/8WSi3Kz5Iao4qRIcryDUS+jUh7ms3mQm0qKVip0WGblk26UuTNV7DtpM9YamvsbSfX8saWfmi7H3/8UR239g9LKtmJEydUDY4cp2zLv0mNtpyUpQtAjiU5DguqyHtarqVLoWDwLSOJZIivdCv4g/LasSSSoRAFj0l/b8fSyGdcVlaW+49HqxdbsGCBGqUyZ84cNarioYceskZFRRWqvqbCnn76aevatWutR44csf7888/WHj16WKOjo9WoAfHII49Y4+PjrT/++KN1+/bt1g4dOqgLWa0XLlyw7ty5U13krfPWW2+p28eOHVPNM3XqVHX8ff3119bffvtNjQJKSEiwZmRkOJrvxhtvtF599dXWLVu2WDds2GBt0qSJ9e677/a75i2rLeW+sWPHqlEVcpyuXr3a2qZNG9VWmZmZjudgW1qtjz76qDUyMlK9p5OSkhyX9PR0RzuV957Ozc21tmzZ0tqrVy/rrl27rN9//721Vq1a1nHjxln9RXnteOjQIevkyZNV+8kxKe/xhg0bWjt37ux4DrajzfPPP69Gn0k7yeeg/CwjRVeuXOn249GrAxzxzjvvqMYKDg5Ww8Y3b97s6V0ytDvvvNMaExOj2qtu3brqZ3nz2snJeOTIkWpYX1hYmPXWW29Vb3SyWtesWaNOxkUvMqTZPlR8/Pjx1tq1a6vAu3v37tYDBw4UarqzZ8+qgKZq1apq2ON9992nTuj+pqy2lJOKfLjJh5oMKa1fv751xIgRxb64sC1tQ+xLusyePdup9/TRo0etN910kzU0NFR94ZEvQjk5OVZ/UV47Hj9+XAUzNWrUUO/txo0bW5955hlrampqoefx93YU999/v3rPyjlG3sPyOWgPbtx9PJrkH+dyPkRERETG5rU1OERERESlYYBDREREPocBDhEREfkcBjhERETkcxjgEBERkc9hgENEREQ+hwEOERER+RwGOERERORzGOAQUYUtXboU77//frHt9957L1q2bOnRlpSVxk0mk7oEBQVV+PcyMzMdvyeX7du367qfROQeDHCIyOUAZ/z48Zg3b54hWnL27Nn4+eefK/z4KlWqYNOmTXjvvfd03S8icq+Kf80hIipFo0aNDNM2kklq165dhR8vWZt//OMfKpNDRL6DGRwiqhDphpo7dy727Nnj6M6RbSV1Uc2ZM8fR3dOrVy+EhYXh8ssvx+rVq2GxWPDiiy+idu3a6jJu3Di1raB9+/ahf//+iIyMRHh4OPr27Ys///yz0q/U1KlT0bhxY4SEhKBWrVro0aMHjhw5wleeyIcxg0NEFSLdUP/73/+wf/9+fP7552qbBAtlGTZsGB555BE8/fTTKsgYOHAghg8fjrS0NHz66afYsmULJkyYgFatWmHw4MHqdw4fPoyOHTuqgEkCpYCAALz66qvo3r07Dhw4oLqUnCF/R/Z98uTJ6NChA1JTU/HTTz+pfSAi38UAh4gq3A0lAc2xY8dUl05FPP7443j00UfV7bp166pARrI6UvMievfujW+++QaLFi1yBDiTJk1CjRo1sGrVKpVxERLwNGzYEJ988glGjhzp1Cu2detWtG7dWmWK7CQ7RES+jV1URKSbnj17Om43bdpUXUsmpiDZnpiY6Ph55cqVuOWWW9RIqNzcXHWpXr06rr76amzbts3pfWjTpg127tyJMWPGYMOGDcjJyXHp/0RE3oEBDhHpJioqynE7ODi42Db79oIFvsnJyZg+fTrMZnOhi3QrFQyEKkrqg95++2388MMPuP7661UWavTo0cjIyHDp/0ZExsYuKiIyFOmekqLikrqiIiIinH4+qeGRgEYuJ0+exIIFC/D8888jOjpa1eYQkW9igENEFVY026IHGeH0+++/qy6pwMBATZ9b6oCk4Fnm7JGRWkTkuxjgEFGFNWvWDLNmzcL8+fPRpEkTlQVp0KCBpi0oRcbt27dXBcgPPfSQGkp++vRprFu3TnUx3X333U4938MPP6xqeKQwWq5lEsBff/3V6WJlIvIuDHCIqMIeeOABNSpJRkedPXtWDfmWodxakvlq5G/IXDkShFy8eBExMTHo3LmzGg3lLBmB9e9//1td0tPT1WgsqcmR/wsR+S6T1Wq1enoniIi0WIuqW7du2Lx5s5rJ2JnuLRmpJRki6R6TkVrOzIRMRMbEDA4R+RTpipLgRoKWipCaotDQUN33i4jciwEOEfmEtm3bOubJkWUiKkpmRi44v07z5s112T8ici92UREREZHP4UR/RERE5HMY4BAREZHPYYBDREREPocBDhEREfkcBjhERETkcxjgEBERkc9hgENEREQ+hwEOERERwdf8Pxonz+9rdEPWAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "if True:\n", " fsz = 11\n", From 24069db32a6174414cb0b190cd30522efdf4cf30 Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sun, 9 Nov 2025 23:11:02 +0900 Subject: [PATCH 03/48] - changed time to time0 --- src/cssrlib/cssrlib.py | 2 +- src/cssrlib/plot.py | 21 +++++++++++++++++---- src/cssrlib/utils.py | 11 +++++++---- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/cssrlib/cssrlib.py b/src/cssrlib/cssrlib.py index 54961be..2ea49e1 100644 --- a/src/cssrlib/cssrlib.py +++ b/src/cssrlib/cssrlib.py @@ -449,7 +449,7 @@ def ssig2rsig(self, sys: sGNSS, utyp: uTYP, ssig): def set_time(self, t): """ set observation time (week, tow) """ - self.time = t + self.time0 = t week, tow = time2gpst(t) self.week = week self.tow0 = tow//86400*86400 diff --git a/src/cssrlib/plot.py b/src/cssrlib/plot.py index ac770b2..4765c2a 100644 --- a/src/cssrlib/plot.py +++ b/src/cssrlib/plot.py @@ -114,7 +114,7 @@ def draw_circle(ax, ccrs, p, col, nc=10, alpha=0.5): transform=ccrs) -def plot_enu(t, enu, smode=None, ztd=None, ylim=1.0, figtype=1): +def plot_enu(t, enu, smode=None, ztd=None, ylim=1.0, ylim_v=None, figtype=1): """ plot ENU coordinates """ lbl_t = ['East [m]', 'North [m]', 'Up [m]'] @@ -122,12 +122,17 @@ def plot_enu(t, enu, smode=None, ztd=None, ylim=1.0, figtype=1): fig = plt.figure(figsize=[7, 9]) fig.set_rasterized(True) + idx2 = np.array([]) if smode is not None: + idx2 = np.where(smode == 2)[0] # dgps idx4 = np.where(smode == 4)[0] # fix idx5 = np.where(smode == 5)[0] # float idx0 = np.where(smode == 0)[0] # none + if ylim_v is None: + ylim_v = ylim + if figtype == 1: # ENU versus t nfig = 3 if ztd is None else 4 @@ -136,6 +141,9 @@ def plot_enu(t, enu, smode=None, ztd=None, ylim=1.0, figtype=1): plt.subplot(nfig, 1, k+1) if smode is None: plt.plot(t, enu[:, k]) + elif len(idx2) > 0: # DGPS + plt.plot(t[idx0], enu[idx0, k], 'r.', label='none') + plt.plot(t[idx2], enu[idx2, k], 'y.', label='dgps') else: plt.plot(t[idx0], enu[idx0, k], 'r.', label='none') plt.plot(t[idx5], enu[idx5, k], 'y.', label='float') @@ -143,7 +151,9 @@ def plot_enu(t, enu, smode=None, ztd=None, ylim=1.0, figtype=1): plt.ylabel(lbl_t[k]) plt.grid() - plt.ylim([-ylim, ylim]) + lim = ylim_v if k == 2 else ylim + plt.ylim([-lim, lim]) + plt.gca().xaxis.set_major_formatter(md.DateFormatter(fmt)) if ztd is not None: @@ -159,11 +169,11 @@ def plot_enu(t, enu, smode=None, ztd=None, ylim=1.0, figtype=1): markersize=8, label='fix') plt.ylabel('ZTD [cm]') + plt.ylim([0, 40]) plt.grid() plt.gca().xaxis.set_major_formatter(md.DateFormatter(fmt)) - plt.xlabel('Time [HH:MM]') - + plt.xlabel('Time [HH:MM]') plt.legend() elif figtype == 2: # horizontal coordinates @@ -171,6 +181,9 @@ def plot_enu(t, enu, smode=None, ztd=None, ylim=1.0, figtype=1): if smode is None: plt.plot(enu[:, 0], enu[:, 1]) + elif idx2 is not None: # DGPS + plt.plot(enu[idx0, 0], enu[idx0, 1], 'r.', label='none') + plt.plot(enu[idx2, 0], enu[idx2, 1], 'y.', label='dgps') else: plt.plot(enu[idx0, 0], enu[idx0, 1], 'r.', label='none') plt.plot(enu[idx5, 0], enu[idx5, 1], 'y.', label='float') diff --git a/src/cssrlib/utils.py b/src/cssrlib/utils.py index 84593f3..e1982a1 100644 --- a/src/cssrlib/utils.py +++ b/src/cssrlib/utils.py @@ -9,7 +9,7 @@ import matplotlib.pyplot as plt import numpy as np from cssrlib.gnss import time2str, sys2str, ecef2pos, ecef2enu, timediff -from cssrlib.gnss import gtime_t, char2sys, rSigRnx, uGNSS, uTYP, uSIG +from cssrlib.gnss import gtime_t, rSigRnx from cssrlib.peph import atxdec, searchpcv from cssrlib.plot import plot_enu @@ -126,13 +126,15 @@ def prepare_signal(self, obsfile=None): def save_output(self, t, ne, ppp=None): """ output result """ - self.t[ne] = timediff(self.nav.t, self.t0)/86400.0 + self.t[ne] = timediff(t, self.t0)/86400.0 sol = self.nav.xa[0:3] if self.nav.smode == 4 else self.nav.x[0:3] self.enu[ne, :] = ecef2enu(self.pos_ref, sol-self.xyz_ref) self.smode[ne] = self.nav.smode - if ppp is not None: + if ppp is None: + self.ztd = None + else: self.ztd[ne] = self.nav.xa[ppp.IT(self.nav.na)] \ if self.nav.smode == 4 else self.nav.x[ppp.IT(self.nav.na)] @@ -175,7 +177,8 @@ def plot(self, ttl='test', fig_type=1, ylim=1, ylim_v=2, gfmt='png', """ plot utility """ if fig_type == 1: - plot_enu(self.t, self.enu, self.smode, ylim=ylim, ylim_v=ylim_v) + plot_enu(self.t, self.enu, self.smode, + self.ztd, ylim=ylim, ylim_v=ylim_v) elif fig_type == 2: plot_enu(self.t, self.enu, self.smode, figtype=fig_type, ylim=ylim) From 979e203d0f8065e6acb4dc646ac821805aef5284 Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Mon, 10 Nov 2025 20:54:45 +0900 Subject: [PATCH 04/48] - added check for None --- src/cssrlib/pntpos.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cssrlib/pntpos.py b/src/cssrlib/pntpos.py index e924c3a..e537d23 100644 --- a/src/cssrlib/pntpos.py +++ b/src/cssrlib/pntpos.py @@ -277,7 +277,7 @@ def zdres(self, obs, cs, bsx, rs, vs, dts, x, rtype=1): trop_hs, trop_wet, _ = tropmodel(obs.t, pos, model=self.nav.trpModel) - if cs.cssrmode == sCSSRTYPE.DGPS: + if cs is not None and cs.cssrmode == sCSSRTYPE.DGPS: trop_hs, trop_wet, _ = tropmodel(obs.t, pos, model=self.nav.trpModel) posr = cs.posr[cs.inet-1, :] @@ -315,7 +315,7 @@ def zdres(self, obs, cs, bsx, rs, vs, dts, x, rtype=1): trop = mapfh*trop_hs + mapfw*trop_wet else: # Differential Tropospheric Delay Correction (DTDC) - if cs.cssrmode == sCSSRTYPE.DGPS: + if cs is not None and cs.cssrmode == sCSSRTYPE.DGPS: _, e_ = geodist(rs[i, :], rr_r) _, el_r = satazel(posr, e_) From 5f91e8a4cabf6e6b7d4f4b3eace9d5ef5bdfeede Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sat, 15 Nov 2025 15:43:14 +0900 Subject: [PATCH 05/48] - added merge_cssr to support multiple channel for QZSS CLAS. --- src/cssrlib/cssrlib.py | 55 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/cssrlib/cssrlib.py b/src/cssrlib/cssrlib.py index 2ea49e1..5794fd6 100644 --- a/src/cssrlib/cssrlib.py +++ b/src/cssrlib/cssrlib.py @@ -1343,6 +1343,61 @@ def decode_l6msg(self, msg, ofst): copy_buff(msg, self.buff, i, 1695*self.fcnt, 1695) self.fcnt = self.fcnt + 1 + def merge_cssr(self, cs_): + """ merge additional correction """ + if self.facility != cs_.facility: + return False + + # merge sis corrections + sat_t = [x for x in cs_.sat_n if x not in self.sat_n] + for sat in sat_t: + sys, _ = sat2prn(sat) + self.sat_n.append(sat) + self.gnss_n.append(sys) + self.nsat_n += 1 + self.nsat_g[sys] += 1 + self.sig_n[sat] = cs_.sig_n[sat] + + if sat in self.dclk: + self.dclk[sat] = cs_.dclk[sat] + if sat in self.dorb: + self.iode[sat] = cs_.iode[sat] + self.dorb[sat] = cs_.dorb[sat] + if sat in self.cbias: + self.cbias[sat] = cs_.cbias[sat] + self.nsig_n.append(len(self.cbias[sat])) + if sat in self.pbias: + self.pbias[sat] = cs_.pbias[sat] + if sat in self.ura: + self.ura[sat] = cs_.ura[sat] + + # merge local corrections + for inet in range(0, self.MAXNET+1): + sat_t = [x for x in cs_.lc[inet].sat_n + if x not in self.lc[inet].sat_n] + for sat in sat_t: + self.lc[inet].sat_n.append(sat) + self.lc[inet].nsat_n += 1 + if sat in cs_.lc[inet].dclk: + self.lc[inet].dclk[sat] = cs_.lc[inet].dclk[sat] + if sat in cs_.lc[inet].dorb: + self.lc[inet].iode[sat] = cs_.lc[inet].iode[sat] + self.lc[inet].dorb[sat] = cs_.lc[inet].dorb[sat] + if sat in cs_.lc[inet].ura: + self.lc[inet].ura[sat] = cs_.lc[inet].ura[sat] + if sat in cs_.lc[inet].cbias: + self.lc[inet].cbias[sat] = cs_.lc[inet].cbias[sat] + if sat in cs_.lc[inet].pbias: + self.lc[inet].pbias[sat] = cs_.lc[inet].pbias[sat] + + if sat in cs_.lc[inet].stec_quality: + self.lc[inet].stec_quality[sat] = \ + cs_.lc[inet].stec_quality[sat] + self.lc[inet].ci[sat] = cs_.lc[inet].ci[sat] + self.lc[inet].dstec[sat] = cs_.lc[inet].dstec[sat] + + return True + class cssre(): """ Class to encode the Compact SSR messages (experimental) """ From 78f011867f9cf621e3ffb44306e341c583d248c8 Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sun, 23 Nov 2025 22:51:59 +0900 Subject: [PATCH 06/48] - added L1CB in rtcm decoder - fixed lock flag of rtcm msm decoder --- src/cssrlib/rtcm.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/cssrlib/rtcm.py b/src/cssrlib/rtcm.py index 3e2328e..fd78b53 100644 --- a/src/cssrlib/rtcm.py +++ b/src/cssrlib/rtcm.py @@ -443,6 +443,7 @@ def msm2rsig(self, sys: uGNSS, utyp: uTYP, ssig): qzs_tbl = { 2: uSIG.L1C, + 3: uSIG.L1E, 9: uSIG.L6S, 10: uSIG.L6L, 11: uSIG.L6X, @@ -1273,10 +1274,6 @@ def decode_msm(self, msg, i): rr = np.zeros(self.nsat) rms = rCST.CLIGHT*1e-3 - P2_10 = 0.0009765625 - P2_24 = 5.960464477539063E-08 - P2_29 = 1.862645149230957E-09 - P2_31 = 4.656612873077393E-10 # satellite part if msm >= 1 and msm <= 3: @@ -1297,7 +1294,7 @@ def decode_msm(self, msg, i): i += 4 for k in range(self.nsat): if r[k] != 0.0: - r[k] += bs.unpack_from('u10', msg, i)[0]*P2_10*rms + r[k] += bs.unpack_from('u10', msg, i)[0]*rCST.P2_10*rms i += 10 if msm == 5 or msm == 7: for k in range(self.nsat): @@ -1308,7 +1305,7 @@ def decode_msm(self, msg, i): # signal part if msm != 2: sz = 15 if msm < 6 else 20 - scl = P2_24 if msm < 6 else P2_29 + scl = rCST.P2_24 if msm < 6 else rCST.P2_29 for k in range(ncell): pr_ = bs.unpack_from('s'+str(sz), msg, i)[0] i += sz @@ -1316,7 +1313,7 @@ def decode_msm(self, msg, i): if msm > 1: sz = 22 if msm < 6 else 24 - scl = P2_29 if msm < 6 else P2_31 + scl = rCST.P2_29 if msm < 6 else rCST.P2_31 for k in range(ncell): cp_ = bs.unpack_from('s'+str(sz), msg, i)[0] i += sz @@ -1402,11 +1399,14 @@ def decode_msm(self, msg, i): if sat_ in self.lock: for j, ll in enumerate(ll_): + if sig[j] not in self.sig_n: + continue + idx = sig.index(sig[j]) ll_p = self.lock[sat_][j] if (ll == 0 & ll_p != 0) | ll < ll_p: - obs.lli[k, j] |= 1 + obs.lli[k, idx] |= 1 if hf_[j] > 0: - obs.lli[k, j] |= 3 + obs.lli[k, idx] |= 3 self.lock[sat_] = ll_ From 50980d2889b95635a66a2a8d068f3ae3f48eb40a Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Mon, 24 Nov 2025 09:37:39 +0900 Subject: [PATCH 07/48] - config file support --- src/cssrlib/gnss.py | 54 ++++++++++++++++++++ src/cssrlib/mlambda.py | 34 +++++++++++-- src/cssrlib/ppprtk.py | 15 +++--- src/cssrlib/pppssr.py | 110 +++++++++++++++++++++++++++-------------- src/cssrlib/rawnav.py | 11 +++-- 5 files changed, 171 insertions(+), 53 deletions(-) diff --git a/src/cssrlib/gnss.py b/src/cssrlib/gnss.py index 7495bd8..f4e1f47 100644 --- a/src/cssrlib/gnss.py +++ b/src/cssrlib/gnss.py @@ -1652,3 +1652,57 @@ def load_config(file_path): with open(file_path, 'r') as file: config = yaml.safe_load(file) return config + + +trop_model_tbl = { + "": uTropoModel.NONE, + "SAAST": uTropoModel.SAAST, + "HOPF": uTropoModel.HOPF, + "SBAS": uTropoModel.SBAS +} + +iono_model_tbl = { + "": uIonoModel.NONE, + "KLOBUCHAR": uIonoModel.KLOBUCHAR, + "NEQUICK_G": uIonoModel.GIM, + "SBAS": uIonoModel.SBAS +} + + +default_config = { + "nav": { + "ephopt": 2, # 2: SSR-APC + "trop_opt": 1, + "iono_opt": 1, + "phw_opt": 1, + "cmooth": False, + "trop_model": "SAAST", + "iono_model": "KLOBUCHAR", + + "eratio": [50.0, 50.0], + "err": [0, 0.007, 0.0035], + + # initial value of covariance + "sig_p0": 100.0, # [m] + "sig_v0": 1.0, # [m/s] + "sig_ztd0": 0.1, # [m] + "sig_ion0": 10.0, # [m] + "sig_n0": 30.0, # [cyc] + + # Process noise sigma + # "sig_qp": 100.0, + "sig_qp": 1e-2, # [m/sqrt(s)] + "sig_qv": 1.0, # [m] + "sig_qztd": 0.0008, # [m] + "sig_qiono": 10.0, # [m] + "sig_qb": 1e-4, # [m] + + # AR parameters + "armode": 0, # 0:float-ppp,1:continuous,2:instantaneous,3:fix-and-hold + "parmode": 2, # Partial AR 1: normal, 2: PAR + "par_P0": 0.995, # probability of sussefull AR + "thresar": 3.0, # AR acceptance threshold + "elmaskar": 20.0, + + } +} diff --git a/src/cssrlib/mlambda.py b/src/cssrlib/mlambda.py index f01805f..e4a7dbe 100644 --- a/src/cssrlib/mlambda.py +++ b/src/cssrlib/mlambda.py @@ -242,10 +242,11 @@ def estimILS(L, d, ahat, ncands=2): return afixed, sqnorm -def parsearch(zhat, Qzhat, Z, L, d, Ps, P0=0.995, ncands=2): +def parsearch(zhat, Qzhat, Z, L, d, Ps, P0=0.995, ncands=2, exclmax=1): """ Partial Ambiguity Resolution """ n = len(Qzhat) k = 0 + # idx = np.argsort(d)[::-1] while Ps < P0 and k < (n-1): k += 1 @@ -253,7 +254,7 @@ def parsearch(zhat, Qzhat, Z, L, d, Ps, P0=0.995, ncands=2): # would be fixed Ps = sr_boost(d[k:]) - if Ps > P0: + if k <= exclmax and Ps > P0: # zpar, sqnorm = msearch(L[k:, k:], d[k:], zhat[k:], ncands) zpar, sqnorm = estimILS(L[k:, k:], d[k:], zhat[k:], ncands) @@ -285,7 +286,7 @@ def parsearch(zhat, Qzhat, Z, L, d, Ps, P0=0.995, ncands=2): return zpar, sqnorm, Qzpar, Zpar, Ps, nfix, zfix -def mlambda(ahat, Qahat, ncands=2, armode=1, P0=0.995): +def mlambda(ahat, Qahat, ncands=2, parmode=1, P0=0.995): """ modified LAMBDA method for ambiguity resolution """ # s = time.perf_counter_ns() L, d = ldldecom(Qahat) @@ -299,14 +300,37 @@ def mlambda(ahat, Qahat, ncands=2, armode=1, P0=0.995): Ps = sr_boost(d) - if armode == 1: + if parmode == 1: # zfix, s = msearch(L, d, zhat, ncands) zfix, s = estimILS(L, d, zhat, ncands) nfix = len(zhat) - elif armode == 2: # PAR + elif parmode == 2: # PAR zpar, s, Qzpar, Zpar, Ps, nfix, zfix = parsearch(zhat, Qzhat, Z, L, d, Ps, P0, ncands) + elif parmode == 3: + + # zfix, s = estimILS(L, d, zhat, ncands) + zfix, s = msearch(L, d, zhat, ncands) + nfix = len(zhat) + + thresar = 2.0 + ratio = s[1]/s[0] + + if ratio < thresar: + + for k in range(len(zhat)): + d_ = np.delete(d, k) + L_ = np.delete(np.delete(L, k, 0), k, 1) + zhat_ = np.delete(d, k) + + zfix_, s = msearch(L_, d_, zhat_, ncands) + # zfix, s = estimILS(L_, d_, zhat_, ncands) + ratio = s[1]/s[0] + if ratio >= thresar: + break + + nfix = len(zhat)-1 afix_ = iZt@zfix return afix_, s, nfix, Ps diff --git a/src/cssrlib/ppprtk.py b/src/cssrlib/ppprtk.py index a5cca62..e1c90d2 100644 --- a/src/cssrlib/ppprtk.py +++ b/src/cssrlib/ppprtk.py @@ -9,16 +9,15 @@ class ppprtkpos(pppos): """ class for PPP-RTK processing """ - def __init__(self, nav, pos0=np.zeros(3), logfile=None): + def __init__(self, nav, pos0=np.zeros(3), logfile=None, config=None): """ initialize variables for PPP-RTK """ # trop, iono from cssr # phase windup model is local/regional - super().__init__(nav=nav, pos0=pos0, logfile=logfile, - trop_opt=2, iono_opt=2, phw_opt=2) + super().__init__(nav=nav, pos0=pos0, logfile=logfile, config=config) - self.nav.eratio = np.ones(self.nav.nf)*50 # [-] factor - self.nav.err = [0, 0.01, 0.005]/np.sqrt(2) # [m] sigma - self.nav.sig_p0 = 30.0 # [m] - self.nav.thresar = 2.0 # AR acceptance threshold - self.nav.armode = 3 # AR is enabled + # self.nav.eratio = np.ones(self.nav.nf)*50 # [-] factor + # self.nav.err = [0, 0.01, 0.005]/np.sqrt(2) # [m] sigma + # self.nav.sig_p0 = 30.0 # [m] + # self.nav.thresar = 2.0 # AR acceptance threshold + # self.nav.armode = 3 # AR is enabled diff --git a/src/cssrlib/pppssr.py b/src/cssrlib/pppssr.py index 4be1f81..8457bae 100644 --- a/src/cssrlib/pppssr.py +++ b/src/cssrlib/pppssr.py @@ -6,8 +6,9 @@ from cssrlib.ephemeris import satposs from cssrlib.gnss import sat2id, sat2prn, rSigRnx, uTYP, uGNSS, rCST -from cssrlib.gnss import uTropoModel, ecef2pos, tropmodel, geodist, satazel -from cssrlib.gnss import time2str, timediff, gpst2utc, tropmapf, uIonoModel +from cssrlib.gnss import ecef2pos, tropmodel, geodist, satazel +from cssrlib.gnss import time2str, timediff, gpst2utc, tropmapf +from cssrlib.gnss import trop_model_tbl, iono_model_tbl, default_config from cssrlib.ppp import tidedisp, tidedispIERS2010, uTideModel from cssrlib.ppp import shapiro, windupcorr from cssrlib.peph import antModelRx, antModelTx @@ -31,34 +32,52 @@ class pppos(): VAR_HOLDAMB = 0.001 def __init__(self, nav, pos0=np.zeros(3), - logfile=None, trop_opt=1, iono_opt=1, phw_opt=1): + logfile=None, trop_opt=-1, iono_opt=-1, phw_opt=-1, + config=None): """ initialize variables for PPP """ + if config is None: + config = default_config + + if trop_opt > 0: + config['nav']['trop_opt'] = trop_opt + if iono_opt > 0: + config['nav']['iono_opt'] = iono_opt + if phw_opt > 0: + config['nav']['phw_opt'] = phw_opt + self.nav = nav # Number of frequencies (actually signals!) # - self.nav.ephopt = 2 # SSR-APC + if 'ephopt' in config['nav']: + self.nav.ephopt = config['nav']['ephopt'] # SSR-APC # Select tropospheric model # - self.nav.trpModel = uTropoModel.SAAST + if 'trop_model' in config['nav']: + self.nav.trpModel = trop_model_tbl[config['nav']['trop_model']] # Select iono model # - self.nav.ionoModel = uIonoModel.KLOBUCHAR + if 'iono_model' in config['nav']: + self.nav.ionoModel = iono_model_tbl[config['nav']['iono_model']] # 0: use trop-model, 1: estimate, 2: use cssr correction - self.nav.trop_opt = trop_opt + if 'trop_opt' in config['nav']: + self.nav.trop_opt = config['nav']['trop_opt'] # 0: use iono-model, 1: estimate, 2: use cssr correction - self.nav.iono_opt = iono_opt + if 'iono_opt' in config['nav']: + self.nav.iono_opt = config['nav']['iono_opt'] # 0: none, 1: full model, 2: local/regional model - self.nav.phw_opt = phw_opt + if 'phw_opt' in config['nav']: + self.nav.phw_opt = config['nav']['phw_opt'] # carrier smoothing - self.nav.csmooth = False + if 'csmooth' in config['nav']: + self.nav.csmooth = config['nav']['csmooth'] # Position (+ optional velocity), zenith tropo delay and # slant ionospheric delay states @@ -89,41 +108,58 @@ def __init__(self, nav, pos0=np.zeros(3), # # Observation noise parameters # - self.nav.eratio = np.ones(self.nav.nf)*50 # [-] factor - self.nav.err = [0, 0.01, 0.005]/np.sqrt(2) # [m] sigma + if 'eratio' in config['nav']: + self.nav.eratio = config['nav']['eratio'] + if 'err' in config['nav']: + self.nav.err = config['nav']['err'] # [m] # Initial sigma for state covariance # - self.nav.sig_p0 = 100.0 # [m] - self.nav.sig_v0 = 1.0 # [m/s] - self.nav.sig_ztd0 = 0.1 # [m] - self.nav.sig_ion0 = 10.0 # [m] - self.nav.sig_n0 = 30.0 # [cyc] + if 'sig_p0' in config['nav']: + self.nav.sig_p0 = config['nav']['sig_p0'] # [m] + if 'sig_v0' in config['nav']: + self.nav.sig_v0 = config['nav']['sig_v0'] # [m/s] + if 'sig_ztd0' in config['nav']: + self.nav.sig_ztd0 = config['nav']['sig_ztd0'] # [m] + if 'sig_ion0' in config['nav']: + self.nav.sig_ion0 = config['nav']['sig_ion0'] # [m] + if 'sig_n0' in config['nav']: + self.nav.sig_n0 = config['nav']['sig_n0'] # [cyc] # Process noise sigma # - if self.nav.pmode == 0: - self.nav.sig_qp = 100.0/np.sqrt(1) # [m/sqrt(s)] - self.nav.sig_qv = None - else: - self.nav.sig_qp = 0.01/np.sqrt(1) # [m/sqrt(s)] - self.nav.sig_qv = 1.0/np.sqrt(1) # [m/s/sqrt(s)] - self.nav.sig_qztd = 0.05/np.sqrt(3600) # [m/sqrt(s)] - self.nav.sig_qion = 10.0/np.sqrt(1) # [m/s/sqrt(s)] - self.nav.sig_qb = 1e-4/np.sqrt(1) # [m/s/sqrt(s)] + if 'sig_qp' in config['nav']: + self.nav.sig_qp = config['nav']['sig_qp'] # [m/sqrt(s)] + if 'sig_qv' in config['nav']: + self.nav.sig_qv = config['nav']['sig_qv'] # [m/s/sqrt(s)] + if 'sig_qztd' in config['nav']: + self.nav.sig_qztd = config['nav']['sig_qztd'] # [m/sqrt(s)] + if 'sig_qion' in config['nav']: + self.nav.sig_qion = config['nav']['sig_qion'] # [m/s/sqrt(s)] + if 'sig_qb' in config['nav']: + self.nav.sig_qb = config['nav']['sig_qb'] # [m/s/sqrt(s)] # Processing options # self.nav.tidecorr = uTideModel.IERS2010 # self.nav.tidecorr = uTideModel.SIMPLE - self.nav.thresar = 3.0 # AR acceptance threshold + if 'thresar' in config['nav']: + # AR acceptance threshold + self.nav.thresar = config['nav']['thresar'] # 0:float-ppp,1:continuous,2:instantaneous,3:fix-and-hold - self.nav.armode = 0 - self.nav.elmaskar = np.deg2rad(20.0) # elevation mask for AR - self.nav.elmin = np.deg2rad(15.0) - - self.nav.parmode = 2 # 1: normal, 2: PAR - self.nav.par_P0 = 0.995 # probability of sussefull AR + if 'armode' in config['nav']: + self.nav.armode = config['nav']['armode'] + if 'elmaskar' in config['nav']: + self.nav.elmaskar = \ + config['nav']['elmaskar']*rCST.D2R # elevation mask for AR + if 'elmin' in config['nav']: + self.nav.elmin = config['nav']['elmin']*rCST.D2R + + if 'parmode' in config['nav']: + self.nav.parmode = config['nav']['parmode'] # 1: normal, 2: PAR + # probability of sussefull AR + if 'par_P0' in config['nav']: + self.nav.par_P0 = config['nav']['par_P0'] # Initial state vector # @@ -1071,7 +1107,7 @@ def ddidx(self, nav, sat): ix = np.resize(ix, (nb, 2)) return ix - def resamb_lambda(self, sat, armode=1, P0=0.995): + def resamb_lambda(self, sat, parmode=1, P0=0.995): """ resolve integer ambiguity using LAMBDA method """ nx = self.nav.nx na = self.nav.na @@ -1089,8 +1125,8 @@ def resamb_lambda(self, sat, armode=1, P0=0.995): Qab = self.nav.P[0:na, ix[:, 0]]-self.nav.P[0:na, ix[:, 1]] # MLAMBDA ILS - b, s, nfix, Ps = mlambda(y, Qb, armode=armode, P0=P0) - if nfix > 0 and (armode == 2 or s[0] <= 0.0 or + b, s, nfix, Ps = mlambda(y, Qb, parmode=parmode, P0=P0) + if nfix > 0 and (parmode == 2 or s[0] <= 0.0 or s[1]/s[0] >= self.nav.thresar): self.nav.xa = self.nav.x[0:na].copy() self.nav.Pa = self.nav.P[0:na, 0:na].copy() @@ -1103,7 +1139,7 @@ def resamb_lambda(self, sat, armode=1, P0=0.995): # restore SD ambiguity xa = self.restamb(bias, nb) - elif armode == 2 and nfix == 0: + elif parmode == 2 and nfix == 0: nb = 0 if self.nav.monlevel > 0: self.nav.fout.write( diff --git a/src/cssrlib/rawnav.py b/src/cssrlib/rawnav.py index 8155848..039a4cd 100644 --- a/src/cssrlib/rawnav.py +++ b/src/cssrlib/rawnav.py @@ -11,7 +11,7 @@ Satellite Positioning, Navigation and Timing Service (IS-QZSS-PNT-006), 2024 [3] Galileo Open Service Signal-in-Space Interface Control Document - (OS SIS ICD) Issue 2.1, 2023 + (OS SIS ICD) Issue 2.2, 2025 [4] BeiDou Navigation Satellite System Signal In Space Interface Control Document: Open Service Signal B1C (Version 1.0), 2017 [5] GLONASS Interface Control Document (Edition 5.1), 2008 @@ -26,7 +26,7 @@ import bitstruct.c as bs import struct as st from cssrlib.gnss import gpst2time, gst2time, bdt2time, rCST -from cssrlib.gnss import prn2sat, uGNSS, sat2prn, bdt2gpst, utc2gpst, time2gpst +from cssrlib.gnss import uGNSS, sat2prn, bdt2gpst, utc2gpst, time2gpst from cssrlib.gnss import Eph, Geph, uTYP, copy_buff, gtime_t, timeadd, Seph from cssrlib.gnss import tod2tow from cssrlib.rinex import rnxenc, rSigRnx @@ -59,6 +59,7 @@ def gep2time(N4, Nt, sod): class RawNav(): """ Raw Navigation Message decoder """ + def __init__(self, opt=None, prefix=''): self.gps_lnav = {} for k in range(uGNSS.GPSMAX): @@ -488,6 +489,7 @@ def decode_gps_cnav_iono(self, msg, i): alp = np.array([a0*rCST.P2_30, a1*rCST.P2_27, a2*rCST.P2_24, a3*rCST.P2_24]) bet = np.array([b0*(2**11), b1*(2**14), b2*(2**16), b3*(2**16)]) + return i def decode_gps_cnav_utc(self, msg, i): @@ -1467,6 +1469,9 @@ def decode_irn_l1nav(self, week, time, sat, msg): i += 103+3 prn = bs.unpack_from('u6', msg, i)[0] + if prn != prn_: + return None + tow = itow*7200+toi*18 # subframe 3 (274) @@ -1487,7 +1492,7 @@ def decode_irn_l1nav(self, week, time, sat, msg): elif msgid == 17: # NavIC Time offsets i = self.decode_irn_l1nav_utc(msg, i) elif msgid == 0: # null message - None + pass i += 243 From 6a27d610762a9f52fa29407ad36c993e4ec05e59 Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Mon, 24 Nov 2025 09:50:22 +0900 Subject: [PATCH 08/48] - added config. - fixed sig_qion --- src/cssrlib/gnss.py | 2 +- src/cssrlib/rtk.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cssrlib/gnss.py b/src/cssrlib/gnss.py index f4e1f47..1bbf4a0 100644 --- a/src/cssrlib/gnss.py +++ b/src/cssrlib/gnss.py @@ -1694,7 +1694,7 @@ def load_config(file_path): "sig_qp": 1e-2, # [m/sqrt(s)] "sig_qv": 1.0, # [m] "sig_qztd": 0.0008, # [m] - "sig_qiono": 10.0, # [m] + "sig_qion": 10.0, # [m] "sig_qb": 1e-4, # [m] # AR parameters diff --git a/src/cssrlib/rtk.py b/src/cssrlib/rtk.py index ad0f70b..2949f78 100644 --- a/src/cssrlib/rtk.py +++ b/src/cssrlib/rtk.py @@ -12,13 +12,13 @@ class rtkpos(pppos): """ class for RTK processing """ - def __init__(self, nav, pos0=np.zeros(3), logfile=None): + def __init__(self, nav, pos0=np.zeros(3), logfile=None, config=None): """ initialize variables for PPP-RTK """ # trop, iono from cssr # phase windup model is local/regional super().__init__(nav=nav, pos0=pos0, logfile=logfile, - trop_opt=0, iono_opt=0, phw_opt=0) + trop_opt=0, iono_opt=0, phw_opt=0, config=config) self.nav.eratio = np.ones(self.nav.nf)*50 # [-] factor self.nav.err = [0, 0.01, 0.005]/np.sqrt(2) # [m] sigma From cf7f091439868bc56a347ee4717e5f2a9e203cb5 Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sun, 14 Dec 2025 14:20:23 +0900 Subject: [PATCH 09/48] - added RTCM SSR decoder --- src/cssrlib/cssrlib.py | 8 + src/cssrlib/gnss.py | 40 ++ src/cssrlib/rtcm.py | 1286 ++++++++++++++++++++++++++++++++-------- 3 files changed, 1095 insertions(+), 239 deletions(-) diff --git a/src/cssrlib/cssrlib.py b/src/cssrlib/cssrlib.py index 5794fd6..58c74f2 100644 --- a/src/cssrlib/cssrlib.py +++ b/src/cssrlib/cssrlib.py @@ -220,6 +220,8 @@ def __init__(self): self.ng = -1 self.pbias = {} self.cbias = {} + self.yaw = {} + self.dyaw = {} self.iode = None self.dorb = None self.dclk = None @@ -319,6 +321,12 @@ def __init__(self, foutname=None): self.iodssr_p = -1 self.iodssr_c = np.ones(16, dtype=np.int32)*-1 self.sig_n_p = [] + self.mi = 0 + self.uint = 0 + + # SSR Update Interval [s] DF391 + self.udint_t = [1, 2, 5, 10, 15, 30, 60, 120, + 240, 300, 600, 900, 1800, 3600, 7200, 10800] # maximum validty time for correction self.tmax = {sCType.CLOCK: 30.0, sCType.ORBIT: 120.0, diff --git a/src/cssrlib/gnss.py b/src/cssrlib/gnss.py index 1bbf4a0..4667fed 100644 --- a/src/cssrlib/gnss.py +++ b/src/cssrlib/gnss.py @@ -166,6 +166,23 @@ class uGNSS(IntEnum): MAXSAT = GPSMAX+GALMAX+QZSMAX+BDSMAX+GLOMAX+SBSMAX+IRNMAX + MINPRNGPS = 1 + MAXPRNGPS = 36 + MINPRNGLO = 1 + MAXPRNGLO = 27 + MINPRNGAL = 1 + MAXPRNGAL = 36 + MINPRNQZS = 193 + MAXPRNQZS = 210 + MINPRNBDS = 1 + MAXPRNBDS = 18 + MINPRNBDS3 = 19 + MAXPRNBDS3 = 58 + MINPRNIRN = 1 + MAXPRNIRN = 14 + MINPRNSBS = 120 + MAXPRNSBS = 158 + class uTYP(IntEnum): """ class for observation types""" @@ -668,6 +685,16 @@ def __init__(self): self.sat = [] self.sig = {} + def sort(self): + """ sort using satellite index """ + idx = np.argsort(self.sat) + self.sat = self.sat[idx] + self.P = self.P[idx, :] + self.L = self.L[idx, :] + self.D = self.D[idx, :] + self.S = self.S[idx, :] + self.lli = self.lli[idx, :] + class Eph(): """ class to define ephemeris """ @@ -1262,6 +1289,19 @@ def sys2str(sys): return gnss_tbl[sys] +def sid2prn(sys, sid): + """ convert satellite id to prn """ + if sys == uGNSS.QZS: + prn = sid+rCST.MINPRNQZS-1 + elif sys == uGNSS.BDS3: + prn = sid+rCST.MINPRNBDS3-1 + elif sys == uGNSS.SBS: + prn = sid+rCST.MINPRNSBS-1 + else: + prn = sid + return prn + + def vnorm(r): """ calculate norm of a vector """ return r/np.linalg.norm(r) diff --git a/src/cssrlib/rtcm.py b/src/cssrlib/rtcm.py index fd78b53..beb4b5e 100644 --- a/src/cssrlib/rtcm.py +++ b/src/cssrlib/rtcm.py @@ -20,7 +20,7 @@ from cssrlib.gnss import uGNSS, sat2id, gpst2time, timediff, time2str, sat2prn from cssrlib.gnss import uTYP, uSIG, rSigRnx, bdt2time, bdt2gpst, glo2time from cssrlib.gnss import time2bdt, gpst2bdt, rCST, time2gpst, utc2gpst, timeadd -from cssrlib.gnss import gtime_t, sys2str, sat2prn, sat2id +from cssrlib.gnss import gtime_t, sys2str, sat2prn, sat2id, gpst2utc from crccheck.crc import Crc24LteA from enum import IntEnum from cssrlib.gnss import Eph, Obs, Geph, Seph @@ -39,9 +39,9 @@ class sRTCM(IntEnum): BDS_EPH = 1014 QZS_EPH = 1015 IRN_EPH = 1016 - SBS_EPH = 1016 + SBS_EPH = 1017 - # test messages + # SC-134 test messages INTEG_MIN = 2003 INTEG_EXT = 2004 INTEG_EXT_SIS = 2005 @@ -54,6 +54,16 @@ class sRTCM(IntEnum): INTEG_SSR_IONO = 2012 INTEG_SSR_TROP = 2013 + # RTCM SSR test messages + SSR_META = 60 + SSR_GRID = 61 + SSR_SATANT = 80 + SSR_TROP = 95 + SSR_STEC = 96 + SSR_HCLK = 46 + SSR_PBIAS = 85 + SSR_PBIAS_EX = 90 + class Integrity(): """ class for integrity information in SC-134 """ @@ -175,6 +185,20 @@ def __init__(self): self.vp_r_tbl = {s: k for k, s in enumerate(self.vp_tbl)} +class TropModel(): + """ Tropospheric delay correction model for RTCM SSR """ + + def __init__(self): + self.dah = 0.0 # DF+38 + self.dbh = 0.0 # DF+39 + self.dch = 0.0 # DF+40 + self.daw = 0.0 # DF+41 + self.dbw = 0.0 # DF+42 + self.dcw = 0.0 # DF+43 + self.rh = np.array([]) + self.rw = np.array([]) + + class rtcm(cssr): """ class to decode RTCM3 messages """ @@ -189,6 +213,8 @@ def __init__(self, foutname=None): self.pid = 0 # SSR Provider ID self.sid = 0 # SSR Solution Type + self.uint = 0 # Update Interval + self.mi = 0 self.nrtk_r = {} @@ -245,13 +271,13 @@ def ssrtype(self, msgtype): break return sys, ssr - def svid2sat(self, sys, svid): + def svid2sat(self, sys, svid, f_rtcm=False): """ convert svid to sat """ prn = svid if sys == uGNSS.QZS: - prn += 192 + prn += uGNSS.MINPRNQZS-11 if f_rtcm else uGNSS.MINPRNQZS-1 elif sys == uGNSS.SBS: - prn += 119 + prn += uGNSS.MINPRNSBS-1 return prn2sat(sys, prn) def ssig2rsig(self, sys: uGNSS, utyp: uTYP, ssig): @@ -279,9 +305,12 @@ def ssig2rsig(self, sys: uGNSS, utyp: uTYP, ssig): 1: uSIG.L1P, 2: uSIG.L2C, 3: uSIG.L2P, + 4: uSIG.L4A, + 5: uSIG.L4B, + 6: uSIG.L6A, + 7: uSIG.L6B, 10: uSIG.L3I, 11: uSIG.L3Q, - 12: uSIG.L3X, } gal_tbl = { @@ -322,6 +351,7 @@ def ssig2rsig(self, sys: uGNSS, utyp: uTYP, ssig): 12: uSIG.L5D, 13: uSIG.L5P, 14: uSIG.L5X, + 15: uSIG.L1A, } qzs_tbl = { @@ -330,20 +360,14 @@ def ssig2rsig(self, sys: uGNSS, utyp: uTYP, ssig): 2: uSIG.L1C, 3: uSIG.L2S, 4: uSIG.L2L, - 5: uSIG.L2X, 6: uSIG.L5I, 7: uSIG.L5Q, - 8: uSIG.L5X, 9: uSIG.L6S, 10: uSIG.L6L, 11: uSIG.L6X, 12: uSIG.L1X, - 13: uSIG.L1Z, - 14: uSIG.L5D, - 15: uSIG.L5P, - 16: uSIG.L5Z, 17: uSIG.L6E, - 18: uSIG.L6Z, + 19: uSIG.L1E, # TBD } sbs_tbl = { @@ -533,8 +557,13 @@ def decode_head(self, msg, i, sys): if self.subtype == sCSSR.PBIAS: ci, mw = bs.unpack_from('u1u1', msg, i) i += 2 + elif self.subtype == sRTCM.SSR_PBIAS: + # Satellite Yaw Information Indicator DF486 + # Extended Phase Bias Property ID DF+2 + iyaw, self.iexpb = bs.unpack_from('b1u4', msg, i) + i += 5 - if self.subtype != sCSSR.VTEC: + if self.subtype not in [sCSSR.VTEC, sRTCM.SSR_PBIAS_EX, sRTCM.SSR_STEC]: nsat = bs.unpack_from('u6', msg, i)[0] i += 6 else: @@ -543,7 +572,11 @@ def decode_head(self, msg, i, sys): self.pid = pid self.sid = sid - v = {'iodssr': iodssr, 'nsat': nsat} + v = {'iodssr': iodssr, 'uint': uint, 'mi': mi, 'nsat': nsat} + if self.subtype == sRTCM.SSR_PBIAS: + v['iyaw'] = iyaw + v['iexpb'] = self.iexpb + return i, v def decode_sat(self, msg, i, sys=uGNSS.NONE): @@ -596,8 +629,8 @@ def decode_clk_sat(self, msg, i, k, inet=0): dclk, ddclk, dddclk = bs.unpack_from('s22s21s27', msg, i) i += 70 self.dclk_n[k] = self.sval(dclk, 22, 0.1e-3) - # self.lc[inet].ddclk[k] = self.sval(ddclk, 21, 0.4e-3) - # self.lc[inet].dddclk[k] = self.sval(dddclk, 27, 4e-6) + self.ddclk_n[k] = self.sval(ddclk, 21, 0.4e-3) + self.dddclk_n[k] = self.sval(dddclk, 27, 4e-6) return i def decode_hclk_sat(self, msg, i, k, inet=0): @@ -628,6 +661,36 @@ def get_ssr_sys(self, msgtype): tbl_t = {1265: uGNSS.GPS, 1266: uGNSS.GLO, 1267: uGNSS.GAL, 1268: uGNSS.QZS, 1269: uGNSS.SBS, 1270: uGNSS.BDS} return tbl_t[msgtype] + # RTCM SSR test messages + elif msgtype >= 41 and msgtype < 47: # OBC + return uGNSS.GLO + elif msgtype in [62, 65, 68, 71, 74, 77]: # OBC + return uGNSS.GAL + elif msgtype in [63, 66, 69, 72, 75, 78]: # OBC + return uGNSS.BDS + elif msgtype in [64, 67, 70, 73, 76, 79]: # OBC + return uGNSS.QZS + elif msgtype >= 80 and msgtype < 85: # proposed satellite antenna + tbl_t = {80: uGNSS.GPS, 81: uGNSS.GLO, 82: uGNSS.GAL, + 83: uGNSS.BDS, 84: uGNSS.QZS} + return tbl_t[msgtype] + elif msgtype >= 85 and msgtype < 90: # proposed phase bias + tbl_t = {85: uGNSS.GPS, 86: uGNSS.GLO, 87: uGNSS.GAL, + 88: uGNSS.BDS, 89: uGNSS.QZS} + return tbl_t[msgtype] + elif msgtype >= 90 and msgtype < 95: # proposed phase bias + tbl_t = {90: uGNSS.GPS, 91: uGNSS.GLO, 92: uGNSS.GAL, + 93: uGNSS.BDS, 94: uGNSS.QZS} + return tbl_t[msgtype] + elif msgtype >= 96 and msgtype < 101: # regional iono + tbl_t = {96: uGNSS.GPS, 97: uGNSS.GLO, 98: uGNSS.GAL, + 99: uGNSS.BDS, 100: uGNSS.QZS} + elif msgtype in [60, 61, 95]: # METADATA, grid definition, trop + return uGNSS.NONE + else: + print(f"definition of {msgtype} is missing") + + return tbl_t[msgtype] def decode_cssr_orb(self, msg, i, inet=0): """ decode RTCM Orbit Correction message """ @@ -644,8 +707,11 @@ def decode_cssr_orb(self, msg, i, inet=0): self.sat_n = [] self.lc[inet].iode = {} self.lc[inet].dorb = {} + self.lc[inet].ddorb = {} self.iodssr = v['iodssr'] + self.uint = v['uint'] + self.mi = v['mi'] sat = [] for k in range(nsat): i, sat_ = self.decode_sat(msg, i, sys) @@ -653,6 +719,7 @@ def decode_cssr_orb(self, msg, i, inet=0): sat.append(sat_) self.lc[inet].iode[sat_] = self.iode_n[k] self.lc[inet].dorb[sat_] = self.dorb_n[k, :] + self.lc[inet].ddorb[sat_] = self.ddorb_n[k, :] self.set_t0(inet, sat_, sCType.ORBIT, self.time) @@ -676,12 +743,23 @@ def decode_cssr_clk(self, msg, i, inet=0): if timediff(self.time, self.lc[inet].t0s[sCType.CLOCK]) > 0: self.lc[inet].dclk = {} + self.lc[inet].ddclk = {} + self.lc[inet].dddclk = {} self.dclk_n = np.zeros(nsat) + self.ddclk_n = np.zeros(nsat) + self.dddclk_n = np.zeros(nsat) + + self.iodssr = v['iodssr'] + self.uint = v['uint'] + self.mi = v['mi'] + for k in range(nsat): i, sat_ = self.decode_sat(msg, i, sys) i = self.decode_clk_sat(msg, i, k) self.lc[inet].dclk[sat_] = self.dclk_n[k] + self.lc[inet].ddclk[sat_] = self.ddclk_n[k] + self.lc[inet].dddclk[sat_] = self.dddclk_n[k] self.set_t0(inet, sat_, sCType.CLOCK, self.time) @@ -703,6 +781,10 @@ def decode_cssr_cbias(self, msg, i, inet=0): self.sat_b = [] self.lc[inet].cbias = {} + self.iodssr = v['iodssr'] + self.uint = v['uint'] + self.mi = v['mi'] + for k in range(nsat): i, sat_ = self.decode_sat(msg, i, sys) self.set_t0(inet, sat_, sCType.CBIAS, self.time) @@ -739,6 +821,13 @@ def decode_cssr_pbias(self, msg, i, inet=0): if timediff(self.time, self.lc[inet].t0s[sCType.PBIAS]) > 0: self.sat_b = [] self.lc[inet].pbias = {} + self.lc[inet].si = {} + self.lc[inet].cnt = {} + + self.iodssr = v['iodssr'] + self.uint = v['uint'] + self.mi = v['mi'] + self.iyaw = v['iyaw'] for k in range(nsat): i, sat_ = self.decode_sat(msg, i, sys) @@ -746,22 +835,42 @@ def decode_cssr_pbias(self, msg, i, inet=0): nsig = bs.unpack_from('u5', msg, i)[0] i += 5 - yaw, dyaw = bs.unpack_from('u9s8', msg, i) - yaw *= 1.0/256.0 - dyaw = self.sval(dyaw, 8, 1.0/8192.0) - i += 17 + if self.subtype != sRTCM.SSR_PBIAS or \ + (self.subtype == sRTCM.SSR_PBIAS and v['iyaw']): + yaw, dyaw = bs.unpack_from('u9s8', msg, i) + yaw *= 1.0/256.0 + dyaw = self.sval(dyaw, 8, 1.0/8192.0) + i += 17 + self.lc[inet].yaw[sat_] = yaw + self.lc[inet].dyaw[sat_] = dyaw + if sat_ not in self.sat_b: self.sat_b.append(sat_) if sat_ not in self.lc[inet].pbias: self.lc[inet].pbias[sat_] = {} + self.lc[inet].si[sat_] = {} + self.lc[inet].cnt[sat_] = {} for j in range(nsig): - sig, si, wl, ci, pb = bs.unpack_from('u5u1u2u4s20', msg, i) - i += 32 + # tracking mode: DF461 + # signal integer indicator: DF483 + sig, si = bs.unpack_from('u5b1', msg, i) + i += 6 + + if self.subtype != sRTCM.SSR_PBIAS: + wl = bs.unpack_from('u2', msg, i)[0] + i += 2 + + # signal discontinuity indicator: DF485 + # phase bias: DF482 + cnt, pb = bs.unpack_from('u4s20', msg, i) + i += 24 rsig = self.ssig2rsig(sys, uTYP.L, sig).str() self.lc[inet].pbias[sat_][rsig] = self.sval(pb, 20, 1e-4) + self.lc[inet].si[sat_][rsig] = si + self.lc[inet].cnt[sat_][rsig] = cnt self.iodssr_c[sCType.PBIAS] = v['iodssr'] self.lc[0].cstat |= (1 << sCType.PBIAS) @@ -778,16 +887,24 @@ def decode_cssr_comb(self, msg, i, inet=0): self.dorb_n = np.zeros((nsat, 3)) self.ddorb_n = np.zeros((nsat, 3)) self.dclk_n = np.zeros(nsat) + self.ddclk_n = np.zeros(nsat) + self.dddclk_n = np.zeros(nsat) if timediff(self.time, self.lc[inet].t0s[sCType.ORBIT]) > 0: self.nsat_n = 0 self.sys_n = [] self.sat_n = [] self.lc[inet].dclk = {} + self.lc[inet].ddclk = {} + self.lc[inet].dddclk = {} self.lc[inet].iode = {} self.lc[inet].dorb = {} + self.lc[inet].ddorb = {} self.iodssr = v['iodssr'] + self.uint = v['uint'] + self.mi = v['mi'] + sat = [] for k in range(nsat): i, sat_ = self.decode_sat(msg, i, sys) @@ -799,7 +916,10 @@ def decode_cssr_comb(self, msg, i, inet=0): sat.append(sat_) self.lc[inet].iode[sat_] = self.iode_n[k] self.lc[inet].dorb[sat_] = self.dorb_n[k, :] + self.lc[inet].ddorb[sat_] = self.ddorb_n[k, :] self.lc[inet].dclk[sat_] = self.dclk_n[k] + self.lc[inet].ddclk[sat_] = self.ddclk_n[k] + self.lc[inet].dddclk[sat_] = self.dddclk_n[k] self.nsat_n += nsat self.sys_n += [sys]*nsat @@ -823,6 +943,10 @@ def decode_cssr_ura(self, msg, i, inet=0): if timediff(self.time, self.lc[inet].t0s[sCType.URA]) > 0: self.ura = np.zeros(self.nsat_n) + self.iodssr = v['iodssr'] + self.uint = v['uint'] + self.mi = v['mi'] + for k in range(nsat): i, sat_ = self.decode_sat(msg, i, sys) s = self.sat_n.index(sat_) @@ -846,6 +970,10 @@ def decode_cssr_hclk(self, msg, i, inet=0): if timediff(self.time, self.lc[inet].t0s[sCType.HCLOCK]) > 0: self.lc[inet].dclk = {} + self.iodssr = v['iodssr'] + self.uint = v['uint'] + self.mi = v['mi'] + for k in range(v['nsat']): i, sat_ = self.decode_sat(msg, i, sys) i = self.decode_hclk_sat(msg, i, k) @@ -1008,31 +1136,55 @@ def decode_time(self, msg): return False - def out_log_ssr_clk(self, sys): + def out_log_ssr_clk(self, sys, flg_drift=True): """ output ssr clock correction to log file """ - self.fh.write(" {:s}\t{:s}\n".format("SatID", "dclk [m]")) + self.fh.write(f" IODSSR:{self.iodssr} ProvID:{self.pid} " + + f"SolID:{self.sid}\n") + self.fh.write(" {:s}\t{:s}".format("SatID", "dclk [m]")) + + if flg_drift: + self.fh.write("\t{:s}\t{:s}".format( + "ddclk [mm/s]", "dddclk [mm/s^2]")) + self.fh.write("\n") + for k, sat_ in enumerate(self.lc[0].dclk.keys()): sys_, _ = sat2prn(sat_) if sys_ != sys: continue - self.fh.write(" {:s}\t{:8.4f}\n".format(sat2id(sat_), - self.lc[0].dclk[sat_])) + self.fh.write(" {:s}\t{:8.4f}".format(sat2id(sat_), + self.lc[0].dclk[sat_])) + if flg_drift: + self.fh.write("\t{:8.4f}\t{:8.4f}".format( + self.lc[0].ddclk[sat_], self.lc[0].dddclk[sat_])) + self.fh.write("\n") - def out_log_ssr_orb(self, sys): + def out_log_ssr_orb(self, sys, flg_vel=True): """ output ssr orbit correction to log file """ - self.fh.write(" {:s}\t{:s}\t{:s}\t{:s}\t{:s}\n" - .format("SatID", "IODE", "Radial[m]", - "Along[m]", "Cross[m]")) + self.fh.write(f" IODSSR:{self.iodssr} ProvID:{self.pid} " + + f"SolID:{self.sid}\n") + self.fh.write(f" Reference Datum: {self.datum}\n") + self.fh.write(" {:s}\t{:s}\t{:s}\t{:s}\t{:s}" + .format("SatID", "IODE", "R[m]", "A[m]", "C[m]")) + if flg_vel: + self.fh.write("\t{:s}\t{:s}\t{:s}".format("dR[mm/s]", + "dA[mm/s]", "dC[mm/s]")) + self.fh.write("\n") for k, sat_ in enumerate(self.lc[0].dorb.keys()): sys_, _ = sat2prn(sat_) if sys_ != sys: continue - self.fh.write(" {:s}\t{:3d}\t{:6.3f}\t{:6.3f}\t{:6.3f}\n". + self.fh.write(" {:s}\t{:3d}\t{:7.4f}\t{:7.4f}\t{:7.4f}". format(sat2id(sat_), self.lc[0].iode[sat_], self.lc[0].dorb[sat_][0], self.lc[0].dorb[sat_][1], self.lc[0].dorb[sat_][2])) + if flg_vel: + self.fh.write("\t{:7.4f}\t{:7.4f}\t{:7.4f}". + format(self.lc[0].ddorb[sat_][0]*1e3, + self.lc[0].ddorb[sat_][1]*1e3, + self.lc[0].ddorb[sat_][2]*1e3)) + self.fh.write("\n") def out_log(self, obs=None, eph=None, geph=None, seph=None): """ output ssr message to log file """ @@ -1041,6 +1193,11 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write("{:4d}\t{:s}\n".format(self.msgtype, time2str(self.time))) + if self.subtype not in [sRTCM.SSR_META, sRTCM.SSR_GRID]: + self.fh.write(f" Update Interval: {self.udint_t[self.uint]}[s]") + + self.fh.write(f" MultiMsg: {self.mi}\n") + if self.subtype == sCSSR.CLOCK: self.out_log_ssr_clk(sys) @@ -1051,30 +1208,105 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.out_log_ssr_clk(sys) self.out_log_ssr_orb(sys) - if self.subtype == sCSSR.CBIAS or self.subtype == sCSSR.BIAS: + if self.subtype == sRTCM.SSR_META: + self.fh.write(f" ProvID:{self.pid} SolID:{self.sid} " + + f"NumEnt:{self.nm}\n") + + if self.subtype == sRTCM.SSR_TROP: + self.fh.write(f" IODSSR:{self.iodssr} ProvID:{self.pid} " + + f"SolID:{self.sid} GridID:{self.gid}\n") + self.fh.write(f" Residiaul Model Indicator: {self.rmi}\n") + if self.rmi: + trop = self.lc[inet].trop + self.fh.write(f" a(d/w): {trop.ah:7.4f} {trop.aw:7.4f}\n") + self.fh.write(f" b(d/w): {trop.bh:7.4f} {trop.bw:7.4f}\n") + self.fh.write(f" c(d/w): {trop.ch:7.4f} {trop.cw:7.4f}\n") + self.fh.write(f" ofst(d/w)[m]: {trop.ofsth:7.4f}" + + f" {trop.ofstw:7.4f}\n") + self.fh.write(f" ge(d/w)[mm/deg]: {trop.geh*1e3:7.4f}" + + f" {trop.gew*1e3:7.4f}\n") + self.fh.write(f" gn(d/w)[mm/deg]: {trop.gnh*1e3:7.4f}" + + f" {trop.gnw*1e3:7.4f}\n") + + ngp = trop.rh.shape[0] + for k in range(ngp): + self.fh.write(f" res(d/w)[m] {trop.rh[k]:7.4f}" + + f" {trop.rw[k]:7.4f}\n") + + if self.subtype == sRTCM.SSR_STEC: + self.fh.write(f" IODSSR:{self.iodssr} ProvID:{self.pid} " + + f"SolID:{self.sid} GridID:{self.gid}\n") + self.fh.write(f" Polynomial[TECU]: {self.pmi} (Grad:{self.pgi})\n") + if self.pmi: + for sat in self.lc[inet].sat_n: + self.fh.write(f" {sat2id(sat):s}") + ci = self.lc[inet].ci[sat] + self.fh.write(f" C00 {ci[0]:7.4f}") + if self.pgi: + self.fh.write(f" C01 {ci[1]:7.4f} C10 {ci[2]:7.4f}") + self.fh.write("\n") + self.fh.write(f" Residual[TECU]: {self.rmi}\n") + if self.rmi: + for sat in self.lc[inet].sat_n: + self.fh.write(f" {sat2id(sat):s}") + stec = self.lc[inet].stec[sat] + ngp = len(stec) + for k in range(ngp): + self.fh.write(f" {stec[k]:7.4f}") + self.fh.write("\n") + + if self.subtype == sRTCM.SSR_GRID: + self.fh.write(f" ProvID:{self.pid} GridID:{self.gid}" + + f" GridType:{self.gtype} Ofst:{self.ofst}\n") + self.fh.write(" RefPos:{:6.3f} {:6.3f} {:6.3f}\n" + .format(self.pos0[0], self.pos0[1], self.pos0[2])) + ng = self.dpos.shape[0] + for k in range(ng): + self.fh.write(" dpos: {:6.3f} {:6.3f} {:9.3f}\n".format( + self.dpos[k][0], self.dpos[k][1], self.dpos[k][2])) + + if self.subtype in [sCSSR.CBIAS, sCSSR.BIAS]: + self.fh.write(f" IODSSR:{self.iodssr} ProvID:{self.pid} " + + f"SolID:{self.sid}\n") self.fh.write(" {:s}\t{:s}\t{:s}\t{:s}\n" .format("SatID", "SigID", "CBias[m]", "...")) - for k, sat_ in enumerate(self.lc[inet].cbias.keys()): + for k, sat_ in enumerate(self.lc[0].cbias.keys()): sys_, _ = sat2prn(sat_) if sys_ != sys: continue self.fh.write(" {:s}\t".format(sat2id(sat_))) - for sig in self.lc[inet].cbias[sat_].keys(): + for sig in self.lc[0].cbias[sat_].keys(): self.fh.write("{:s}\t{:5.2f}\t" - .format(sig, self.lc[inet].cbias[sat_][sig])) + .format(sig, self.lc[0].cbias[sat_][sig])) self.fh.write("\n") - if self.subtype == sCSSR.PBIAS or self.subtype == sCSSR.BIAS: - self.fh.write(" {:s}\t{:s}\t{:s}\t{:s}\n" - .format("SatID", "SigID", "PBias[m]", "...")) - for k, sat_ in enumerate(self.lc[inet].pbias.keys()): + if self.subtype in [sCSSR.PBIAS, sCSSR.BIAS, sRTCM.SSR_PBIAS]: + self.fh.write(f" IODSSR:{self.iodssr} ProvID:{self.pid} " + + f"SolID: {self.sid}\n") + self.fh.write(f" ExtPB ID: {self.iexpb} Yaw: {self.iyaw}\n") + + self.fh.write(" {:s}".format("SatID")) + if self.iyaw: + self.fh.write("\t{:s}\t{:s}".format("Yaw", "dYaw")) + + self.fh.write("\t{:s}\t{:s}\t{:s}\t{:s}\t{:s}\n" + .format("SigID", "PBias[m]", "i", "cnt", "...")) + for k, sat_ in enumerate(self.lc[0].pbias.keys()): sys_, _ = sat2prn(sat_) if sys_ != sys: continue self.fh.write(" {:s}\t".format(sat2id(sat_))) - for sig in self.lc[inet].pbias[sat_].keys(): - self.fh.write("{:s}\t{:5.2f}\t" - .format(sig, self.lc[inet].pbias[sat_][sig])) + + if self.iyaw: + self.fh.write("\t{:7.4f}\t{:7.4f}\t" + .format(self.lc[0].yaw[sat_], + self.lc[0].dyaw[sat_])) + + for sig in self.lc[0].pbias[sat_].keys(): + self.fh.write("{:s}\t{:7.4f}\t{:1d}\t{:2d}\t" + .format(sig, self.lc[0].pbias[sat_][sig], + self.lc[0].si[sat_][sig], + self.lc[0].cnt[sat_][sig])) self.fh.write("\n") if self.subtype == sRTCM.ANT_DESC: @@ -1245,7 +1477,13 @@ def decode_msm(self, msg, i): svmask, sigmask = bs.unpack_from('u64u32', msg, i) i += 96 - ofst = 193 if sys == uGNSS.QZS else 1 + if sys == uGNSS.QZS: + ofst = 193 + elif sys == uGNSS.SBS: + ofst = 120 + else: + ofst = 1 + prn, self.nsat = self.decode_mask(svmask, 64, ofst) sig, self.nsig = self.decode_mask(sigmask, 32) sz = self.nsat*self.nsig @@ -1891,6 +2129,324 @@ def decode_unicode_text(self, msg, i): ic = i//8 self.ustr = bytes(msg[ic:ic+ncp]).decode() + def decode_ssr_metadata(self, msg, i): + """ decode SSR Metadata Message """ + # nm: nomber of ssr model/correction entries + iodssr, pid, sid, nm = bs.unpack_from('u4u16u4u5', msg, i) + i += 29 + + self.iodssr = iodssr + self.pid = pid + self.sid = sid + self.nm = nm # number of entries + + if nm == 0: # no metadata + i += 7 + return i + + mpi = 0 + # Model/Correction Part + + # Model/Correction Type Indicator: DF+64 + # Model/Correction Application Indicator: MT+65 + # Non-Default Model/Correction Indicator: MT+66 + mt, ap, mi = bs.unpack_from('u5u1b1', msg, i) + i += 7 + if mi: + mci = bs.unpack_from('u3', msg, i)[0] # model/correction id + i += 3 + iodmi = bs.unpack_from('b1', msg, i)[0] + i += 1 + if iodmi: + iodm = bs.unpack_from('u6', msg, i)[0] + i += 6 + if mi: + mpi = bs.unpack_from('b1', msg, i)[0] + i += 1 + if mpi: + nb = bs.unpack_from('u8', msg, i)[0] + i += 8 + if mi and mpi: + data = bs.unpack_from('u'+str(nb), msg, i)[0] + i += nb + + if mt == 7: # GNSS BE Reference + gnss, be = bs.unpack_from('u4u4', msg, i) + i += 8 + + return i + + def decode_ssr_satant(self, sys, msg, i): + """ decode GNSS Satellite Antenna Message (E80,81,82,83,84) """ + pid, iods, pci, gdi, ndi = bs.unpack_from('u16u6b1b1b1', msg, i) + i += 25 + if ndi: + nmas, nde = bs.unpack_from('u5u4', msg, i) + i += 9 + ssi, satmask = bs.unpack_from('u1u64', msg, i) + i += 65 + + prn, nsat = self.decode_mask(satmask, 64, 1) + for k in range(nsat): + fsi, freqmask = bs.unpack_from('u1u6', msg, i) + i += 7 + freq, nf = self.decode_mask(freqmask, 6, 0) + for j in range(nf): + nci = bs.unpack_from('b1', msg, i)[0] + i += 1 + if nci: + nc = bs.unpack_from('s12', msg, i)[0] + i += 12 + if ndi: + ndc = bs.unpack_from('s'+str(3+nde), msg, i)[0] + i += 3+nde + return i + + def decode_ssr_grid(self, msg, i): + """ decode SSR Grid Definition Message (E61) """ + self.pid, self.mi, gid, gtype = bs.unpack_from('u16u1u10u3', msg, i) + i += 30 + + self.gid = gid + self.gtype = gtype + self.inet = gid + + # """load grid coordinates from file """ + dtype0 = [('nid', 'i4'), ('gid', 'i4'), + ('lat', 'f8'), ('lon', 'f8'), ('alt', 'f8')] + + if gtype == 0: # grid type 0 + lat0, lon0, alt0, ofst, npnt = bs.unpack_from( + 's18s19u10u12u8', msg, i) + i += 67 + + lat = self.sval(lat0, 18, 1e-3) + lon = self.sval(lon0, 19, 1e-3) + alt = self.sval(alt0, 10, 12.5)-1000.0 + self.grid = np.array([(gid, ofst, lat, lon, alt)], dtype=dtype0) + self.dpos = np.zeros((npnt, 3)) + self.pos0 = np.array([lat, lon, alt]) + self.ofst = ofst + + for k in range(ofst, ofst+npnt): + dlat, dlon, dalt = bs.unpack_from('s13s14s9', msg, i) + i += 36 + lat += self.sval(dlat, 13, 1e-3) + lon += self.sval(dlon, 14, 1e-3) + alt += self.sval(dalt, 9, 12.5) + d = np.array([(gid, k+1, lat, lon, alt)], dtype=dtype0) + self.grid = np.append(self.grid, d) + self.dpos[k-ofst, :] = (dlat*1e-3, dlon*1e-3, dalt*12.5) + + elif gtype == 1: # grid type 1 + lat0, lon0, ofst, npnt = bs.unpack_from('s18s19u12u8', msg, i) + i += 57 + lat = self.sval(lat0, 18, 1e-3) + lon = self.sval(lon0, 19, 1e-3) + self.grid = np.array([], dtype=dtype0) + for k in range(npnt): + dlat, dlon = bs.unpack_from('s13s14', msg, i) + i += 27 + lat += self.sval(dlat, 13, 1e-3) + lon += self.sval(dlon, 14, 1e-3) + d = np.array([(gid, k, lat, lon, 0)], dtype=dtype0) + self.grid = np.append(self.grid, d) + + elif gtype == 2: # grid type 2 + lat0_, lon0_, nrow, ncol, dlat_, dlon_, gma = bs.unpack_from( + 's18s19u6u6u9u10b1', msg, i) + i += 69 + lat0 = self.sval(lat0_, 18, 1e-3) + lon0 = self.sval(lon0_, 19, 1e-3) + dlat = self.sval(dlat_, 9, 0.01) + dlon = self.sval(dlon_, 10, 0.01) + self.grid = np.array() + + if gma: + latmask = bs.unpack_from('u'+str(nrow), msg, i)[0] + i += nrow + lonmask = bs.unpack_from('u'+str(ncol), msg, i)[0] + i += ncol + + ilat, nlat = self.decode_mask(latmask, nrow, 0) + ilon, nlon = self.decode_mask(lonmask, ncol, 0) + n = nlat*nlon + egpmask = bs.unpack_from('u'+str(n), msg, i)[0] + i += n + iex, nex = self.decode_mask(egpmask, n, 0) + else: + iex = [] + + ii = ofst + self.grid = np.array([], dtype=dtype0) + for j in range(ncol): + for k in range(nrow): + lat = lat0 + dlat*j + lon = lon0 + dlon*k + idx = nrow*j+k + if gma and idx not in iex: + continue + d = np.array([(gid, ii, lat, lon, 0)], dtype=dtype0) + self.grid = np.append(self.grid, d) + ii += 1 + + return i + + def decode_ssr_pbias_ex(self, msg, i): + """ decode SSR Extended Satellite Phase Bias Message """ + + """ + - linked with Phase bias messages with epoch time for given GNSS and + id (DF+002). + - widelane signal group indicator (DF484) shall be repeated for + every satellite and signal in corresponding phase bias message. + - the message shall be sent before corresponding phase bias message. + + - DF484: + 00 - no widelane group information for this signal + 10 - signal belongs to group one of widelane combinations + with integer property + + """ + sys = self.get_ssr_sys(self.msgtype) + i, v = self.decode_head(msg, i, sys) + + nsig = 0 # TBD + + # Extended Phase Bias Property ID + self.eid = bs.unpack_from('u4', msg, i)[0] # DF+002 + i += 4 + self.lc[0].wl = np.zeros(nsig) + for j in range(nsig): + self.lc[0].wl[j] = bs.unpack_from('u2', msg, i)[0] + i += 2 + return i + + def decode_ssr_trop(self, msg, i, inet=0): + """ decode SSR Tropspheric Correction Message """ + tow, uint, mi = bs.unpack_from('u20u4u1', msg, i) + i += 25 + iodssr, pid, sid = bs.unpack_from('u4u16u4', msg, i) + i += 24 + gid, rmi = bs.unpack_from('u10b1', msg, i) + i += 11 + + # atmospheric model part + dah, dbh, dch, daw, dbw, dcw = bs.unpack_from( + 's11s9s9s13s6s5', msg, i) + i += 53 + ofsth, geh, gnh = bs.unpack_from('s13s15s15', msg, i) + i += 43 + ofstw, gew, gnw = bs.unpack_from('s13s15s15', msg, i) + i += 43 + + inet = gid + + trop = TropModel() + # mapping function for hydrostatic/wet term + + trop.ah = self.sval(dah, 11, 2.5e-7)+0.00118 + trop.bh = self.sval(dbh, 9, 5e-6)+0.00298 + trop.ch = self.sval(dch, 9, 2e-4)+0.0682 + trop.aw = self.sval(daw, 13, 1e-6)+0.000104 + trop.bw = self.sval(dbw, 6, 2.5e-5)+0.0015 + trop.cw = self.sval(dcw, 5, 2e-3)+0.048 + trop.aht = 2.53e-5 + trop.bht = 5.49e-3 + trop.cht = 1.14e-3 + + # hydrostatic + trop.ofsth = ofsth*0.1e-3+2.3 # offset [m] + trop.geh = geh*0.01e-3 # west-east gradient [m/deg] + trop.gnh = gnh*0.01e-3 # south-north gradient [m/deg] + # wet + trop.ofstw = ofstw*0.1e-3+0.252 # offset [m] + trop.gew = gew*0.1e-3 # west-east gradient [m/deg] + trop.gnw = gnw*0.01e-3 # south-north gradient [m/deg] + + if rmi: # residual information part + ofstg, ngp, n, m = bs.unpack_from('u12u12u4u4', msg, i) + i += 32 + trop.rh = np.zeros(ofstg+ngp) + trop.rw = np.zeros(ofstg+ngp) + + for k in range(ofstg, ofstg+ngp): + rh, rw = bs.unpack_from(f's{n}s{m}', msg, i) + i += n+m + # hydrostatic grid point residual [m] + trop.rh[k] = self.sval(rh, n, 0.1e-3) + # wet grid point residual [m] + trop.rw[k] = self.sval(rw, m, 0.1e-3) + + self.lc[inet].trop = trop + + self.iodssr = iodssr + self.rmi = rmi + return i + + def decode_ssr_iono(self, msg, i, inet=0): + """ decode SSR Regional Ionospheric Correction message (E96,E97,E98,E99,E100) """ + sys = self.get_ssr_sys(self.msgtype) + i, v = self.decode_head(msg, i, sys) + # Grid ID DF+22 + # Polynomial Model Indicator DF+96 + # Residual Model Indicator + gid, pmi, rmi, satmask = bs.unpack_from('u10b1b1u64', msg, i) + i += 76 + # Zenith-mapped STEC Polynomial Gradient Indicator DF+60 + svid, nsat = self.decode_mask(satmask, 64, 1) + sat = np.zeros(nsat, dtype=int) + for k in range(nsat): + # sat[k] = self.svid2sat(sys, svid[k], True) + sat[k] = self.svid2sat(sys, svid[k]) + + inet = gid + self.lc[inet].sat_n = sat + + if pmi: # polynomial model information part + self.pgi = bs.unpack_from('b1', msg, i)[0] + i += 1 + sz = 3 if self.pgi else 1 + self.lc[inet].ci = {} + for k in range(nsat): + sat_ = sat[k] + self.lc[inet].ci[sat_] = np.zeros(sz) + c00 = bs.unpack_from('s17', msg, i)[0] + i += 17 + self.lc[inet].ci[sat_][0] = self.sval(c00, 17, 0.01) + if self.pgi: # polynomial grandient indicator is true + c01, c10 = bs.unpack_from('s18s18', msg, i) + i += 36 + self.lc[inet].ci[sat_][1] = self.sval(c01, 18, 1e-3) + self.lc[inet].ci[sat_][2] = self.sval(c10, 18, 1e-3) + + if rmi: # residual model information part + ofst, ng, rlen = bs.unpack_from('u12u12u5', msg, i) + i += 29 + if rlen > 0: + # Zenith-mapped STEC Grid Point Residual + # Resolution Scale Factor Exponent + sf = bs.unpack_from('u3', msg, i)[0] # DF+073 + i += 3 + + self.lc[inet].stec = {} + fmt = 's'+str(rlen) + + for k in range(nsat): + sat_ = sat[k] + self.lc[inet].stec[sat_] = np.zeros(ofst+ng) + for j in range(ofst, ofst+ng): + res = bs.unpack_from(fmt, msg, i)[0] # DF+062 + i += rlen + scl = 1e-3*(2**sf) + self.lc[inet].stec[sat_][j] = self.sval(res, rlen, scl) + + self.gid = gid + self.pmi = pmi + self.rmi = rmi + + return i + def decode_integrity_min(self, msg, i): """ RTCM SC-134 minimum integrity message (MT3) """ @@ -1962,22 +2518,23 @@ def decode_integrity_min(self, msg, i): sig_msts[sys][sat] = bs.unpack_from('u32', msg, i)[0] i += 32 - self.integ.pid = pid - self.integ.tow = tow + self.integ.pid = pid + self.integ.tow = tow - self.integ.iod_sys = iod_sys - self.integ.sts = sts_tbl - self.integ.mst = mst_tbl - self.integ.src = src_tbl + self.integ.iod_sys = iod_sys + self.integ.sts = sts_tbl + self.integ.mst = mst_tbl + self.integ.src = src_tbl - self.integ.sigmask = sigmask - self.integ.iod_sig = iod_sig - self.integ.sig_ists = sig_ists - self.integ.sig_msts = sig_msts + self.integ.sigmask = sigmask + self.integ.iod_sig = iod_sig + self.integ.sig_ists = sig_ists + self.integ.sig_msts = sig_msts + return i def decode_integrity_ext(self, msg, i): """ RTCM SC-134 extended integrity message - service levels and overbounding parameters (MT4) """ + service levels and overbounding parameters (MT4) """ # GPS Epoch Time (TOW) DFi008 # augmentation service provider id DFi027 @@ -2126,10 +2683,11 @@ def decode_integrity_ext(self, msg, i): self.integ.ob_p[sys][sat] = ob_p self.integ.sig_ob_c[sys][sat] = sig_ob_c self.integ.ob_c[sys][sat] = ob_c + return i def decode_integrity_ext_sis_local(self, msg, i): """ RTCM SC-134 extended integrity message - signal in space integrity and local error parameters (MT5) """ + signal in space integrity and local error parameters (MT5) """ # integrity parameter IOD DFi006 # augmentation service provider id DFi027 @@ -2142,7 +2700,8 @@ def decode_integrity_ext_sis_local(self, msg, i): # update rate interval DFi067 (0.1) # Validity Area Type DFi056 # Time correlation integrity/continuity flag DFi137 - mask_sys, vp, uri, atype, fc = bs.unpack_from('u16u4u16u2u1', msg, i) + mask_sys, vp, uri, atype, fc = bs.unpack_from( + 'u16u4u16u2u1', msg, i) i += 39 sys_t, nsys = self.decode_mask(mask_sys, 16, ofst=0) @@ -2178,62 +2737,63 @@ def decode_integrity_ext_sis_local(self, msg, i): self.integ.pnt_vr = [x, y, z, vr] - for sys_ in sys_t: - sys = self.integ.sys_tbl[sys_] + for sys_ in sys_t: + sys = self.integ.sys_tbl[sys_] - # GNSS satellite mask DFi009 - # issue of GNSS satellite mask DFi010 - mask_sat, iod_mask = bs.unpack_from('u64u2', msg, i) - i += 66 - - svid_t, nsat = self.decode_mask(mask_sat, 64) + # GNSS satellite mask DFi009 + # issue of GNSS satellite mask DFi010 + mask_sat, iod_mask = bs.unpack_from('u64u2', msg, i) + i += 66 - self.integ.idx_c[sys] = {} - self.integ.idx_p[sys] = {} - self.integ.cbr[sys] = {} - self.integ.sig_dp[sys] = {} - self.integ.idx_dp[sys] = {} + svid_t, nsat = self.decode_mask(mask_sat, 64) - self.integ.oc[sys] = {} - self.integ.sig_ion[sys] = {} - self.integ.idx_ion[sys] = {} - self.integ.sig_trp[sys] = {} - self.integ.idx_trp[sys] = {} + self.integ.idx_c[sys] = {} + self.integ.idx_p[sys] = {} + self.integ.cbr[sys] = {} + self.integ.sig_dp[sys] = {} + self.integ.idx_dp[sys] = {} - for svid in svid_t: - ofst = 192 if sys == uGNSS.QZS else 0 - prn = svid+ofst - sat = prn2sat(sys, prn) - - # Pseudorange error rate integrity index DFi120 - # Phase error rate integrity index DFi121 - # Nominal pseudorange bias rate of change DFi125 - # Bounding sigma on phase-range-rate error DFi122 - # Phase-range-rate error rate integrity index DFi123 - idx_c, idx_p, cbr, sig_dp, idx_dp = bs.unpack_from( - 'u4u4u3u4u4', msg, i) - i += 19 - - # Orbit and clock error rate integrity parameter DFi127 - # Residual ionospheric error standard deviation DFi128 - # Index of bounding parameter on change in iono error DFi129 - # Residual tropospheric error standard deviation DFi130 - # Index of bounding parameter on change in tropo error DFi131 - oc, sig_ion, idx_ion, sig_trp, idx_trp = bs.unpack_from( - 'u3u3u3u3u3', msg, i) - i += 15 + self.integ.oc[sys] = {} + self.integ.sig_ion[sys] = {} + self.integ.idx_ion[sys] = {} + self.integ.sig_trp[sys] = {} + self.integ.idx_trp[sys] = {} - self.integ.idx_c[sys][sat] = idx_c - self.integ.idx_p[sys][sat] = idx_p - self.integ.cbr[sys][sat] = cbr - self.integ.sig_dp[sys][sat] = sig_dp - self.integ.idx_dp[sys][sat] = idx_dp + for svid in svid_t: + ofst = 192 if sys == uGNSS.QZS else 0 + prn = svid+ofst + sat = prn2sat(sys, prn) - self.integ.oc[sys][sat] = oc - self.integ.sig_ion[sys][sat] = sig_ion - self.integ.idx_ion[sys][sat] = idx_ion - self.integ.sig_trp[sys][sat] = sig_trp - self.integ.idx_trp[sys][sat] = idx_trp + # Pseudorange error rate integrity index DFi120 + # Phase error rate integrity index DFi121 + # Nominal pseudorange bias rate of change DFi125 + # Bounding sigma on phase-range-rate error DFi122 + # Phase-range-rate error rate integrity index DFi123 + idx_c, idx_p, cbr, sig_dp, idx_dp = bs.unpack_from( + 'u4u4u3u4u4', msg, i) + i += 19 + + # Orbit and clock error rate integrity parameter DFi127 + # Residual ionospheric error standard deviation DFi128 + # Index of bounding parameter on change in iono error DFi129 + # Residual tropospheric error standard deviation DFi130 + # Index of bounding parameter on change in tropo error DFi131 + oc, sig_ion, idx_ion, sig_trp, idx_trp = bs.unpack_from( + 'u3u3u3u3u3', msg, i) + i += 15 + + self.integ.idx_c[sys][sat] = idx_c + self.integ.idx_p[sys][sat] = idx_p + self.integ.cbr[sys][sat] = cbr + self.integ.sig_dp[sys][sat] = sig_dp + self.integ.idx_dp[sys][sat] = idx_dp + + self.integ.oc[sys][sat] = oc + self.integ.sig_ion[sys][sat] = sig_ion + self.integ.idx_ion[sys][sat] = idx_ion + self.integ.sig_trp[sys][sat] = sig_trp + self.integ.idx_trp[sys][sat] = idx_trp + return i def decode_integrity_ext_service_area(self, msg, i): """ RTCM SC-134 Extended Service Area Parameters (MT6) """ @@ -2242,14 +2802,16 @@ def decode_integrity_ext_service_area(self, msg, i): # augmentation service provider id DFi027 # GPS Epoch Time (TOW) DFi008 # augmentation integrity level DFi026 - iod_sa, tow, pid, alvl = bs.unpack_from('u6u30u12u4', msg, i) + iod_sa, tow, pid, alvl = bs.unpack_from( + 'u6u30u12u4', msg, i) i += 52 # GNSS Constellation Mask DFi013 # Validity Period DFi065 (0-15) # update rate interval DFi067 (0.1) # Validity Area Type DFi056 - mask_sys, vp, uri, atype = bs.unpack_from('u16u4u16u2', msg, i) + mask_sys, vp, uri, atype = bs.unpack_from( + 'u16u4u16u2', msg, i) i += 38 sys_t, nsys = self.decode_mask(mask_sys, 16, ofst=0) @@ -2297,6 +2859,7 @@ def decode_integrity_ext_service_area(self, msg, i): x, y, z, vr = bs.unpack_from('s34s34s34u20', msg, i) i += 122 self.integ.pnt_vr = [x, y, z, vr] + return i def decode_integrity_quality(self, msg, i): """ RTCM SC-134 Message Quality Indicator (MT7) """ @@ -2325,6 +2888,7 @@ def decode_integrity_quality(self, msg, i): for k in range(np): self.integ.qi[k] = bs.unpack_from('u8', msg, i)[0] i += 8 + return i def decode_integrity_cnr_acg(self, msg, i): """ RTCM SC-134 CNR/ACG Signal In Space Monitoring Message (MT8) """ @@ -2352,54 +2916,56 @@ def decode_integrity_cnr_acg(self, msg, i): self.integ.pos[k, :] = [lat, lon, alt] - # Constellation Mask DFi013 - # GNSS Satellite Mask DFi009 - # Issue of GNSS Satellite Mask DFi010 - mask_c, mask_s, iod_sat = bs.unpack_from('u16u64u2', msg, i) - i += 82 - - self.integ.iod_sat[k] = iod_sat + # Constellation Mask DFi013 + # GNSS Satellite Mask DFi009 + # Issue of GNSS Satellite Mask DFi010 + mask_c, mask_s, iod_sat = bs.unpack_from( + 'u16u64u2', msg, i) + i += 82 - sys_t, nsys = self.decode_mask(mask_c, 16, ofst=0) - svid_t, nsat = self.decode_mask(mask_s, 64) + self.integ.iod_sat[k] = iod_sat - for sys in sys_t: - self.integ.band[sys] = {} - self.integ.cnr[sys] = {} - self.integ.agc[sys] = {} + sys_t, nsys = self.decode_mask(mask_c, 16, ofst=0) + svid_t, nsat = self.decode_mask(mask_s, 64) - for svid in svid_t: - ofst = 192 if sys == uGNSS.QZS else 0 - prn = svid+ofst - sat = prn2sat(sys, prn) + for sys in sys_t: + self.integ.band[sys] = {} + self.integ.cnr[sys] = {} + self.integ.agc[sys] = {} - # GNSS Signal Mask DFi033 - # Issue of GNSS Signal Mask DFi012 - mask_sig, iod_sig = bs.unpack_from('u32u2', msg, i) - i += 34 - sig_t, nsig = self.decode_mask(mask_sig, 32, ofst=0) - - self.integ.band[sys][sat] = np.zeros(nsig) - self.integ.cnr[sys][sat] = np.zeros(nsig) - self.integ.agc[sys][sat] = np.zeros(nsig) - - for sig in sig_t: - # Frequency band ID DFi135 - band = bs.unpack_from('u5', msg, i)[0] - i += 5 - self.integ.band[sys][sat][sig] = band - - for sig in sig_t: - # CNR carrier to noise ratio DFi133 - cnr = bs.unpack_from('u8', msg, i)[0] - i += 8 - self.integ.cnr[sys][sat][sig] = cnr + for svid in svid_t: + ofst = 192 if sys == uGNSS.QZS else 0 + prn = svid+ofst + sat = prn2sat(sys, prn) - for sig in sig_t: - # AGC, Automatic Gain Control DFi134 - agc = bs.unpack_from('u8', msg, i)[0] - i += 8 - self.integ.agc[sys][sat][sig] = agc + # GNSS Signal Mask DFi033 + # Issue of GNSS Signal Mask DFi012 + mask_sig, iod_sig = bs.unpack_from('u32u2', msg, i) + i += 34 + sig_t, nsig = self.decode_mask(mask_sig, 32, ofst=0) + + self.integ.band[sys][sat] = np.zeros(nsig) + self.integ.cnr[sys][sat] = np.zeros(nsig) + self.integ.agc[sys][sat] = np.zeros(nsig) + + for sig in sig_t: + # Frequency band ID DFi135 + band = bs.unpack_from('u5', msg, i)[0] + i += 5 + self.integ.band[sys][sat][sig] = band + + for sig in sig_t: + # CNR carrier to noise ratio DFi133 + cnr = bs.unpack_from('u8', msg, i)[0] + i += 8 + self.integ.cnr[sys][sat][sig] = cnr + + for sig in sig_t: + # AGC, Automatic Gain Control DFi134 + agc = bs.unpack_from('u8', msg, i)[0] + i += 8 + self.integ.agc[sys][sat][sig] = agc + return i def decode_integrity_vmap(self, msg, i): """ RTCM SC-134 Satellite Visibility Map Message (MT9) """ @@ -2430,16 +2996,16 @@ def decode_integrity_vmap(self, msg, i): self.integ.pos[k, :] = [lat*1.1e-8, lon*1.1e-8, alt] - self.integ.azel = np.zeros((naz, 2)) + self.integ.azel = np.zeros((naz, 2)) - # Azimuth DFi206 - # Elevation Mask DFi208 - az = 0 - for k in range(naz): - daz, mask_el = bs.unpack_from('u9u7', msg, i) - i += 16 - az += daz - self.integ.azel[k, :] = [az, mask_el] + # Azimuth DFi206 + # Elevation Mask DFi208 + az = 0 + for k in range(naz): + daz, mask_el = bs.unpack_from('u9u7', msg, i) + i += 16 + az += daz + self.integ.azel[k, :] = [az, mask_el] return i @@ -2453,83 +3019,84 @@ def decode_integrity_mmap(self, msg, i): self.ver = rev self.wg = wg-1 - # GPS Epoch Time (TOW) DFi008 - # number of area points DFi201 - # multipath model ID DFi209: 0:GMM,1:MBM,2:JM - tow, narea, mm_id, mcf = bs.unpack_from('u30u8u3u1', msg, i) - i += 42 - tow *= 1e-3 - self.integ.pos = np.zeros((narea, 3)) - self.integ.mm_id = mm_id - - if mm_id == 0: - - for k in range(narea): - # Area Point - Lat DFi202 - # Area Point - Lon DFi203 - # Area Point - Height DFi204 - lat, lon, alt = bs.unpack_from('s34s35s14', msg, i) - i += 83 + # GPS Epoch Time (TOW) DFi008 + # number of area points DFi201 + # multipath model ID DFi209: 0:GMM,1:MBM,2:JM + tow, narea, mm_id, mcf = bs.unpack_from( + 'u30u8u3u1', msg, i) + i += 42 + tow *= 1e-3 + self.integ.pos = np.zeros((narea, 3)) + self.integ.mm_id = mm_id + + if mm_id == 0: + + for k in range(narea): + # Area Point - Lat DFi202 + # Area Point - Lon DFi203 + # Area Point - Height DFi204 + lat, lon, alt = bs.unpack_from('s34s35s14', msg, i) + i += 83 + + self.integ.pos[k, :] = [lat*1.1e-8, lon*1.1e-8, alt] + + elif mm_id == 1 or mm_id == 2: + sigmask = np.array(narea, dtype=np.int32) + for k in range(narea): + # GNSS Signal Modulation Mask - DFi214 + # Area Point - Lat DFi202 + # Area Point - Lon DFi203 + # Area Point - Height DFi204 + sigmask[k], lat, lon, alt = bs.unpack_from( + 'u8s34s35s14', msg, i) + i += 91 self.integ.pos[k, :] = [lat*1.1e-8, lon*1.1e-8, alt] - elif mm_id == 1 or mm_id == 2: - sigmask = np.array(narea, dtype=np.int32) - for k in range(narea): - # GNSS Signal Modulation Mask - DFi214 - # Area Point - Lat DFi202 - # Area Point - Lon DFi203 - # Area Point - Height DFi204 - sigmask[k], lat, lon, alt = bs.unpack_from( - 'u8s34s35s14', msg, i) - i += 91 - - self.integ.pos[k, :] = [lat*1.1e-8, lon*1.1e-8, alt] + if mm_id == 0: + # Number of GMM components DFi210 + ngmm = bs.unpack_from('u2', msg, i)[0] + i += 2 - if mm_id == 0: - # Number of GMM components DFi210 - ngmm = bs.unpack_from('u2', msg, i)[0] - i += 2 + self.integ.mm_param = np.zeros((ngmm, 3)) - self.integ.mm_param = np.zeros((ngmm, 3)) + for k in range(ngmm): + # GMM component probability DFi211 + # GMM component expectation DFi212 + # GMM component standard deviation DFi213 + prob, exp_, std_ = bs.unpack_from('u4u4u4', msg, i) + i += 12 - for k in range(ngmm): - # GMM component probability DFi211 - # GMM component expectation DFi212 - # GMM component standard deviation DFi213 - prob, exp_, std_ = bs.unpack_from('u4u4u4', msg, i) - i += 12 + prob *= 0.0625 + exp = self.integ.mu_k_t[exp_] + std = self.integ.sig_k_t[std_] + self.integ.mm_param[k, :] = [prob, exp, std] - prob *= 0.0625 - exp = self.integ.mu_k_t[exp_] - std = self.integ.sig_k_t[std_] - self.integ.mm_param[k, :] = [prob, exp, std] + elif mm_id == 1 or mm_id == 2: + self.integ.mm_param = {} + n = 3 if mm_id == 1 else 4 - elif mm_id == 1 or mm_id == 2: - self.integ.mm_param = {} - n = 3 if mm_id == 1 else 4 + for k in range(narea): + sig_t, nsig = self.decode_mask(sigmask[k], 8, ofst=0) + prm = np.zeros((nsig, n)) - for k in range(narea): - sig_t, nsig = self.decode_mask(sigmask[k], 8, ofst=0) - prm = np.zeros((nsig, n)) - - for j in range(nsig): - # Multipath parameter a DFi215 - # Multipath parameter b DFi216 - # Multipath parameter c DFi217 - a, b, c = bs.unpack_from('u4s5s5', msg, i) - i += 14 + for j in range(nsig): + # Multipath parameter a DFi215 + # Multipath parameter b DFi216 + # Multipath parameter c DFi217 + a, b, c = bs.unpack_from('u4s5s5', msg, i) + i += 14 - a = self.integ.sig_k_t[a] - prm[j, 0:3] = [a, b*0.25, c*0.0625] + a = self.integ.sig_k_t[a] + prm[j, 0:3] = [a, b*0.25, c*0.0625] - if mm_id == 2: - # Multipath parameter d DFi218 - d = bs.unpack_from('u8', msg, i)[0] - i += 8 - prm[j, 3] = d*0.3515625 + if mm_id == 2: + # Multipath parameter d DFi218 + d = bs.unpack_from('u8', msg, i)[0] + i += 8 + prm[j, 3] = d*0.3515625 - self.integ.mm_param[k] = prm + self.integ.mm_param[k] = prm return i @@ -2539,7 +3106,8 @@ def decode_integrity_ssr(self, msg, i): i += 12 # SSR provider ID, solution type, iod - self.pid, self.sid, self.iodssr = bs.unpack_from('u16u4u4', msg, i) + self.pid, self.sid, self.iodssr = bs.unpack_from( + 'u16u4u4', msg, i) i += 24 # mask_sys:: DFi013 0:GPS,1:GLO,2:GAL,3:BDS,4:QZS,5:IRN @@ -2551,11 +3119,14 @@ def decode_integrity_ssr(self, msg, i): vp, uri = bs.unpack_from('u4u16', msg, i) i += 20 - self.integ.vp = self.integ.vp_tbl[vp] # Validity Period DFi065 (0-15) - self.integ.uri = uri*0.1 # update rate interval DFi067 (0.1) + # Validity Period DFi065 (0-15) + self.integ.vp = self.integ.vp_tbl[vp] + # update rate interval DFi067 (0.1) + self.integ.uri = uri*0.1 sys_t, nsys = self.decode_mask(mask_sys, 16, ofst=0) iod_sys = {} + flag_t = {} for sys_ in sys_t: sys = self.integ.sys_tbl[sys_] @@ -2580,8 +3151,8 @@ def decode(self, msg, subtype=None): self.msgtype = bs.unpack_from('u12', msg, i)[0] i += 12 if self.monlevel > 0 and self.fh is not None: - self.fh.write("##### RTCM 3.x type:{:04d} msg_size: {:d} data_len: {:d} bytes\n". - format(self.msgtype, self.dlen, self.len)) + self.fh.write("##### RTCM 3.x type:{:04d} msg_size: {:d} bytes\n". + format(self.msgtype, self.dlen)) obs = None eph = None @@ -2590,7 +3161,7 @@ def decode(self, msg, subtype=None): self.subtype = subtype - # Network RTK residual messages + # SSR messages if self.msgtype in (1057, 1063, 1240, 1246, 1252, 1258): self.subtype = sCSSR.ORBIT i = self.decode_cssr_orb(msg, i) @@ -2615,10 +3186,10 @@ def decode(self, msg, subtype=None): i = self.decode_cssr_hclk(msg, i) elif self.msgtype == 4076: # IGS SSR i = self.decode_igsssr(msg, i) - elif self.msgtype in (1030, 1031, 1303, 1304, 1305): + elif self.msgtype in (1030, 1031, 1303, 1304, 1305): # Network RTK self.subtype = sRTCM.NRTK_RES i = self.decode_nrtk_residual(msg, i) - elif self.is_msmtype(self.msgtype): + elif self.is_msmtype(self.msgtype): # MSM self.subtype = sRTCM.MSM i, obs = self.decode_msm(msg, i) elif self.msgtype in (1007, 1008, 1033): @@ -2654,6 +3225,48 @@ def decode(self, msg, subtype=None): elif self.msgtype == 1029: self.decode_unicode_text(msg, i) + # test messages for RTCM SSR + elif self.msgtype == 60: + self.subtype = sRTCM.SSR_META + i = self.decode_ssr_metadata(msg, i) + elif self.msgtype == 61: + self.subtype = sRTCM.SSR_GRID + i = self.decode_ssr_grid(msg, i) + elif self.msgtype in [1057, 41, 62, 63, 64]: + self.subtype = sCSSR.ORBIT + i = self.decode_cssr_orb(msg, i) + elif self.msgtype in [1058, 42, 65, 66, 67]: + self.subtype = sCSSR.CLOCK + i = self.decode_cssr_clk(msg, i) + elif self.msgtype in [1060, 44, 71, 72, 73]: + self.subtype = sCSSR.COMBINED + i = self.decode_cssr_comb(msg, i) + elif self.msgtype in [1061, 45, 74, 75, 76]: + self.subtype = sCSSR.URA + i = self.decode_cssr_ura(msg, i) + elif self.msgtype in [1062, 46, 77, 78, 79]: + self.subtype = sCSSR.CLOCK + i = self.decode_cssr_hclk(msg, i) + elif self.msgtype in [1059, 43, 68, 69, 70]: + self.subtype = sCSSR.CBIAS + i = self.decode_cssr_cbias(msg, i) + elif self.msgtype >= 80 and self.msgtype < 85: + self.subtype = sRTCM.SSR_SATANT + i = self.decode_ssr_satant(msg, i) + elif self.msgtype >= 85 and self.msgtype < 90: + if not self.mask_pbias: + self.subtype = sRTCM.SSR_PBIAS + i = self.decode_cssr_pbias(msg, i) + elif self.msgtype >= 90 and self.msgtype < 95: + self.subtype = sRTCM.SSR_PBIAS_EX + i = self.decode_ssr_pbias_ex(msg, i) + elif self.msgtype == 95: + self.subtype = sRTCM.SSR_TROP + i = self.decode_ssr_trop(msg, i) + elif self.msgtype >= 96 and self.msgtype < 101: + self.subtype = sRTCM.SSR_STEC + i = self.decode_ssr_iono(msg, i) + # test messages for SC-134 elif self.msgtype == 3: # minimum integrity self.subtype = sRTCM.INTEG_MIN @@ -2691,8 +3304,8 @@ def decode(self, msg, subtype=None): elif self.msgtype == 54: # SSR integrity test msg if self.subtype == 9: self.decode_integrity_vmap(msg, i) - elif self.subtype == 10: - self.decode_integrity_mmap(msg, i) + elif self.subtype == 10: + self.decode_integrity_mmap(msg, i) else: self.subtype = -1 @@ -2712,6 +3325,30 @@ def __init__(self): self.pid = 0 # SSR Provider ID self.sid = 0 # SSR Solution Type + self.mi = 0 # multiple message bit + self.iods = 0 # issue of data station + self.refid = 0 # reference station ID + + self.csi = 0 # clock streering indicator + self.eci = 0 # external clock indicator + self.si = 0 # smoothing indicator + self.smi = 0 # smoothing interval + + self.msm_t = { + uGNSS.GPS: 1071, uGNSS.GLO: 1081, uGNSS.GAL: 1091, + uGNSS.SBS: 1101, uGNSS.QZS: 1111, uGNSS.BDS: 1121 + } + + self.ssr_t = { + uGNSS.GPS: 1057, uGNSS.GLO: 1063, uGNSS.GAL: 1240, + uGNSS.QZS: 1246, uGNSS.SBS: 1252, uGNSS.BDS: 1258 + } + + self.eph_t = { + uGNSS.GPS: 1019, uGNSS.GLO: 1020, uGNSS.BDS: 1042, + uGNSS.QZS: 1044, uGNSS.GAL: 1046 + } + def set_sync(self, msg, k): msg[k] = 0xd3 @@ -2734,6 +3371,173 @@ def set_checksum(self, msg, k, maxlen=0): def set_body(self, msg, buff, k, len_): msg[k+3:k+len_] = buff[3:len_] + def round_i(self, v): + return int(v+0.5) + + def is_msmtype(self, msgtype): + """ check if the message type is MSM """ + for sys_ in self.msm_t.keys(): + if msgtype >= self.msm_t[sys_] and msgtype <= self.msm_t[sys_]+6: + return True + return False + + def msmtype(self, msgtype): + """ get system and msm type from message type """ + sys = uGNSS.NONE + msm = 0 + for sys_ in self.msm_t.keys(): + if msgtype >= self.msm_t[sys_] and msgtype <= self.msm_t[sys_]+6: + sys = sys_ + msm = msgtype-self.msm_t[sys_]+1 + break + return sys, msm + + def encode_msm_time(self, sys, time): + """ decode msm time """ + if sys == uGNSS.GLO: + time = timeadd(gpst2utc(time), 10800.0) + week, tow = time2gpst(time) + dow = int(tow/86400.0) # day of week + tod = self.round_i((tow - dow*86400.0)*1e3) # time of day [ms] + epoch = ((dow & 0x1f) << 27) | (tod & 0x7ffffff) + + elif sys == uGNSS.BDS: + week, tow = time2gpst(gpst2bdt(time)) + epoch = self.round_i(tow*1e3) + else: + week, tow = time2gpst(time) + epoch = self.round_i(tow*1e3) + + return week, epoch + + def encode_msm(self, msg, obs, i): + """ encode MSM message """ + sys, msm = self.msmtype(self.msgtype) + + # self.time, self.tow = self.encode_msm_time(sys, self.week, tow_) + self.week, tow_ = self.encode_msm_time(sys, self.time) + + ofst = 193 if sys == uGNSS.QZS else 1 + + svmask = self.encode_mask(obs.sat, 64, ofst) + sigmask = self.encode_mask(obs.sig, 32) + self.nsat = len(obs.sat) + self.nsig = len(obs.sig) + + # augmentation service provider id DFi027 + bs.pack_into('u12u30u1u3', msg, i, self.refid, + tow_, self.mi, self.iods) + i += 53 + + bs.pack_into('u2u2u1u3', msg, i, self.csi, + self.eci, self.si, self.smi) + i += 8 + bs.pack_into('u64u32', msg, i, svmask, sigmask) + i += 96 + + sz = self.nsat*self.nsig + if sz > 64: + return -1 + ncell = 0 + for k in range(self.nsat): + sz_ = len(self.sig_n[k]) + cellmask = self.encode_mask(self.sig_n[k], self.nsig) + bs.pack_into(f'u{self.nsig}', msg, i, cellmask) + i += self.nsig + ncell += sz_ + + r = np.zeros(self.nsat) + pr = np.ones(ncell)*np.nan + cp = np.ones(ncell)*np.nan + lock = np.zeros(ncell, dtype=int) + half = np.zeros(ncell, dtype=int) + cnr = np.zeros(ncell) + rrf = np.zeros(ncell) + + ex = np.zeros(self.nsat, dtype=int) + rr = np.zeros(self.nsat) + + rms = rCST.CLIGHT*1e-3 + + nobs, nsig = obs.P.shape() + + for i in range(nobs): + for j in range(nsig): + k = 0 # TBD + freq = obs.sig.frequency() + r[k] = self.round_i( + obs.P[i, j]/(rms*rCST.P2_10))*rms*rCST.P2_10 + + # satellite part + if msm >= 1 and msm <= 3: + for k in range(self.nsat): + v = int(r[k]/rms) + bs.pack_into('u10', msg, i, v) + i += 10 + else: + for k in range(self.nsat): + v = int(r[k]/rms) + bs.pack_into('u8', msg, i, v) + i += 8 + + if msm == 5 or msm == 7: + for k in range(self.nsat): + bs.pack_into('u4', msg, i, ex[k]) + i += 4 + + for k in range(self.nsat): + if r[k] != 0.0: + v = int(r[k]/(rCST.P2_10*rms)) + bs.pack_into('u10', msg, i, v) + i += 10 + + if msm == 5 or msm == 7: + for k in range(self.nsat): + v = int(rr[k]) + bs.pack_into('s14', msg, i, v) + i += 14 + + # signal part + if msm != 2: + sz = 15 if msm < 6 else 20 + scl = rCST.P2_24 if msm < 6 else rCST.P2_29 + for k in range(ncell): + pr_ = int(pr[k]/(scl*rms)) + bs.pack_into('s'+str(sz), msg, i, pr_) + i += sz + + if msm > 1: + sz = 22 if msm < 6 else 24 + scl = rCST.P2_29 if msm < 6 else rCST.P2_31 + for k in range(ncell): + cp_ = int(cp[k]/(scl*rms)) + bs.pack_into('s'+str(sz), msg, i, cp_) + i += sz + + sz = 4 if msm < 6 else 10 + for k in range(ncell): + bs.pack_into('u'+str(sz), msg, i, lock[k]) + i += sz + + for k in range(ncell): + bs.pack_into('u1', msg, i, half[k]) + i += 1 + + if msm > 3: + sz = 6 if msm < 6 else 10 + scl = 1.0 if msm < 6 else 0.0625 + for k in range(ncell): + v = int(cnr[k]/scl) + bs.pack_into('u'+str(sz), msg, i, v) + i += sz + + if msm == 5 or msm == 7: + for k in range(ncell): + bs.pack_into('s15', msg, i, rrf[k]*1e4) + i += 15 + + return i + def encode_integrity_ssr(self, msg, i): """ RTCM SC-134 SSR integrity message (MT11,12,13) """ @@ -2785,13 +3589,17 @@ def encode_integrity_ssr(self, msg, i): return i - def encode(self, msg): + def encode(self, msg, obs=None): """ encode RTCM messages """ i = 24 bs.pack_into('u12', msg, i, self.msgtype) i += 12 - if self.msgtype in (11, 12, 13): # SSR integrity (test) + if self.is_msmtype(self.msgtype, obs): + self.subtype = sRTCM.MSM + i = self.encode_msm(msg, obs, i) + + elif self.msgtype in (11, 12, 13): # SSR integrity (test) i = self.encode_integrity_ssr(msg, i) return i From 427820fb31b6067f5ae205fb5dcf12a81f1029bb Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sun, 14 Dec 2025 15:34:33 +0900 Subject: [PATCH 10/48] - mi changed to bool --- src/cssrlib/rtcm.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/cssrlib/rtcm.py b/src/cssrlib/rtcm.py index beb4b5e..1f2e797 100644 --- a/src/cssrlib/rtcm.py +++ b/src/cssrlib/rtcm.py @@ -214,7 +214,7 @@ def __init__(self, foutname=None): self.pid = 0 # SSR Provider ID self.sid = 0 # SSR Solution Type self.uint = 0 # Update Interval - self.mi = 0 + self.mi = False self.nrtk_r = {} @@ -544,7 +544,7 @@ def decode_head(self, msg, i, sys): self.tow = bs.unpack_from('u'+str(blen), msg, i)[0] self.time = gpst2time(self.week, self.tow) i += blen - uint, mi = bs.unpack_from('u4u1', msg, i) + uint, mi = bs.unpack_from('u4b1', msg, i) i += 5 if self.subtype in (sCSSR.ORBIT, sCSSR.COMBINED): @@ -2204,7 +2204,7 @@ def decode_ssr_satant(self, sys, msg, i): def decode_ssr_grid(self, msg, i): """ decode SSR Grid Definition Message (E61) """ - self.pid, self.mi, gid, gtype = bs.unpack_from('u16u1u10u3', msg, i) + self.pid, self.mi, gid, gtype = bs.unpack_from('u16b1u10u3', msg, i) i += 30 self.gid = gid @@ -3325,7 +3325,7 @@ def __init__(self): self.pid = 0 # SSR Provider ID self.sid = 0 # SSR Solution Type - self.mi = 0 # multiple message bit + self.mi = False # multiple message bit self.iods = 0 # issue of data station self.refid = 0 # reference station ID @@ -3425,7 +3425,7 @@ def encode_msm(self, msg, obs, i): self.nsig = len(obs.sig) # augmentation service provider id DFi027 - bs.pack_into('u12u30u1u3', msg, i, self.refid, + bs.pack_into('u12u30b1u3', msg, i, self.refid, tow_, self.mi, self.iods) i += 53 From 386158752d67fd38744858b90dff9bcd62f3333d Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sun, 14 Dec 2025 22:40:42 +0900 Subject: [PATCH 11/48] - updated SSR Integrity message output. --- src/cssrlib/rtcm.py | 145 +++++++++++++++++++++++++++----------------- 1 file changed, 91 insertions(+), 54 deletions(-) diff --git a/src/cssrlib/rtcm.py b/src/cssrlib/rtcm.py index 1f2e797..cd39ad4 100644 --- a/src/cssrlib/rtcm.py +++ b/src/cssrlib/rtcm.py @@ -65,46 +65,16 @@ class sRTCM(IntEnum): SSR_PBIAS_EX = 90 +class satAntCorr(): + """ class for satellite antenna corrections """ + + def __init__(self, nadc, naddc): + self.nadc = nadc + self.naddc = naddc + + class Integrity(): """ class for integrity information in SC-134 """ - pid = 0 # augmentation provider id DFi027 (0-4095) - vp = 0 # validity period DFi065 (0-15) - uri = 0 # update rate interval DFi067 (b16) - - # placeholder for RTCM SSR - pidssr = 0 # SSR provider ID - sidssr = 0 # SSR solution type - iodssr = 0 # SSR iod - - tow = 0 - iod_sys = {} # issue of GNSS satellite mask DFi010 (b2) - sts = {} # constellation integrity status DFi029 (b16) - mst = {} # constellation monitoring status DFi030 (b16) - src = {} - - nid = {} - flag = {} - sigmask = {} # GNSS signal mask DFi033 (b32) - iod_sig = {} # issue of GNSS signal mask DFi012 (b2) - sig_ists = {} # signal integrity status DFi031 (b32) - sig_msts = {} # signal monitoring status DFi032 (b32) - - Paug = 0 # Augmentation System Probability Falut DFi049 - - Pa_sp = {} # Single Satellite PR Message Fault Probability DFi046 - sig_a = {} # Overbounding stdev of PR Augmentation Meesage DFi036 - Pa_sc = {} # Single Satellite CP Message Fault Probability DFi048 - ob_p = {} # Overbounding bias of long-ter PR bias DFi037 - sig_ob_c = {} # Ovebounding stdev of CP DFi038 - ob_c = {} # Overbounding bias of long-term CP bias DFi039 - - nsys = 0 - nsat = {} - sys_t = None - sat = {} - mask_sys = 0 - - mm_param = None sys_tbl = {0: uGNSS.GPS, 1: uGNSS.GLO, 2: uGNSS.GAL, 3: uGNSS.BDS, 4: uGNSS.QZS, 5: uGNSS.IRN} @@ -184,6 +154,45 @@ def __init__(self): self.sys_r_tbl = {self.sys_tbl[s]: s for s in self.sys_tbl.keys()} self.vp_r_tbl = {s: k for k, s in enumerate(self.vp_tbl)} + self.pid = 0 # augmentation provider id DFi027 (0-4095) + self.vp = 0 # validity period DFi065 (0-15) + self.uri = 0 # update rate interval DFi067 (b16) + + # placeholder for RTCM SSR + self.pidssr = 0 # SSR provider ID + self.sidssr = 0 # SSR solution type + self.iodssr = 0 # SSR iod + + self.tow = 0 + self.iod_sys = {} # issue of GNSS satellite mask DFi010 (b2) + self.sts = {} # constellation integrity status DFi029 (b16) + self.mst = {} # constellation monitoring status DFi030 (b16) + self.src = {} + + self.nid = {} + self.flag = {} + self.sigmask = {} # GNSS signal mask DFi033 (b32) + self.iod_sig = {} # issue of GNSS signal mask DFi012 (b2) + self.sig_ists = {} # signal integrity status DFi031 (b32) + self.sig_msts = {} # signal monitoring status DFi032 (b32) + + self.Paug = 0 # Augmentation System Probability Falut DFi049 + + self.Pa_sp = {} # Single Satellite PR Message Fault Probability DFi046 + self.sig_a = {} # Overbounding stdev of PR Augmentation Meesage DFi036 + self.Pa_sc = {} # Single Satellite CP Message Fault Probability DFi048 + self.ob_p = {} # Overbounding bias of long-ter PR bias DFi037 + self.sig_ob_c = {} # Ovebounding stdev of CP DFi038 + self.ob_c = {} # Overbounding bias of long-term CP bias DFi039 + + self.nsys = 0 + self.nsat = {} + self.sys_t = None + self.sat = {} + self.mask_sys = 0 + + self.mm_param = None + class TropModel(): """ Tropospheric delay correction model for RTCM SSR """ @@ -233,6 +242,7 @@ def __init__(self, foutname=None): uGNSS.QZS: 1044, uGNSS.GAL: 1046 } + self.antc = {} self.integ = Integrity() self.test_mode = False # for interop testing in SC134 @@ -687,8 +697,11 @@ def get_ssr_sys(self, msgtype): 99: uGNSS.BDS, 100: uGNSS.QZS} elif msgtype in [60, 61, 95]: # METADATA, grid definition, trop return uGNSS.NONE + elif msgtype in [11]: # RTCM SC-134 test messages + return uGNSS.NONE else: print(f"definition of {msgtype} is missing") + # return uGNSS.NONE return tbl_t[msgtype] @@ -1413,8 +1426,7 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write("\n") for sys in self.integ.flag.keys(): - self.fh.write(" Network ID:{:3d} Integrity Flag: ".format( - self.integ.nid[sys])) + self.fh.write(" NIntegrity Flag: ") for sat in self.integ.flag[sys]: self.fh.write(" {:3s}:{:2d}".format( sat2id(sat), self.integ.flag[sys][sat])) @@ -2176,30 +2188,55 @@ def decode_ssr_metadata(self, msg, i): return i - def decode_ssr_satant(self, sys, msg, i): + def decode_ssr_satant(self, msg, i): """ decode GNSS Satellite Antenna Message (E80,81,82,83,84) """ + sys = self.get_ssr_sys(self.msgtype) + + # Provider ID + # Satellite Antenna IOD (DF+10) + # Phase Center Information Infdicator (DF+11) + # Group Delay Informatyion Indicator (DF+12) + # Nadir Angle Dependent Corrections Indicator (DF+13) pid, iods, pci, gdi, ndi = bs.unpack_from('u16u6b1b1b1', msg, i) i += 25 if ndi: - nmas, nde = bs.unpack_from('u5u4', msg, i) + # Maximum off-nadir angle (DF+14) + # Nadir angle dependent corrections range extension (DF+15) + emax, nde = bs.unpack_from('u5u4', msg, i) i += 9 - ssi, satmask = bs.unpack_from('u1u64', msg, i) + # Satellite Set Indicator (DF+16): set if True, else for single + ssi, satmask = bs.unpack_from('b1u64', msg, i) i += 65 - prn, nsat = self.decode_mask(satmask, 64, 1) - for k in range(nsat): - fsi, freqmask = bs.unpack_from('u1u6', msg, i) + svids, nsat = self.decode_mask(satmask, 64, 1) + for svid in svids: + sat = self.svid2sat(sys, svid) + if sat not in self.antc: + self.antc[sat] = {} + # Frequency Set Indicator (DF+17) Set if True else Single + # GNSS Frequency Mask (DF+18) + fsi, freqmask = bs.unpack_from('b1u6', msg, i) i += 7 - freq, nf = self.decode_mask(freqmask, 6, 0) - for j in range(nf): + freq_, nf = self.decode_mask(freqmask, 6, 0) + for freq in freq_: # Frequency part + # Nadir correction indicator (DF+19) nci = bs.unpack_from('b1', msg, i)[0] i += 1 - if nci: - nc = bs.unpack_from('s12', msg, i)[0] + if nci: # nadir corrections + nadc_ = bs.unpack_from('s12', msg, i)[0] # DF+20 i += 12 - if ndi: - ndc = bs.unpack_from('s'+str(3+nde), msg, i)[0] - i += 3+nde + nadc = self.sval(nadc_, 12, 1e-3) # [m] + else: + nadc = None + if ndi: # nadir angle dependent corrections (PCVs) (DF+21) + naddc = np.zeros(nde+1) + for k in range(nde+1): + naddc_ = bs.unpack_from('s'+str(3+nde), msg, i)[0] + i += 3+nde + naddc[k] = self.sval(naddc_, 3+nde, 1e-3) # [m] + else: + naddc = None + self.antc[sat][freq] = satAntCorr(nadc, naddc) return i def decode_ssr_grid(self, msg, i): @@ -3595,7 +3632,7 @@ def encode(self, msg, obs=None): bs.pack_into('u12', msg, i, self.msgtype) i += 12 - if self.is_msmtype(self.msgtype, obs): + if self.is_msmtype(self.msgtype): self.subtype = sRTCM.MSM i = self.encode_msm(msg, obs, i) From d2b53c112384095a9361e2546cd10fb3d88784b5 Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sun, 14 Dec 2025 22:42:28 +0900 Subject: [PATCH 12/48] - added MT12,13 --- src/cssrlib/rtcm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cssrlib/rtcm.py b/src/cssrlib/rtcm.py index cd39ad4..29d793c 100644 --- a/src/cssrlib/rtcm.py +++ b/src/cssrlib/rtcm.py @@ -697,7 +697,7 @@ def get_ssr_sys(self, msgtype): 99: uGNSS.BDS, 100: uGNSS.QZS} elif msgtype in [60, 61, 95]: # METADATA, grid definition, trop return uGNSS.NONE - elif msgtype in [11]: # RTCM SC-134 test messages + elif msgtype in [11, 12, 13]: # RTCM SC-134 test messages return uGNSS.NONE else: print(f"definition of {msgtype} is missing") From ceb85774eee9d481c823a3a7fe80946e90fab88b Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sat, 20 Dec 2025 22:14:24 +0900 Subject: [PATCH 13/48] - fixed reference error. --- src/cssrlib/rtcm.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/cssrlib/rtcm.py b/src/cssrlib/rtcm.py index 29d793c..6faa4ac 100644 --- a/src/cssrlib/rtcm.py +++ b/src/cssrlib/rtcm.py @@ -246,6 +246,15 @@ def __init__(self, foutname=None): self.integ = Integrity() self.test_mode = False # for interop testing in SC134 + def is_ssrtype(self, msgtype, tstmsg=False): + """ check if the message type is MSM """ + for sys_ in self.ssr_t.keys(): + if msgtype >= self.ssr_t[sys_] and msgtype <= self.ssr_t[sys_]+6: + return True + if tstmsg and msgtype >= 60 and msgtype <= 100: # SSR test message + return True + return False + def is_msmtype(self, msgtype): """ check if the message type is MSM """ for sys_ in self.msm_t.keys(): @@ -1201,7 +1210,8 @@ def out_log_ssr_orb(self, sys, flg_vel=True): def out_log(self, obs=None, eph=None, geph=None, seph=None): """ output ssr message to log file """ - sys = self.get_ssr_sys(self.msgtype) + if self.is_ssrtype(self.msgtype, True): + sys = self.get_ssr_sys(self.msgtype) inet = self.inet self.fh.write("{:4d}\t{:s}\n".format(self.msgtype, time2str(self.time))) From cccfd1ce9502c0bb0b24780a78f5c89ed941b12c Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sat, 3 Jan 2026 21:25:23 +0900 Subject: [PATCH 14/48] - removed gn. --- src/cssrlib/ppp.py | 112 +++++++++++++++++++++------------------------ 1 file changed, 51 insertions(+), 61 deletions(-) diff --git a/src/cssrlib/ppp.py b/src/cssrlib/ppp.py index 1b3ae34..90e993b 100644 --- a/src/cssrlib/ppp.py +++ b/src/cssrlib/ppp.py @@ -1,9 +1,9 @@ """ module for PPP processing """ -import cssrlib.gnss as gn -from cssrlib.peph import gpst2utc, time2epoch -from enum import IntEnum +from cssrlib.gnss import rCST, epoch2time, time2epoch, timeadd +from cssrlib.gnss import timediff, vnorm, ecef2pos, xyz2enu, enu2xyz, gpst2utc +from cssrlib.gnss import ecef2enu, Nav, prn2sat, pos2ecef, uGNSS from math import sin, cos, atan2, asin import numpy as np try: @@ -129,8 +129,8 @@ def nut_iau1980(t_, f): dpsi += (nut[i, 6]+nut[i, 7]*t_)*sin(ang) deps += (nut[i, 8]+nut[i, 9]*t_)*cos(ang) - dpsi *= gn.rCST.AS2R*1e-4 - deps *= gn.rCST.AS2R*1e-4 + dpsi *= rCST.AS2R*1e-4 + deps *= rCST.AS2R*1e-4 return dpsi, deps @@ -153,7 +153,7 @@ def ast_args(t_): tt[k_+1] = tt[k_]*t_ for k_ in range(5): f[k_] = fc[k_, 0]*3600.0+fc[k_, 1:]@tt - f[k_] = np.fmod(f[k_]*gn.rCST.AS2R, 2.0*np.pi) + f[k_] = np.fmod(f[k_]*rCST.AS2R, 2.0*np.pi) return f @@ -180,33 +180,33 @@ def Rz(t_): def utc2gmst(t_, ut1_utc=0): """ UTC to GMST """ - ep0 = gn.epoch2time([2000, 1, 1, 12, 0, 0]) - tut = gn.timeadd(t_, ut1_utc) - ep = gn.time2epoch(tut) - tut0 = gn.epoch2time([ep[0], ep[1], ep[2], 0, 0, 0]) + ep0 = epoch2time([2000, 1, 1, 12, 0, 0]) + tut = timeadd(t_, ut1_utc) + ep = time2epoch(tut) + tut0 = epoch2time([ep[0], ep[1], ep[2], 0, 0, 0]) ut = ep[3]*3600+ep[4]*60+ep[5] - t1 = gn.timediff(tut0, ep0)/gn.rCST.CENTURY_SEC + t1 = timediff(tut0, ep0)/rCST.CENTURY_SEC t2 = t1**2 t3 = t2*t1 gmst0 = 24110.54841+8640184.812866*t1+0.093104*t2-6.2e-6*t3 gmst = gmst0+1.002737909350795*ut - return np.fmod(gmst, gn.rCST.DAY_SEC)*(2.0*np.pi/gn.rCST.DAY_SEC) + return np.fmod(gmst, rCST.DAY_SEC)*(2.0*np.pi/rCST.DAY_SEC) def eci2ecef(tgps, erpv): """ ECI to ECEF conversion matrix """ - tutc = gn.gpst2utc(tgps) - ep0 = gn.epoch2time([2000, 1, 1, 12, 0, 0]) - dt = gn.timediff(tgps, ep0) - t_ = (dt+19+32.184)/gn.rCST.CENTURY_SEC + tutc = gpst2utc(tgps) + ep0 = epoch2time([2000, 1, 1, 12, 0, 0]) + dt = timediff(tgps, ep0) + t_ = (dt+19+32.184)/rCST.CENTURY_SEC t2 = t_**2 t3 = t2*t_ f = ast_args(t_) # iau1976 precession - ze = (2306.2181*t_+0.30188*t2+0.017998*t3)*gn.rCST.AS2R - th = (2004.3109*t_-0.42665*t2-0.041833*t3)*gn.rCST.AS2R - z = (2306.2181*t_+1.09468*t2+0.018203*t3)*gn.rCST.AS2R - eps = (84381.448-46.8150*t_-0.00059*t2+0.001813*t3)*gn.rCST.AS2R + ze = (2306.2181*t_+0.30188*t2+0.017998*t3)*rCST.AS2R + th = (2004.3109*t_-0.42665*t2-0.041833*t3)*rCST.AS2R + z = (2306.2181*t_+1.09468*t2+0.018203*t3)*rCST.AS2R + eps = (84381.448-46.8150*t_-0.00059*t2+0.001813*t3)*rCST.AS2R P = Rz(-z)@Ry(th)@Rz(-ze) # iau1980 nutation @@ -216,7 +216,7 @@ def eci2ecef(tgps, erpv): # Greenwich apparent sidereal time [rad] gmst = utc2gmst(tutc, erpv[2]) gast = gmst+dpsi*cos(eps) - gast += (0.00264*sin(f[4])+0.000063*sin(2.0*f[4]))*gn.rCST.AS2R + gast += (0.00264*sin(f[4])+0.000063*sin(2.0*f[4]))*rCST.AS2R W = Ry(-erpv[0])@Rx(-erpv[1]) U = W@Rz(gast)@N@P @@ -226,9 +226,9 @@ def eci2ecef(tgps, erpv): def sunmoonpos(tutc, erpv=np.zeros(5)): """ calculate sun/moon position in ECEF """ - tut = gn.timeadd(tutc, erpv[2]) - ep0 = gn.epoch2time([2000, 1, 1, 12, 0, 0]) - t_ = gn.timediff(tut, ep0)/gn.rCST.CENTURY_SEC + tut = timeadd(tutc, erpv[2]) + ep0 = epoch2time([2000, 1, 1, 12, 0, 0]) + t_ = timediff(tut, ep0)/rCST.CENTURY_SEC f = ast_args(t_) eps = np.deg2rad(23.439291-0.0130042*t_) # Mean Obliquity of the ecliptic c_e = cos(eps) @@ -239,7 +239,7 @@ def sunmoonpos(tutc, erpv=np.zeros(5)): ls = np.deg2rad(280.460+36000.770*t_+1.914666471*sin(Ms) + 0.019994643*sin(2.0*Ms)) # Distance of the Sun from the Earth - rs = gn.rCST.AU*(1.000140612-0.016708617*cos(Ms)-0.000139589*cos(2.0*Ms)) + rs = rCST.AU*(1.000140612-0.016708617*cos(Ms)-0.000139589*cos(2.0*Ms)) c_l = cos(ls) s_l = sin(ls) rsun_eci = np.array([rs*c_l, rs*c_e*s_l, rs*s_e*s_l]) @@ -250,7 +250,7 @@ def sunmoonpos(tutc, erpv=np.zeros(5)): 0.17*sin(f[2]-2.0*f[3]) u = (0.9508+0.0518*cos(f[0])+0.0095*cos(f[0]-2.0*f[3]) + 0.0078*cos(2.0*f[3])+0.0028*cos(2.0*f[0])) - rm = gn.rCST.RE_WGS84/sin(np.deg2rad(u)) + rm = rCST.RE_WGS84/sin(np.deg2rad(u)) c_l = cos(np.deg2rad(lm)) s_l = sin(np.deg2rad(lm)) c_p = cos(np.deg2rad(pm)) @@ -270,13 +270,13 @@ def shapiro(rsat, rrcv): rs = np.linalg.norm(rsat) rr = np.linalg.norm(rrcv) rrs = np.linalg.norm(rsat-rrcv) - corr = (2*gn.rCST.GME/gn.rCST.CLIGHT**2)*np.log((rs+rr+rrs)/(rs+rr-rrs)) + corr = (2*rCST.GME/rCST.CLIGHT**2)*np.log((rs+rr+rrs)/(rs+rr-rrs)) return corr def windupcorr(time, rs, vs, rr, phw, full=False): """ calculate windup correction """ - ek = gn.vnorm(rr-rs) + ek = vnorm(rr-rs) if full: # Satellite antenna frame unit vectors assuming standard yaw attitude law # @@ -289,14 +289,14 @@ def windupcorr(time, rs, vs, rr, phw, full=False): eys = r/np.linalg.norm(r) exs = np.cross(eys, ezs) else: - we = np.array([0, 0, gn.rCST.OMGE]) - ek = gn.vnorm(rr-rs) - ezs = gn.vnorm(-rs) - ess = gn.vnorm(vs+np.cross(we, rs)) - eys = gn.vnorm(np.cross(ezs, ess)) + we = np.array([0, 0, rCST.OMGE]) + ek = vnorm(rr-rs) + ezs = vnorm(-rs) + ess = vnorm(vs+np.cross(we, rs)) + eys = vnorm(np.cross(ezs, ess)) exs = np.cross(eys, ezs) - pos = gn.ecef2pos(rr) - E = gn.xyz2enu(pos) + pos = ecef2pos(rr) + E = xyz2enu(pos) exr = E[0, :] eyr = E[1, :] eks = np.cross(ek, eys) @@ -313,24 +313,14 @@ def windupcorr(time, rs, vs, rr, phw, full=False): return phw -class uTideModel(IntEnum): - """ - Enumeration for Earth tide model selection - """ - - NONE = -1 - SIMPLE = 0 - IERS2010 = 1 - - def tide_pl(eu, rp, GMp, pos): """ pole tide correction """ H3 = 0.293 L3 = 0.0156 r = np.linalg.norm(rp) ep = rp/r - K2 = GMp/gn.rCST.GME*gn.rCST.RE_WGS84**4/r**3 - K3 = K2*gn.rCST.RE_WGS84/r + K2 = GMp/rCST.GME*rCST.RE_WGS84**4/r**3 + K3 = K2*rCST.RE_WGS84/r latp = asin(ep[2]) lonp = atan2(ep[1], ep[0]) c_p = cos(latp) @@ -359,8 +349,8 @@ def solid_tide(rsun, rmoon, pos, E, gmst, flag=True): """ solid earth tide correction """ # time domain eu = E[2, :] - dr1 = tide_pl(eu, rsun, gn.rCST.GMS, pos) - dr2 = tide_pl(eu, rmoon, gn.rCST.GMM, pos) + dr1 = tide_pl(eu, rsun, rCST.GMS, pos) + dr2 = tide_pl(eu, rmoon, rCST.GMM, pos) # frequency domain s_2l = sin(2.0*pos[0]) du = -0.012*s_2l*sin(gmst+pos[1]) @@ -382,7 +372,7 @@ def tidedisp(tutc, pos, erpv=None): if erpv is None: erpv = np.zeros(5) rs, rm, gmst = sunmoonpos(tutc, erpv) - E = gn.xyz2enu(pos) + E = xyz2enu(pos) dr = solid_tide(rs, rm, pos, E, gmst) return dr @@ -400,7 +390,7 @@ def tidedispIERS2010(tutc, pos, erpv=None): int(e[5]), np.rad2deg(pos[0]), 0, 1, np.rad2deg(pos[1]), 0, 1) - E = gn.enu2xyz(pos) + E = enu2xyz(pos) return E@np.array([disp_e[0, 0], disp_n[0, 0], disp_u[0, 0]]) @@ -409,7 +399,7 @@ def tidedispIERS2010(tutc, pos, erpv=None): from cssrlib.ephemeris import findeph, eph2pos from cssrlib.rinex import rnxdec - tgps_ = gn.epoch2time([2021, 3, 19, 0, 0, 0]) + tgps_ = epoch2time([2021, 3, 19, 0, 0, 0]) pos_ = np.array([0.61678759, 2.43512138, 64.94054687]) erpv_ = np.array([2.1079217879069683e-06, 4.8733853217911866e-07, -0.044509672541668682, -0.0007141, 0]) @@ -422,10 +412,10 @@ def tidedispIERS2010(tutc, pos, erpv=None): t = np.zeros(n) dr_ = np.zeros((n, 3)) for k in range(n): - tn = gn.timeadd(tgps_, k*300) - t[k] = gn.timediff(tn, tgps_) - dn_ = tidedisp(gn.gpst2utc(tn), pos_, erpv_) - dr_[k, :] = gn.ecef2enu(pos_, dn_) + tn = timeadd(tgps_, k*300) + t[k] = timediff(tn, tgps_) + dn_ = tidedisp(gpst2utc(tn), pos_, erpv_) + dr_[k, :] = ecef2enu(pos_, dn_) plt.figure() plt.plot(t/3600, dr_) @@ -438,11 +428,11 @@ def tidedispIERS2010(tutc, pos, erpv=None): if flg_pwup: bdir = '../data/' navfile = bdir+'30340780.21q' - nav = gn.Nav() + nav = Nav() dec = rnxdec() nav = dec.decode_nav(navfile, nav) - rr_ = gn.pos2ecef(pos_) - sat = gn.prn2sat(gn.uGNSS.QZS, 194) + rr_ = pos2ecef(pos_) + sat = prn2sat(uGNSS.QZS, 194) n = 86400//300 t = np.zeros(n) @@ -450,11 +440,11 @@ def tidedispIERS2010(tutc, pos, erpv=None): d = np.zeros(n) phw_ = 0 for k in range(n): - tn = gn.timeadd(tgps_, k*300) + tn = timeadd(tgps_, k*300) eph = findeph(nav.eph, tn, sat) rs_, vs_, dts = eph2pos(tn, eph, True) phw_ = windupcorr(tn, rs_, vs_, rr_, phw_) - t[k] = gn.timediff(tn, tgps_) + t[k] = timediff(tn, tgps_) ph_[k] = phw_ d[k] = shapiro(rs_, rr_) From b9ebd2f5037965738f0dd8a21c8589b761c6fd31 Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sat, 3 Jan 2026 21:25:56 +0900 Subject: [PATCH 15/48] - moved tideCorr to gnss.py --- src/cssrlib/pppssr.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/cssrlib/pppssr.py b/src/cssrlib/pppssr.py index 8457bae..36bc26c 100644 --- a/src/cssrlib/pppssr.py +++ b/src/cssrlib/pppssr.py @@ -6,11 +6,10 @@ from cssrlib.ephemeris import satposs from cssrlib.gnss import sat2id, sat2prn, rSigRnx, uTYP, uGNSS, rCST -from cssrlib.gnss import ecef2pos, tropmodel, geodist, satazel +from cssrlib.gnss import ecef2pos, tropmodel, geodist, satazel, uTideModel from cssrlib.gnss import time2str, timediff, gpst2utc, tropmapf from cssrlib.gnss import trop_model_tbl, iono_model_tbl, default_config -from cssrlib.ppp import tidedisp, tidedispIERS2010, uTideModel -from cssrlib.ppp import shapiro, windupcorr +from cssrlib.ppp import tidedisp, tidedispIERS2010, shapiro, windupcorr from cssrlib.peph import antModelRx, antModelTx from cssrlib.cssrlib import sCType from cssrlib.cssrlib import sCSSRTYPE as sc From 40d23e2fe6d580accbf442e6b087b08b9a89ead9 Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sat, 3 Jan 2026 21:26:42 +0900 Subject: [PATCH 16/48] - moved uTideModel. - fixed initialization. --- src/cssrlib/gnss.py | 47 +++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/src/cssrlib/gnss.py b/src/cssrlib/gnss.py index 4667fed..157940f 100644 --- a/src/cssrlib/gnss.py +++ b/src/cssrlib/gnss.py @@ -10,6 +10,7 @@ import bitstruct as bs import yaml + gpst0 = [1980, 1, 6, 0, 0, 0] # GPS system time reference gst0 = [1999, 8, 22, 0, 0, 0] # Galileo system time reference bdt0 = [2006, 1, 1, 0, 0, 0] # BeiDou system time reference @@ -320,10 +321,10 @@ class uTropoModel(IntEnum): Enumeration for tropo model selection """ - NONE = -1 - SAAST = 0 - HOPF = 1 - SBAS = 2 + NONE = 0 + SAAST = 1 + HOPF = 2 + SBAS = 3 class uIonoModel(IntEnum): @@ -331,11 +332,21 @@ class uIonoModel(IntEnum): Enumeration for iono model selection """ - NONE = -1 - KLOBUCHAR = 0 - NEQUICK_G = 1 - GIM = 2 - SBAS = 3 + NONE = 0 + KLOBUCHAR = 1 + NEQUICK_G = 2 + GIM = 3 + SBAS = 4 + + +class uTideModel(IntEnum): + """ + Enumeration for Earth tide model selection + """ + + NONE = 0 + SIMPLE = 1 + IERS2010 = 2 class rSigRnx(): @@ -741,9 +752,9 @@ def __init__(self, sat=0): self.sattype = 0 self.sismai = 0 self.code = 0 - self.urai = None - self.sisai = None - self.isc = None + self.urai = np.zeros(4, dtype=int) + self.sisai = np.zeros(5, dtype=int) + self.isc = np.zeros(4) self.integ = 0 # 0:LNAV,INAV,D1/D2, 1:CNAV/CNAV1/FNAV, 2: CNAV2, # 3: CNAV3, 4:FDMA, 5:SBAS @@ -843,6 +854,8 @@ class Nav(): """ class to define the navigation message """ def __init__(self, nf=2): + self.x = None # states + self.eph = [] self.geph = [] self.seph = [] @@ -861,7 +874,7 @@ def __init__(self, nf=2): self.eop = np.zeros(9) self.elmin = np.deg2rad(15.0) - self.tidecorr = False + self.tidecorr = uTideModel.NONE self.nf = nf self.ne = 0 self.nc = 0 @@ -1505,8 +1518,8 @@ def tropmapf(t, pos, el, model=uTropoModel.SAAST): return tropmapfHpf(el) elif model == uTropoModel.SBAS: # SBAS model return tropmapfSBAS(el) - else: - return 0 + else: # model == uTropoModel.NONE + return 0, 0 def tropmodel(t, pos, el=np.pi/2, humi=0.7, model=uTropoModel.SAAST): @@ -1520,8 +1533,8 @@ def tropmodel(t, pos, el=np.pi/2, humi=0.7, model=uTropoModel.SAAST): return tropmodelHpf() elif model == uTropoModel.SBAS: # SBAS model return tropmodelSBAS(t, pos, el) - else: - return 0 + else: # model == uTropoModel.NONE + return 0, 0, 0 def meteo(hgt, humi): From 73f9d7154be79f48220929b2781fc4532dbc0c39 Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sat, 3 Jan 2026 21:27:14 +0900 Subject: [PATCH 17/48] - added scan mode. --- src/cssrlib/rtcm.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/cssrlib/rtcm.py b/src/cssrlib/rtcm.py index 6faa4ac..14d4a22 100644 --- a/src/cssrlib/rtcm.py +++ b/src/cssrlib/rtcm.py @@ -242,6 +242,15 @@ def __init__(self, foutname=None): uGNSS.QZS: 1044, uGNSS.GAL: 1046 } + self.glo_bias = None # GLONASS receiver bias + self.pos_arp = None # receiver position + self.ant_desc = "unknown" + self.ant_id = "" + self.ant_serial = "" + self.rcv_type = "" + self.firm_ver = "unknown" + self.rcv_serial = "" + self.antc = {} self.integ = Integrity() self.test_mode = False # for interop testing in SC134 @@ -3192,7 +3201,7 @@ def decode_integrity_ssr(self, msg, i): self.integ.iod_sys = iod_sys self.integ.flag = flag_t - def decode(self, msg, subtype=None): + def decode(self, msg, subtype=None, scanmode=False): """ decode RTCM messages """ i = 24 self.msgtype = bs.unpack_from('u12', msg, i)[0] @@ -3206,6 +3215,18 @@ def decode(self, msg, subtype=None): geph = None seph = None + if scanmode: + if self.msgtype in (1007, 1008, 1033): + self.subtype = sRTCM.ANT_DESC + i = self.decode_ant_desc(msg, i) + elif self.msgtype in (1005, 1006, 1032): + self.subtype = sRTCM.ANT_POS + i = self.decode_sta_pos(msg, i) + elif self.msgtype == 1230: + self.subtype = sRTCM.GLO_BIAS + i = self.decode_glo_bias(msg, i) + return i, obs, eph, geph, seph + self.subtype = subtype # SSR messages From 23003cde543dc6b1095abd92f0569d1bdc4f18be Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sat, 3 Jan 2026 21:31:52 +0900 Subject: [PATCH 18/48] - improved GLO LxOC support in RINEX decoder --- src/cssrlib/rinex.py | 46 ++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/src/cssrlib/rinex.py b/src/cssrlib/rinex.py index 0e1f72f..332823b 100644 --- a/src/cssrlib/rinex.py +++ b/src/cssrlib/rinex.py @@ -176,7 +176,6 @@ def _decode_nav(self, fnav, nav, append=False): nav.geph = [] nav.seph = [] - for line in fnav: if line[60:73] == 'END OF HEADER': break @@ -447,36 +446,36 @@ def _decode_nav(self, fnav, nav, append=False): # b7-8: M, b6: P4, b5: P3, b4: P2, b2-3: P1, b0-1: P geph.status = int(self.flt(line, 0)) geph.dtaun = -self.flt(line, 1) - geph.urai = int(self.flt(line, 2)) + geph.urai[0] = int(self.flt(line, 2)) if len(line) >= 80: geph.svh = int(self.flt(line, 3)) else: # L1OC,L3OC sattype = int(self.flt(line, 0)) src = int(self.flt(line, 1)) - aode_ee = int(self.flt(line, 2)) - aode_et = int(self.flt(line, 3)) + geph.aode = int(self.flt(line, 2)) + geph.aodc = int(self.flt(line, 3)) line = fnav.readline() # line #5 P2 = int(self.flt(line, 0)) # attitude flag - t0 = self.flt(line, 1) # sec of day, UTC(SU) - tau1 = self.flt(line, 2) - tau2 = self.flt(line, 3) + geph.tin = self.flt(line, 1) # sec of day, UTC(SU) + geph.tau1 = self.flt(line, 2) + geph.tau2 = self.flt(line, 3) line = fnav.readline() # line #6 - yaw = self.flt(line, 0) - sgn = int(self.flt(line, 1)) - win = self.flt(line, 2) - dw = self.flt(line, 3) + geph.yaw = self.flt(line, 0) + geph.sn = int(self.flt(line, 1)) + geph.win = self.flt(line, 2) + geph.dw = self.flt(line, 3) line = fnav.readline() # line #7 - wmax = self.flt(line, 0) - dxpc = self.flt(line, 1) - dypc = self.flt(line, 2) - dzpc = self.flt(line, 3) + geph.wmax = self.flt(line, 0) + geph.dpoc[0] = self.flt(line, 1) + geph.dpoc[1] = self.flt(line, 2) + geph.dpoc[2] = self.flt(line, 3) line = fnav.readline() # line #8 - urai_orb = int(self.flt(line, 0)) - urai_clk = int(self.flt(line, 1)) + geph.urai[0] = int(self.flt(line, 0)) + geph.urai[1] = int(self.flt(line, 1)) tot = self.flt(line, 2) tod = t0 % 86400.0 @@ -532,7 +531,7 @@ def _decode_nav(self, fnav, nav, append=False): continue elif sys not in (uGNSS.GPS, uGNSS.GAL, uGNSS.QZS, uGNSS.BDS, - uGNSS.IRN): + uGNSS.IRN): continue prn = int(line[1:3]) @@ -803,7 +802,8 @@ def decode_obsh(self, obsfile: str) -> int: obsfile: Path = Path(obsfile) if obsfile.suffix.lower() in ['.gz', '.z']: import gzip - self.fobs = gzip.open(obsfile, 'rt', encoding='utf-8', errors='ignore') + self.fobs = gzip.open( + obsfile, 'rt', encoding='utf-8', errors='ignore') else: self.fobs = open(obsfile, 'rt') return self._decode_obsh() @@ -1020,6 +1020,7 @@ def __init__(self, sig_tab=None): self.anttype = "Unknown" self.pos = np.zeros(3) self.dant = np.zeros(3) + self.glo_bias = None self.rec_eph = {} @@ -1070,7 +1071,7 @@ def rnx_obs_header(self, ts: gtime_t, fh=None, ver=4.02): fh.write("{:20s}{:40s}{:20s}\n".format( self.observer, self.agency, "OBSERVER / AGENCY")) fh.write("{:20s}{:20s}{:20s}{:20s}\n".format( - self.rec, self.rectype, self.recver, "REC # / TYPE / VERS")) + self.rec, self.rectype, self.recver[:20], "REC # / TYPE / VERS")) fh.write("{:20s}{:20s}{:20s}{:20s}\n".format( self.ant, self.anttype, "", "ANT # / TYPE")) fh.write("{:14.4f}{:14.4f}{:14.4f}{:18s}{:20s}\n".format( @@ -1092,6 +1093,9 @@ def rnx_obs_header(self, ts: gtime_t, fh=None, ver=4.02): cn = self.sig_tab[sys][uTYP.S] nsig += len(cn) + if nsig == 0: + continue + fh.write("{:1s} {:3d}".format(sys_t[sys], nsig)) n = 0 @@ -1159,8 +1163,8 @@ def rnx_obs_body(self, obs=None, fh=None): fh.write(" {:1d}{:3d}\n".format(0, nsat)) for k in range(nsat): - fh.write("{:3s}".format(sat2id(obs.sat[k]))) sys, prn = sat2prn(obs.sat[k]) + fh.write("{:3s}".format(sat2id(obs.sat[k]))) for i in range(nsig): ssi = min(max(int(obs.S[k][i]/6), 1), 9) lli = obs.lli[k][i] From 8e214931da3fe17e58a9c5ab85cbab28b2c38e90 Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sat, 3 Jan 2026 21:36:58 +0900 Subject: [PATCH 19/48] - added QZS L1C/B mode. - fixed GLO LxOC decoder. --- src/cssrlib/rawnav.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/cssrlib/rawnav.py b/src/cssrlib/rawnav.py index 039a4cd..b6ea70d 100644 --- a/src/cssrlib/rawnav.py +++ b/src/cssrlib/rawnav.py @@ -1842,7 +1842,7 @@ def decode_glo_cdma(self, week, time, sat, buff, stype): geph.tau1 = tau1*rCST.P2_5 geph.tau2 = tau2*rCST.P2_5 - # KP: expected UTC(SU) correection + # KP: expected UTC(SU) correction # A: L1OCd time correction in next string 0: no plan geph.sattype = M # 0:M(L3),1:K1(L3),3:K1(L2/L3),2:K2(L1/L2/L3) @@ -1856,7 +1856,8 @@ def decode_glo_cdma(self, week, time, sat, buff, stype): geph.toe = gep2time(N4, Nt, tb*90.0) _, geph.toes = time2gpst(geph.toe) - geph.tof = gep2time(N4, Nt, ts*3.0) + scl = 2.0 if stype == 1 else 3.0 + geph.tof = gep2time(N4, Nt, ts*scl) geph.iode = tb geph.mode = stype # FDMA:0,L1OC:1,L2OC:2,L3OC:3 @@ -1931,6 +1932,7 @@ class rcvOpt(): flg_sbas = False flg_rnxnav = False flg_rnxobs = False + useL1CB = False class rcvDec(): @@ -1975,6 +1977,10 @@ class rcvDec(): fh_rnxnav = None fh_rnxobs = None + obs = None + + useL1CB = False + mode_galinav = 0 # 0: RawNav, 1: Decoded rn = None # placeholder for Raw Navigation message decoder @@ -2159,6 +2165,8 @@ def init_param(self, opt: rcvOpt, prefix=''): self.file_rnxobs = "rnx.obs" self.fh_rnxobs = open(prefix+self.file_rnxobs, mode='w') # self.re.rnx_obs_header(self.fh_rnxobs) + if opt.useL1CB: # use QZSS L1CB in place of L1CA + self.useL1CB = True def file_close(self): """ close file handlers """ From ede5a677eea0a468e0a9ac09d0798f08e001e2c9 Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sat, 3 Jan 2026 21:39:09 +0900 Subject: [PATCH 20/48] - changed the initialization of P. --- src/cssrlib/pntpos.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/cssrlib/pntpos.py b/src/cssrlib/pntpos.py index e537d23..0d8b1dc 100644 --- a/src/cssrlib/pntpos.py +++ b/src/cssrlib/pntpos.py @@ -49,6 +49,8 @@ def ionmodel(t, pos, az, el, nav=None, model=uIonoModel.KLOBUCHAR, cs=None): diono, _ = ionoSBAS(t, pos, az, el, cs) if diono == 0.0: diono = ionKlobuchar(t, pos, az, el, nav.ion) + else: # model == uIonoModel.NONE + diono = 0.0 return diono # iono delay at L1 [m] @@ -59,7 +61,7 @@ def ICB(self, s=0): """ return index of clock bias (s=0), clock drift (s=1) """ return 3+s if self.nav.pmode == 0 else 6+s - def __init__(self, nav, pos0=np.zeros(3), logfile=None, trop_opt=0, + def __init__(self, nav, pos0=np.array([0, 0, rCST.RE_WGS84]), logfile=None, trop_opt=0, iono_opt=0, phw_opt=0, csmooth=False, rmode=0): self.nav = nav @@ -82,7 +84,7 @@ def __init__(self, nav, pos0=np.zeros(3), logfile=None, trop_opt=0, # Select iono model # - self.ionoModel = uIonoModel.KLOBUCHAR + self.nav.ionoModel = uIonoModel.KLOBUCHAR # 0: use trop-model, 1: estimate, 2: use cssr correction self.nav.trop_opt = trop_opt @@ -333,7 +335,7 @@ def zdres(self, obs, cs, bsx, rs, vs, dts, x, rtype=1): if self.nav.iono_opt == 0: # use model # Ionospheric delay iono = ionmodel(obs.t, pos, az[i], el[i], self.nav, - model=self.ionoModel, cs=cs) + model=self.nav.ionoModel, cs=cs) else: iono = 0.0 @@ -521,6 +523,7 @@ def process(self, obs, cs=None, orb=None, bsx=None, obsb=None): self.udstate(obs_) xp = self.nav.x.copy() + Pp = self.nav.P.copy() # Non-differential residuals # @@ -556,9 +559,8 @@ def process(self, obs, cs=None, orb=None, bsx=None, obsb=None): # SD residuals # v, H, R = self.sdres(obs, xp, y, e, sat, el) - Pp = self.nav.P.copy() - if abs(np.mean(v)) > 100.0: # clock bias initialize/reset + if abs(np.mean(v)) > 1e3: # clock bias initialize/reset ic = self.ICB() idx_ = np.where(v != 0.0)[0] xp[ic] = np.mean(v[idx_]) From cee2104b4e3767f52dbe2104cf2c5269249f0672 Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sun, 4 Jan 2026 14:54:18 +0900 Subject: [PATCH 21/48] - updated to support newest edition of draft SC134 messages. --- src/cssrlib/rtcm.py | 235 ++++++++++++++++++++++++++++---------------- 1 file changed, 149 insertions(+), 86 deletions(-) diff --git a/src/cssrlib/rtcm.py b/src/cssrlib/rtcm.py index 14d4a22..9586f59 100644 --- a/src/cssrlib/rtcm.py +++ b/src/cssrlib/rtcm.py @@ -146,6 +146,10 @@ class Integrity(): sig_k_t = [0.0, 0.04, 0.07, 0.10, 0.20, 0.25, 0.30, 0.40, 0.50, 0.70, 1, 2, 5, 7, 10, -1] + # GNSS Signal Modulation Mask Table 8-45 + mod_t = ['BPSK(1)', 'BPSK(5)', 'BPSK(10)', 'BOC(1,1)', 'CBOC(6,1,1/11)', + 'AltBOC(15,10)', '', ''] + # Validity Period DFi065 vp_tbl = [1, 2, 5, 10, 15, 30, 60, 120, 240, 300, 600, 900, 1800, 3600, 7200, 10800] @@ -1219,13 +1223,14 @@ def out_log_ssr_orb(self, sys, flg_vel=True): def out_log(self, obs=None, eph=None, geph=None, seph=None): """ output ssr message to log file """ + sys = -1 if self.is_ssrtype(self.msgtype, True): sys = self.get_ssr_sys(self.msgtype) inet = self.inet self.fh.write("{:4d}\t{:s}\n".format(self.msgtype, time2str(self.time))) - if self.subtype not in [sRTCM.SSR_META, sRTCM.SSR_GRID]: + if sys > 0 and self.subtype not in [sRTCM.SSR_META, sRTCM.SSR_GRID]: self.fh.write(f" Update Interval: {self.udint_t[self.uint]}[s]") self.fh.write(f" MultiMsg: {self.mi}\n") @@ -1452,6 +1457,61 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write("\n") self.fh.flush() + if self.msgtype == 54: # RTCM SC134 integrity messages + self.fh.write(f" Multiple Message Sequence :{self.integ.seq:}\n") + self.fh.write(" Message Type: {:4d}-{:-2d}\n". + format(self.msgtype, self.subtype)) + if self.subtype == 9: # MT54-9 VMAP + self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") + self.fh.write(f" Number of Area Points: {self.integ.narea:}\n") + self.fh.write(f" Number of Azimuth Slices: { + self.integ.naz:}\n") + self.fh.write(" Area Points (lat, long, alt):\n") + for k in range(self.integ.narea): + self.fh.write(" {:2d}\t{:12.9f}\t{:12.9f}\t{:4.0f}\n".format + (k+1, self.integ.pos[k, 0], self.integ.pos[k, 1], + self.integ.pos[k, 2])) + + self.fh.write(" Visibility mask (az, el):\n") + for k in range(self.integ.naz): + self.fh.write(" {:3.0f}\t{:3.0f}\n".format + (self.integ.azel[k][0], self.integ.azel[k][1])) + elif self.subtype == 10: + self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") + self.fh.write(f" Number of Area Points: {self.integ.narea:}\n") + self.fh.write(f" Multipath Model ID: {self.integ.mm_id:}\n") + self.fh.write(" Boundary Points (lat, long, alt):\n") + for k in range(self.integ.narea): + self.fh.write(" {:2d}\t{:12.9f}\t{:12.9f}\t{:4.0f}\n".format + (k+1, self.integ.pos[k, 0], self.integ.pos[k, 1], + self.integ.pos[k, 2])) + + if self.integ.mm_id == 0: + + self.fh.write( + " Components (Prob, Exp, stdev):\n") + for k in range(self.integ.narea): + for j in range(self.integ.np[k]): + self.fh.write(" {:2d}\t{:1d}\t{:6.4f}\t{:7.2f}\t{:7.2f}\n".format + (k+1, j+1, self.integ.mm_param[k][j][0], + self.integ.mm_param[k][j][1], + self.integ.mm_param[k][j][2])) + elif self.integ.mm_id in [1, 2]: + n = 3 if self.integ.mm_id == 1 else 4 + self.fh.write( + " Multipath Parameters (Modulation,a,b,c(,d)) :\n") + for k in range(self.integ.narea): + for j in range(self.integ.np[k]): + s = int(self.integ.mm_param[k][j][0]) + self.fh.write(" {:2d}\t{:1d}\t{:10s}".format + (k+1, j+1, self.integ.mod_t[s])) + for i in range(n): + self.fh.write("\t{:7.4f}".format + (self.integ.mm_param[k][j][i+1])) + self.fh.write("\n") + + self.fh.flush() + if eph is not None: sys, prn = sat2prn(eph.sat) self.fh.write(" {:20s}{:6d} ({:s})\n".format("PRN:", prn, @@ -3026,22 +3086,22 @@ def decode_integrity_cnr_acg(self, msg, i): def decode_integrity_vmap(self, msg, i): """ RTCM SC-134 Satellite Visibility Map Message (MT9) """ - if self.test_mode: # for test - wg, st, rev = bs.unpack_from('u4u8u8', msg, i) - i += 20 - self.subtype = st - self.ver = rev - self.wg = wg-1 - # GPS Epoch Time (TOW) DFi008 # number of area points DFi201 # Number of Azimuth Slices DFi205 # Message Continuation Flag DFi021 - tow, narea, naz, mcf = bs.unpack_from('u30u8u6u1', msg, i) + + tow, narea, naz, mi = bs.unpack_from('u30u8u6u1', msg, i) i += 45 - self.integ.pos = np.zeros((narea, 3)) + # Multiple Message Sequence Number DFi079 + self.integ.seq = bs.unpack_from('u5', msg, i)[0] + i += 5 - tow *= 1e-3 + self.integ.tow = tow*1e-3 + self.integ.narea = narea + self.integ.naz = naz + self.mi = mi + self.integ.pos = np.zeros((narea, 3)) for k in range(narea): # Area Point - Lat DFi202 @@ -3052,71 +3112,59 @@ def decode_integrity_vmap(self, msg, i): self.integ.pos[k, :] = [lat*1.1e-8, lon*1.1e-8, alt] - self.integ.azel = np.zeros((naz, 2)) + self.integ.azel = np.zeros((naz, 2)) - # Azimuth DFi206 - # Elevation Mask DFi208 - az = 0 - for k in range(naz): - daz, mask_el = bs.unpack_from('u9u7', msg, i) - i += 16 - az += daz - self.integ.azel[k, :] = [az, mask_el] + # Azimuth DFi206 + # Elevation Mask DFi208 + az = 0 + for k in range(naz): + daz, mask_el = bs.unpack_from('u9u7', msg, i) + i += 16 + az += daz + self.integ.azel[k, :] = [az, mask_el] return i def decode_integrity_mmap(self, msg, i): """ RTCM SC-134 Multipath Map Message (MT10) """ - if self.test_mode: # for test - wg, st, rev = bs.unpack_from('u4u8u8', msg, i) - i += 20 - self.subtype = st - self.ver = rev - self.wg = wg-1 - - # GPS Epoch Time (TOW) DFi008 - # number of area points DFi201 - # multipath model ID DFi209: 0:GMM,1:MBM,2:JM - tow, narea, mm_id, mcf = bs.unpack_from( - 'u30u8u3u1', msg, i) - i += 42 - tow *= 1e-3 - self.integ.pos = np.zeros((narea, 3)) - self.integ.mm_id = mm_id - - if mm_id == 0: + # GPS Epoch Time (TOW) DFi008 + # number of area points DFi201 + # multipath model ID DFi209: 0:GMM,1:MBM,2:JM + tow, narea, mm_id, self.mi, self.integ.seq = bs.unpack_from( + 'u30u8u3u1u5', msg, i) + i += 47 + self.integ.tow = tow*1e-3 + self.integ.narea = narea + self.integ.pos = np.zeros((narea, 3)) + self.integ.mm_id = mm_id - for k in range(narea): - # Area Point - Lat DFi202 - # Area Point - Lon DFi203 - # Area Point - Height DFi204 - lat, lon, alt = bs.unpack_from('s34s35s14', msg, i) - i += 83 + sigmask = np.zeros(narea, dtype=np.int32) - self.integ.pos[k, :] = [lat*1.1e-8, lon*1.1e-8, alt] + self.integ.np = np.zeros(narea, dtype=np.int32) + self.integ.mm_param = {} + n = 4 if mm_id == 1 else 5 - elif mm_id == 1 or mm_id == 2: - sigmask = np.array(narea, dtype=np.int32) - for k in range(narea): - # GNSS Signal Modulation Mask - DFi214 - # Area Point - Lat DFi202 - # Area Point - Lon DFi203 - # Area Point - Height DFi204 - sigmask[k], lat, lon, alt = bs.unpack_from( - 'u8s34s35s14', msg, i) - i += 91 - - self.integ.pos[k, :] = [lat*1.1e-8, lon*1.1e-8, alt] + for k in range(narea): + if mm_id in [1, 2]: + sigmask[k] = bs.unpack_from('u8', msg, i)[0] + i += 8 + # Area Point - Lat DFi202 + # Area Point - Lon DFi203 + # Area Point - Height DFi204 + lat, lon, alt = bs.unpack_from('s34s35s14', msg, i) + i += 83 + self.integ.pos[k, :] = [lat*1.1e-8, lon*1.1e-8, alt] + # i += 26 # ? if mm_id == 0: # Number of GMM components DFi210 ngmm = bs.unpack_from('u2', msg, i)[0] i += 2 + self.integ.np[k] = ngmm + prm = np.zeros((ngmm, 3)) - self.integ.mm_param = np.zeros((ngmm, 3)) - - for k in range(ngmm): + for j in range(ngmm): # GMM component probability DFi211 # GMM component expectation DFi212 # GMM component standard deviation DFi213 @@ -3126,33 +3174,34 @@ def decode_integrity_mmap(self, msg, i): prob *= 0.0625 exp = self.integ.mu_k_t[exp_] std = self.integ.sig_k_t[std_] - self.integ.mm_param[k, :] = [prob, exp, std] + prm[j, :] = [prob, exp, std] + + self.integ.mm_param[k] = prm elif mm_id == 1 or mm_id == 2: - self.integ.mm_param = {} - n = 3 if mm_id == 1 else 4 - - for k in range(narea): - sig_t, nsig = self.decode_mask(sigmask[k], 8, ofst=0) - prm = np.zeros((nsig, n)) - - for j in range(nsig): - # Multipath parameter a DFi215 - # Multipath parameter b DFi216 - # Multipath parameter c DFi217 - a, b, c = bs.unpack_from('u4s5s5', msg, i) - i += 14 + # mm_id=1: Mats Brenner Model Data + sig_t, nsig = self.decode_mask(sigmask[k], 8, ofst=0) + self.integ.np[k] = nsig + prm = np.zeros((nsig, n)) + + for j in range(nsig): + # Multipath parameter a DFi215 + # Multipath parameter b DFi216 + # Multipath parameter c DFi217 + a, b, c = bs.unpack_from('u4s5s5', msg, i) + i += 14 - a = self.integ.sig_k_t[a] - prm[j, 0:3] = [a, b*0.25, c*0.0625] + prm[j, 0] = sig_t[j] + a = self.integ.sig_k_t[a] + prm[j, 1:4] = [a, b*0.25, c*0.0625] - if mm_id == 2: - # Multipath parameter d DFi218 - d = bs.unpack_from('u8', msg, i)[0] - i += 8 - prm[j, 3] = d*0.3515625 + if mm_id == 2: + # Multipath parameter d DFi218 + d = bs.unpack_from('u8', msg, i)[0] + i += 8 + prm[j, 4] = d*0.3515625 - self.integ.mm_param[k] = prm + self.integ.mm_param[k] = prm return i @@ -3201,6 +3250,19 @@ def decode_integrity_ssr(self, msg, i): self.integ.iod_sys = iod_sys self.integ.flag = flag_t + def decode_sc134_test_header(self, msg, i): + # MT51-56 has subtype to extent message type space + # DFi028 uint8 as in Table 9-35 + + # Message Type DF002 uint12 + # Working Group DFi020 uint4 + # Sub-Message DFi028 uint8 + # Message Revision DFi00x uint4 => uint8? + wg, self.subtype, self.ver = bs.unpack_from('u4u8u8', msg, i) + self.wg = wg-1 + i += 20 + return i + def decode(self, msg, subtype=None, scanmode=False): """ decode RTCM messages """ i = 24 @@ -3370,12 +3432,13 @@ def decode(self, msg, subtype=None, scanmode=False): self.subtype = sRTCM.INTEG_SSR_TROP self.decode_integrity_ssr(msg, i) elif self.msgtype == 54: # SSR integrity test msg + i = self.decode_sc134_test_header(msg, i) if self.subtype == 9: self.decode_integrity_vmap(msg, i) - elif self.subtype == 10: - self.decode_integrity_mmap(msg, i) - else: - self.subtype = -1 + elif self.subtype == 10: + self.decode_integrity_mmap(msg, i) + else: + self.subtype = -1 if self.monlevel > 0 and self.fh is not None: self.out_log(obs, eph, geph, seph) From 053646cfc3f611ad3d17d0ed5e37feb6d29ccb89 Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sun, 4 Jan 2026 15:54:04 +0900 Subject: [PATCH 22/48] - fixed compile error. --- src/cssrlib/rtcm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cssrlib/rtcm.py b/src/cssrlib/rtcm.py index 9586f59..57e152a 100644 --- a/src/cssrlib/rtcm.py +++ b/src/cssrlib/rtcm.py @@ -1464,8 +1464,8 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): if self.subtype == 9: # MT54-9 VMAP self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") self.fh.write(f" Number of Area Points: {self.integ.narea:}\n") - self.fh.write(f" Number of Azimuth Slices: { - self.integ.naz:}\n") + self.fh.write(" Number of Azimuth Slices: " + + f"{self.integ.naz:}\n") self.fh.write(" Area Points (lat, long, alt):\n") for k in range(self.integ.narea): self.fh.write(" {:2d}\t{:12.9f}\t{:12.9f}\t{:4.0f}\n".format From 8b33b360e5c1897b59574decaf1babd1cb444446 Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sat, 10 Jan 2026 10:18:38 +0900 Subject: [PATCH 23/48] - fixed SBAS MT24,26 decoder. --- src/cssrlib/sbas.py | 58 ++++++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/src/cssrlib/sbas.py b/src/cssrlib/sbas.py index 005b3c8..625aa4b 100644 --- a/src/cssrlib/sbas.py +++ b/src/cssrlib/sbas.py @@ -432,25 +432,16 @@ def decode_sbas_mask(self, msg, i): i += 2 return i - def decode_sbas_fast_corr(self, msg, i): - """ Types 2 to 5 fast correction message """ - type_ = 0 if self.msgtype == 0 else self.msgtype-2 - - iodf, iodp = bs.unpack_from('u2u2', msg, i) - i += 4 - - if iodp != self.iodp: - i += 208 - return i - - for k in range(13): + def decode_sbas_fast_corr_blk(self, msg, i, n, type_): + """ decode fast correction for n blocks """ + for k in range(n): fc = bs.unpack_from('s12', msg, i)[0] i += 12 j = type_*13+k if j >= len(self.sat): break self.lc[0].hclk[self.sat[j]] = -self.sval(fc, 12, 0.125) - for k in range(13): + for k in range(n): udrei = bs.unpack_from('u4', msg, i)[0] i += 4 j = type_*13+k @@ -462,6 +453,20 @@ def decode_sbas_fast_corr(self, msg, i): self.lc[0].cstat |= (1 << sCType.HCLOCK) return i + def decode_sbas_fast_corr(self, msg, i): + """ Types 2 to 5 fast correction message """ + type_ = 0 if self.msgtype == 0 else self.msgtype-2 + + iodf, iodp = bs.unpack_from('u2u2', msg, i) + i += 4 + + if iodp != self.iodp: + i += 208 + return i + + i = self.decode_sbas_fast_corr_blk(msg, i, 13, type_) + return i + def decode_sbas_integrity(self, msg, i): """ Type 6 integrity message """ iodf2, iodf3, iodf4, iodf5 = bs.unpack_from('u2u2u2u2', msg, i) @@ -640,8 +645,8 @@ def decode_sbas_lcorr_half(self, msg, i): vc = bs.unpack_from('u1', msg, i)[0] i += 1 - i0 = 117 if vc == 0 else 105 - iodp = bs.unpack_from('u2', msg, i0)[0] + ofst = 102 if vc == 0 else 103 + iodp = bs.unpack_from('u2', msg, i+ofst)[0] if iodp != self.iodp: return i @@ -669,6 +674,20 @@ def decode_sbas_lcorr_half(self, msg, i): return i + def decode_sbas_mixed_corr(self, msg, i): + """ Type 24 mixed fast/long correction """ + + # fast correction (106bits) + iodp, type_, iodf = bs.unpack_from('u2u2u2', msg, i+96) + + if iodp != self.iodp: + i += 208 + return i + + i = self.decode_sbas_fast_corr_blk(msg, i, 6, type_) + i = self.decode_sbas_lcorr_half(msg, i) # MT25 half-msg(106bits) + return i + def decode_sbas_long_corr(self, msg, i): """ Type 25 long-term satellite error correction """ for k in range(2): @@ -677,13 +696,18 @@ def decode_sbas_long_corr(self, msg, i): def decode_sbas_iono(self, msg, i): """ Type 26 Ionospheric delay message """ - iodi = bs.unpack_from('u2', msg, 217)[0] + iodi = bs.unpack_from('u2', msg, i+203)[0] if iodi != self.iodi: i += 212 return i band, bid = bs.unpack_from('u4u4', msg, i) - nigp = len(self.vtec[band]) i += 8 + if band not in self.vtec: + i += 204 + return i + + nigp = len(self.vtec[band]) + for k in range(15): vd, givei = bs.unpack_from('u9u4', msg, i) i += 13 From 605a0ff219f0bd4cc39ee169ca87e1689da8a1ef Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Tue, 13 Jan 2026 21:49:27 +0900 Subject: [PATCH 24/48] - applied a change in MT54.9 message. --- src/cssrlib/rtcm.py | 54 +++++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/src/cssrlib/rtcm.py b/src/cssrlib/rtcm.py index 57e152a..5087345 100644 --- a/src/cssrlib/rtcm.py +++ b/src/cssrlib/rtcm.py @@ -1466,16 +1466,16 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write(f" Number of Area Points: {self.integ.narea:}\n") self.fh.write(" Number of Azimuth Slices: " + f"{self.integ.naz:}\n") - self.fh.write(" Area Points (lat, long, alt):\n") + self.fh.write(" Area Points (lat, long, alt), naz:\n") for k in range(self.integ.narea): - self.fh.write(" {:2d}\t{:12.9f}\t{:12.9f}\t{:4.0f}\n".format + self.fh.write(" {:2d}\t{:12.9f}\t{:12.9f}\t{:4.0f}\t{:2d}\n".format (k+1, self.integ.pos[k, 0], self.integ.pos[k, 1], - self.integ.pos[k, 2])) + self.integ.pos[k, 2], self.integ.naz[k])) - self.fh.write(" Visibility mask (az, el):\n") - for k in range(self.integ.naz): - self.fh.write(" {:3.0f}\t{:3.0f}\n".format - (self.integ.azel[k][0], self.integ.azel[k][1])) + self.fh.write(" Visibility mask (az, el):\n") + for j in range(self.integ.naz[k]): + self.fh.write(" {:3.0f}\t{:3.0f}\n".format + (self.integ.azel[k][j][0], self.integ.azel[k][j][1])) elif self.subtype == 10: self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") self.fh.write(f" Number of Area Points: {self.integ.narea:}\n") @@ -2477,7 +2477,7 @@ def decode_ssr_trop(self, msg, i, inet=0): trop.gnh = gnh*0.01e-3 # south-north gradient [m/deg] # wet trop.ofstw = ofstw*0.1e-3+0.252 # offset [m] - trop.gew = gew*0.1e-3 # west-east gradient [m/deg] + trop.gew = gew*0.01e-3 # west-east gradient [m/deg] trop.gnw = gnw*0.01e-3 # south-north gradient [m/deg] if rmi: # residual information part @@ -2544,6 +2544,9 @@ def decode_ssr_iono(self, msg, i, inet=0): # Resolution Scale Factor Exponent sf = bs.unpack_from('u3', msg, i)[0] # DF+073 i += 3 + scl = 1e-3*(2**sf) + else: + scl = 1.0 self.lc[inet].stec = {} fmt = 's'+str(rlen) @@ -2554,7 +2557,6 @@ def decode_ssr_iono(self, msg, i, inet=0): for j in range(ofst, ofst+ng): res = bs.unpack_from(fmt, msg, i)[0] # DF+062 i += rlen - scl = 1e-3*(2**sf) self.lc[inet].stec[sat_][j] = self.sval(res, rlen, scl) self.gid = gid @@ -3088,40 +3090,44 @@ def decode_integrity_vmap(self, msg, i): # GPS Epoch Time (TOW) DFi008 # number of area points DFi201 - # Number of Azimuth Slices DFi205 # Message Continuation Flag DFi021 - tow, narea, naz, mi = bs.unpack_from('u30u8u6u1', msg, i) - i += 45 + tow, narea, mi = bs.unpack_from('u30u8u1', msg, i) + i += 39 # Multiple Message Sequence Number DFi079 self.integ.seq = bs.unpack_from('u5', msg, i)[0] i += 5 self.integ.tow = tow*1e-3 self.integ.narea = narea - self.integ.naz = naz self.mi = mi self.integ.pos = np.zeros((narea, 3)) + self.integ.naz = np.zeros(narea, dtype=int) + self.integ.azel = {} for k in range(narea): # Area Point - Lat DFi202 # Area Point - Lon DFi203 # Area Point - Height DFi204 - lat, lon, alt = bs.unpack_from('s34s35s14', msg, i) - i += 83 + # Number of Azimuth Slices DFi205 + lat, lon, alt, naz = bs.unpack_from('s34s35s14u6', msg, i) + i += 89 self.integ.pos[k, :] = [lat*1.1e-8, lon*1.1e-8, alt] + self.integ.naz[k] = naz - self.integ.azel = np.zeros((naz, 2)) + azel = np.zeros((naz, 2)) - # Azimuth DFi206 - # Elevation Mask DFi208 - az = 0 - for k in range(naz): - daz, mask_el = bs.unpack_from('u9u7', msg, i) - i += 16 - az += daz - self.integ.azel[k, :] = [az, mask_el] + # Azimuth DFi206 + # Elevation Mask DFi208 + az = 0 + for j in range(naz): + daz, mask_el = bs.unpack_from('u9u7', msg, i) + i += 16 + az += daz + azel[j, :] = [az, mask_el] + + self.integ.azel[k] = azel return i From 68e1b184ae14d06e7d61ad0171cee45c41ff070f Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Thu, 15 Jan 2026 22:12:00 +0900 Subject: [PATCH 25/48] - updated SC134 message based on the newest standard. --- src/cssrlib/rtcm.py | 503 ++++++++++++++++++++------------------------ 1 file changed, 229 insertions(+), 274 deletions(-) diff --git a/src/cssrlib/rtcm.py b/src/cssrlib/rtcm.py index 5087345..e8a2684 100644 --- a/src/cssrlib/rtcm.py +++ b/src/cssrlib/rtcm.py @@ -42,14 +42,15 @@ class sRTCM(IntEnum): SBS_EPH = 1017 # SC-134 test messages - INTEG_MIN = 2003 - INTEG_EXT = 2004 - INTEG_EXT_SIS = 2005 - INTEG_EXT_AREA = 2006 - INTEG_QUALITY = 2007 - INTEG_CNR = 2008 - INTEG_VMAP = 2009 - INTEG_MMAP = 2010 + INTEG_MIN = 2000 + INTEG_EXT = 2005 + INTEG_EXT_SIS = 2006 + INTEG_PRI_AREA = 2007 + INTEG_EXT_AREA = 2008 + INTEG_QUALITY = 2051 + INTEG_CNR = 2091 + INTEG_VMAP = 2071 + INTEG_MMAP = 2072 INTEG_SSR = 2011 INTEG_SSR_IONO = 2012 INTEG_SSR_TROP = 2013 @@ -150,6 +151,9 @@ class Integrity(): mod_t = ['BPSK(1)', 'BPSK(5)', 'BPSK(10)', 'BOC(1,1)', 'CBOC(6,1,1/11)', 'AltBOC(15,10)', '', ''] + # + mm_model_t = ['GMM', 'Mats Brenner', 'Jahn'] + # Validity Period DFi065 vp_tbl = [1, 2, 5, 10, 15, 30, 60, 120, 240, 300, 600, 900, 1800, 3600, 7200, 10800] @@ -1461,12 +1465,11 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write(f" Multiple Message Sequence :{self.integ.seq:}\n") self.fh.write(" Message Type: {:4d}-{:-2d}\n". format(self.msgtype, self.subtype)) + if self.subtype == 9: # MT54-9 VMAP self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") self.fh.write(f" Number of Area Points: {self.integ.narea:}\n") - self.fh.write(" Number of Azimuth Slices: " + - f"{self.integ.naz:}\n") - self.fh.write(" Area Points (lat, long, alt), naz:\n") + self.fh.write(" Boundary Points (lat, long, alt, naz):\n") for k in range(self.integ.narea): self.fh.write(" {:2d}\t{:12.9f}\t{:12.9f}\t{:4.0f}\t{:2d}\n".format (k+1, self.integ.pos[k, 0], self.integ.pos[k, 1], @@ -1477,19 +1480,19 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write(" {:3.0f}\t{:3.0f}\n".format (self.integ.azel[k][j][0], self.integ.azel[k][j][1])) elif self.subtype == 10: + model = self.integ.mm_model_t[self.integ.mm_id] self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") self.fh.write(f" Number of Area Points: {self.integ.narea:}\n") - self.fh.write(f" Multipath Model ID: {self.integ.mm_id:}\n") - self.fh.write(" Boundary Points (lat, long, alt):\n") + self.fh.write(f" Multipath Model ID: {model}\n") + self.fh.write(" Boundary Points (lat, long, alt), np:\n") for k in range(self.integ.narea): - self.fh.write(" {:2d}\t{:12.9f}\t{:12.9f}\t{:4.0f}\n".format + self.fh.write(" {:2d}\t{:12.9f}\t{:12.9f}\t{:4.0f}\t{:2d}\n".format (k+1, self.integ.pos[k, 0], self.integ.pos[k, 1], - self.integ.pos[k, 2])) + self.integ.pos[k, 2], self.integ.np[k])) if self.integ.mm_id == 0: - self.fh.write( - " Components (Prob, Exp, stdev):\n") + self.fh.write(" Components (Prob, Exp, stdev):\n") for k in range(self.integ.narea): for j in range(self.integ.np[k]): self.fh.write(" {:2d}\t{:1d}\t{:6.4f}\t{:7.2f}\t{:7.2f}\n".format @@ -1506,7 +1509,7 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write(" {:2d}\t{:1d}\t{:10s}".format (k+1, j+1, self.integ.mod_t[s])) for i in range(n): - self.fh.write("\t{:7.4f}".format + self.fh.write("\t{:9.6f}".format (self.integ.mm_param[k][j][i+1])) self.fh.write("\n") @@ -2566,10 +2569,10 @@ def decode_ssr_iono(self, msg, i, inet=0): return i def decode_integrity_min(self, msg, i): - """ RTCM SC-134 minimum integrity message (MT3) """ + """ RTCM SC-134 minimum integrity message (MT3,2000) """ # augmentation service provider id DFi027 - # GPS Epoch Time (TOW) DFi008 + # GNSS Epoch Time (TOW) DFi008 # Constellation Mask DFi013 pid, tow, mask_sys = bs.unpack_from('u12u30u16', msg, i) i += 58 @@ -2623,18 +2626,18 @@ def decode_integrity_min(self, msg, i): prn = svid+ofst sat = prn2sat(sys, prn) - # GNSS signal mask DFi033 - sigmask[sys][sat] = bs.unpack_from('u32', msg, i)[0] - i += 32 - # issue of GNSS signal mask DFi012 + # GNSS frequency mask DFi022 + sigmask[sys][sat] = bs.unpack_from('u8', msg, i)[0] + i += 8 + # issue of GNSS frequency mask DFi023 iod_sig[sys][sat] = bs.unpack_from('u2', msg, i)[0] i += 2 - # signal integrity status DFi031 - sig_ists[sys][sat] = bs.unpack_from('u32', msg, i)[0] - i += 32 - # signal monitoring status DFi032 - sig_msts[sys][sat] = bs.unpack_from('u32', msg, i)[0] - i += 32 + # frequency integrity status DFi024 + sig_ists[sys][sat] = bs.unpack_from('u8', msg, i)[0] + i += 8 + # frequency monitoring status DFi025 + sig_msts[sys][sat] = bs.unpack_from('u8', msg, i)[0] + i += 8 self.integ.pid = pid self.integ.tow = tow @@ -2652,14 +2655,17 @@ def decode_integrity_min(self, msg, i): def decode_integrity_ext(self, msg, i): """ RTCM SC-134 extended integrity message - service levels and overbounding parameters (MT4) """ + service levels and overbounding parameters (MT4,2005) """ # GPS Epoch Time (TOW) DFi008 # augmentation service provider id DFi027 + # service area id DFi075 # Augmentation Integrity Level DFi026 + # TTTcomm DFi050 # Service Provider Solution Type DFi004 - tow, pid, ilvl, stype = bs.unpack_from('u30u12u4u4', msg, i) - i += 50 + tow, pid, area, ilvl, ttt, stype = bs.unpack_from('u30u12u10u8u7u15', + msg, i) + i += 82 self.integ.pid = pid self.integ.tow = tow @@ -2669,14 +2675,12 @@ def decode_integrity_ext(self, msg, i): # GNSS Constellation Mask DFi013 # Validity Period DFi065 (0-15) # update rate interval DFi067 (0.1) - # Validity Area Type DFi056 - mask_sys, vp, uri, atype = bs.unpack_from('u16u4u16u2', msg, i) - i += 38 + mask_sys, vp, uri = bs.unpack_from('u16u4u16', msg, i) + i += 36 sys_t, nsys = self.decode_mask(mask_sys, 16, ofst=0) self.integ.vp = vp # Validity Period DFi065 (0-15) self.integ.uri = uri # update rate interval DFi067 (0.1) - self.integ.atype = atype # Paug Bundling flag DFi066 f_paug = bs.unpack_from('u1', msg, i)[0] @@ -2693,35 +2697,7 @@ def decode_integrity_ext(self, msg, i): self.integ.fc, self.integ.iod_p = bs.unpack_from('u1u6', msg, i) i += 7 - if atype == 1: # Validity Area Parameters defined in Table 10.3-5 - # service area IOD DFi005 - # number of area points DFi201 - self.integ.iod_sa, narea = bs.unpack_from('u6u8', msg, i) - i += 14 - self.integ.narea = narea - self.integ.pos = np.zeros((narea, 3)) - - for k in range(narea): - # Area Point - Lat DFi202 - # Area Point - Lon DFi203 - # Area Point - Height DFi204 - lat, lon, alt = bs.unpack_from('s34s35s14', msg, i) - i += 83 - - self.integ.pos[k, :] = [lat, lon, alt] - - elif atype == 0: # Validity Radius Data defined in Table 10.3-6 - # service area IOD DFi005 - self.integ.iod_sa = bs.unpack_from('u6', msg, i)[0] - i += 6 - # Augmentation Network Computation Point ECEF - X DFi053 - # Augmentation Network Computation Point ECEF - Y DFi054 - # Augmentation Network Computation Point ECEF - Z DFi055 - # Validity Radius DFi057 - x, y, z, vr = bs.unpack_from('s34s34s34u20', msg, i) - i += 122 - - self.integ.pnt_vr = [x, y, z, vr] + # constellation specific part self.integ.Pa_sp = {} self.integ.sig_a = {} @@ -2749,19 +2725,21 @@ def decode_integrity_ext(self, msg, i): i += 8 # Correlation Time of Pseudorange Augmentation Message Error DFi044 + # Gauss-Markov stdev of pseudorange DFi069 # Correlation Time of Carrier Phase Augmentation Message Error # DFi045 + # Gauss-Markov stdev of carrier-phase DFi070 # Multiple Satellite Pseudorange Augmentation Message Fault # Probability DFi047 # Multiple Satellite Carrier Phase Augmentation Message Fault # Probability DFi035 - tau_p, tau_c, tau_cp, tau_cc = bs.unpack_from('u4u4u4u4', msg, i) - i += 16 + tau_p, sig_p, tau_c, sig_c, tau_cp, tau_cc = bs.unpack_from( + 'u4u5u4u5u4u4', msg, i) + i += 26 - # Update rate interval DFi067 # GNSS satellite mask DFi009 - uri, mask_sat = bs.unpack_from('u16u64', msg, i) - i += 80 + mask_sat = bs.unpack_from('u64', msg, i) + i += 64 self.integ.mfd_s[sys] = mfd_s self.integ.mfd_c[sys] = mfd_c @@ -2771,10 +2749,9 @@ def decode_integrity_ext(self, msg, i): self.integ.tau_cp[sys] = tau_cp self.integ.tau_cc[sys] = tau_cc - self.integ.uri[sys] = uri - svid_t, nsat = self.decode_mask(mask_sat, 64) + # satellite-specific part for svid in svid_t: ofst = 192 if sys == uGNSS.QZS else 0 prn = svid+ofst @@ -2805,182 +2782,194 @@ def decode_integrity_ext(self, msg, i): def decode_integrity_ext_sis_local(self, msg, i): """ RTCM SC-134 extended integrity message - signal in space integrity and local error parameters (MT5) """ + signal in space integrity and local error parameters (MT5, 2006) """ + # GNSS Epoch Time (TOW) DFi008 # integrity parameter IOD DFi006 # augmentation service provider id DFi027 - # GPS Epoch Time (TOW) DFi008 - iod_ip, pid, tow = bs.unpack_from('u6u12u30', msg, i) - i += 48 + # service area id DFi075 + tow, iod_ip, pid, area = bs.unpack_from('u30u6u12u10', msg, i) + i += 58 # GNSS Constellation Mask DFi013 # Validity Period DFi065 (0-15) # update rate interval DFi067 (0.1) - # Validity Area Type DFi056 # Time correlation integrity/continuity flag DFi137 - mask_sys, vp, uri, atype, fc = bs.unpack_from( - 'u16u4u16u2u1', msg, i) - i += 39 + mask_sys, vp, uri, fc = bs.unpack_from('u16u4u16u1', msg, i) + i += 37 + + # Bounding inter-constellation bias error parameter DFi132 + # Bounding inter-frequency bias error parameter DFi136 + bias_ic, bias_if = bs.unpack_from('u3u3', msg, i) + i += 6 sys_t, nsys = self.decode_mask(mask_sys, 16, ofst=0) self.integ.vp = vp # Validity Period DFi065 (0-15) self.integ.uri = uri # update rate interval DFi067 (0.1) + # constellation-specific part + for sys_ in sys_t: + sys = self.integ.sys_tbl[sys_] + + # GNSS satellite mask DFi009 + # issue of GNSS satellite mask DFi010 + mask_sat, iod_mask = bs.unpack_from('u64u2', msg, i) + i += 66 + + svid_t, nsat = self.decode_mask(mask_sat, 64) + + self.integ.sig_pd[sys] = {} + self.integ.sig_cd[sys] = {} + self.integ.cbr[sys] = {} + self.integ.sig_dp[sys] = {} + self.integ.idx_dp[sys] = {} + + self.integ.oc[sys] = {} + self.integ.sig_ion[sys] = {} + self.integ.idx_ion[sys] = {} + self.integ.sig_trp[sys] = {} + self.integ.idx_trp[sys] = {} + + for svid in svid_t: + ofst = 192 if sys == uGNSS.QZS else 0 + prn = svid+ofst + sat = prn2sat(sys, prn) + + # Bound on rate of change of PR error stdev DFi120 + # Bound on rate of change in phase error stdev DFi121 + # Nominal pseudorange bias rate of change DFi125 + # Bounding sigma on phase-range-rate error DFi122 + # Phase-range-rate error rate integrity index DFi123 + sig_pd, sig_cd, cbr, sig_dp, idx_dp = bs.unpack_from( + 'u4u4u3u4u4', msg, i) + i += 19 + + # Orbit and clock error rate integrity parameter DFi127 + # Residual ionospheric error standard deviation DFi128 + # Index of bounding parameter on change in iono error DFi129 + # Residual tropospheric error standard deviation DFi130 + # Index of bounding parameter on change in tropo error DFi131 + oc, sig_ion, idx_ion, sig_trp, idx_trp = bs.unpack_from( + 'u3u3u3u3u3', msg, i) + i += 15 + + self.integ.sig_pd[sys][sat] = sig_pd + self.integ.sig_cd[sys][sat] = sig_cd + self.integ.cbr[sys][sat] = cbr + self.integ.sig_dp[sys][sat] = sig_dp + self.integ.idx_dp[sys][sat] = idx_dp + + self.integ.oc[sys][sat] = oc + self.integ.sig_ion[sys][sat] = sig_ion + self.integ.idx_ion[sys][sat] = idx_ion + self.integ.sig_trp[sys][sat] = sig_trp + self.integ.idx_trp[sys][sat] = idx_trp + return i + + def decode_integrity_pri_service_area(self, msg, i): + """ RTCM SC-134 Primary Service Area Parameters (MT6,MT2007) """ + + # GNSS Epoch Time (TOW) DFi008 + # service area iod DFi005 + # augmentation service provider id DFi027 + # service area id DFi075 + # Validity Period DFi065 (0-15) + tow, iod_sa, pid, area, vp = bs.unpack_from( + 'u30u6u12u10u4', msg, i) + i += 62 + + # augmentation integrity level DFi026 + # GNSS Constellation Mask DFi013 + + # update rate interval DFi067 (0.1) + # service area rtpe DFi056 + # service area parameter continuation flag DFi041 + # multiple message sequence number + uri, atype, cf, seq = bs.unpack_from('u16u2u1u5', msg, i) + i += 24 + + self.integ.iod_sa = iod_sa + self.integ.tow = tow + self.integ.pid = pid + self.integ.vp = vp + self.integ.uri = uri + if atype == 1: # Validity Area Parameters defined in Table 10.3-5 - # service area IOD DFi005 # number of area points DFi201 - self.integ.iod_sa, narea = bs.unpack_from('u6u8', msg, i) - i += 14 + narea = bs.unpack_from('u8', msg, i)[0] + i += 8 self.integ.pos = np.zeros((narea, 3)) - for k in range(narea): # Area Point - Lat DFi202 # Area Point - Lon DFi203 - # Area Point - Height DFi204 - lat, lon, alt = bs.unpack_from('s34s35s14', msg, i) - i += 83 - - self.integ.pos[k, :] = [lat, lon, alt] + lat, lon = bs.unpack_from('s34s35', msg, i) + i += 69 + self.integ.pos[k, :] = [lat, lon, 0] elif atype == 0: # Validity Radius Data defined in Table 10.3-6 - # service area IOD DFi005 - self.integ.iod_sa = bs.unpack_from('u6', msg, i)[0] - i += 6 - # Augmentation Network Computation Point ECEF - X DFi053 - # Augmentation Network Computation Point ECEF - Y DFi054 - # Augmentation Network Computation Point ECEF - Z DFi055 - # Validity Radius DFi057 - x, y, z, vr = bs.unpack_from('s34s34s34u20', msg, i) - i += 122 - - self.integ.pnt_vr = [x, y, z, vr] - - for sys_ in sys_t: - sys = self.integ.sys_tbl[sys_] - - # GNSS satellite mask DFi009 - # issue of GNSS satellite mask DFi010 - mask_sat, iod_mask = bs.unpack_from('u64u2', msg, i) - i += 66 - - svid_t, nsat = self.decode_mask(mask_sat, 64) - - self.integ.idx_c[sys] = {} - self.integ.idx_p[sys] = {} - self.integ.cbr[sys] = {} - self.integ.sig_dp[sys] = {} - self.integ.idx_dp[sys] = {} - - self.integ.oc[sys] = {} - self.integ.sig_ion[sys] = {} - self.integ.idx_ion[sys] = {} - self.integ.sig_trp[sys] = {} - self.integ.idx_trp[sys] = {} - - for svid in svid_t: - ofst = 192 if sys == uGNSS.QZS else 0 - prn = svid+ofst - sat = prn2sat(sys, prn) - - # Pseudorange error rate integrity index DFi120 - # Phase error rate integrity index DFi121 - # Nominal pseudorange bias rate of change DFi125 - # Bounding sigma on phase-range-rate error DFi122 - # Phase-range-rate error rate integrity index DFi123 - idx_c, idx_p, cbr, sig_dp, idx_dp = bs.unpack_from( - 'u4u4u3u4u4', msg, i) - i += 19 - - # Orbit and clock error rate integrity parameter DFi127 - # Residual ionospheric error standard deviation DFi128 - # Index of bounding parameter on change in iono error DFi129 - # Residual tropospheric error standard deviation DFi130 - # Index of bounding parameter on change in tropo error DFi131 - oc, sig_ion, idx_ion, sig_trp, idx_trp = bs.unpack_from( - 'u3u3u3u3u3', msg, i) - i += 15 - - self.integ.idx_c[sys][sat] = idx_c - self.integ.idx_p[sys][sat] = idx_p - self.integ.cbr[sys][sat] = cbr - self.integ.sig_dp[sys][sat] = sig_dp - self.integ.idx_dp[sys][sat] = idx_dp - - self.integ.oc[sys][sat] = oc - self.integ.sig_ion[sys][sat] = sig_ion - self.integ.idx_ion[sys][sat] = idx_ion - self.integ.sig_trp[sys][sat] = sig_trp - self.integ.idx_trp[sys][sat] = idx_trp + # Area Point - Lat DFi202 + # Area Point - Lon DFi203 + # Validity Radius DF057 + lat, lon, r = bs.unpack_from('s34s35u20', msg, i) + i += 89 + self.integ.pos = [lat, lon, r] return i def decode_integrity_ext_service_area(self, msg, i): - """ RTCM SC-134 Extended Service Area Parameters (MT6) """ + """ RTCM SC-134 Extended Service Area Parameters (MT7,MT2008) """ - # integrity parameter IOD DFi006 + # GNSS Epoch Time (TOW) DFi008 + # service area iod DFi005 # augmentation service provider id DFi027 - # GPS Epoch Time (TOW) DFi008 - # augmentation integrity level DFi026 - iod_sa, tow, pid, alvl = bs.unpack_from( - 'u6u30u12u4', msg, i) - i += 52 - - # GNSS Constellation Mask DFi013 + # service area id DFi075 # Validity Period DFi065 (0-15) - # update rate interval DFi067 (0.1) - # Validity Area Type DFi056 - mask_sys, vp, uri, atype = bs.unpack_from( - 'u16u4u16u2', msg, i) - i += 38 + tow, iod_sa, pid, area, vp = bs.unpack_from( + 'u30u6u12u10u4', msg, i) + i += 62 - sys_t, nsys = self.decode_mask(mask_sys, 16, ofst=0) + # update rate interval DFi067 (0.1) + # service area rtpe DFi056 + # service area parameter continuation flag DFi041 + # multiple message sequence number DFi079 + uri, atype, cf, seq = bs.unpack_from('u16u2u1u5', msg, i) + i += 24 # Augmentation IR Degradation Factor DFi140 # Augmentation TTD Degradation Factor DFi141 # Extended Area Time Parameter Degradation Factor DFi138 # Extended Area Spatial Parameter Degradation Factor DFi139 - ir_d, ttd_d, t_d, s_d = bs.unpack_from('u2u2u3u3', msg, i) + f_ir, f_ttd, f_ext, f_exs = bs.unpack_from('u2u2u3u3', msg, i) i += 10 self.integ.iod_sa = iod_sa self.integ.tow = tow self.integ.pid = pid - self.integ.alvl = alvl self.integ.vp = vp self.integ.uri = uri - self.integ.ir_d = ir_d - self.integ.ttd_d = ttd_d - self.integ.t_d = t_d - self.integ.s_d = s_d if atype == 1: # Validity Area Parameters defined in Table 10.3-5 - # service area IOD DFi005 # number of area points DFi201 - self.integ.iod_sa, narea = bs.unpack_from('u6u8', msg, i) - i += 14 + narea = bs.unpack_from('u8', msg, i)[0] + i += 8 self.integ.pos = np.zeros((narea, 3)) for k in range(narea): # Area Point - Lat DFi202 # Area Point - Lon DFi203 - # Area Point - Height DFi204 - lat, lon, alt = bs.unpack_from('s34s35s14', msg, i) - i += 83 - self.integ.pos[k, :] = [lat, lon, alt] + lat, lon = bs.unpack_from('s34s35', msg, i) + i += 69 + self.integ.pos[k, :] = [lat, lon, 0] elif atype == 0: # Validity Radius Data defined in Table 10.3-6 - # service area IOD DFi005 - self.integ.iod_sa = bs.unpack_from('u6', msg, i)[0] - i += 6 - # Augmentation Network Computation Point ECEF - X DFi053 - # Augmentation Network Computation Point ECEF - Y DFi054 - # Augmentation Network Computation Point ECEF - Z DFi055 - # Validity Radius DFi057 - x, y, z, vr = bs.unpack_from('s34s34s34u20', msg, i) - i += 122 - self.integ.pnt_vr = [x, y, z, vr] + # Area Point - Lat DFi202 + # Area Point - Lon DFi203 + # Validity Radius DF057 + lat, lon, r = bs.unpack_from('s34s35u20', msg, i) + i += 89 + self.integ.pos = [lat, lon, r] return i def decode_integrity_quality(self, msg, i): - """ RTCM SC-134 Message Quality Indicator (MT7) """ + """ RTCM SC-134 Message Quality Indicator (MT7, 2051) """ # GPS Epoch Time (TOW) DFi008 # augmentation service provider id DFi027 @@ -3009,95 +2998,60 @@ def decode_integrity_quality(self, msg, i): return i def decode_integrity_cnr_acg(self, msg, i): - """ RTCM SC-134 CNR/ACG Signal In Space Monitoring Message (MT8) """ + """ RTCM SC-134 CNR/ACG Signal In Space Monitoring Message (MT8, 2091) """ # augmentation service provider id DFi027 # GPS Epoch Time (TOW) DFi008 - # number of reference stations DFi072 # update rate interval DFi067 (0.1) - pid, tow, nst, uri = bs.unpack_from('u12u30u12u16', msg, i) - i += 12+30+12+16 + # service area id DFi075 + # constellation mask DFi013 + pid, tow, uri, area, mask_c = bs.unpack_from('u12u30u16u10u16', msg, i) + i += 84 self.integ.tow = tow self.integ.pid = pid self.integ.uri = uri - self.integ.pos = np.zeros((nst, 3)) - self.integ.iod_sat = np.zeros(nst, dtype=int) - - for k in range(nst): - # Area Point - Lat DFi202 - # Area Point - Lon DFi203 - # Area Point - Height DFi204 - lat, lon, alt = bs.unpack_from('s34s35s14', msg, i) - i += 34+35+14 - - self.integ.pos[k, :] = [lat, lon, alt] - - # Constellation Mask DFi013 - # GNSS Satellite Mask DFi009 - # Issue of GNSS Satellite Mask DFi010 - mask_c, mask_s, iod_sat = bs.unpack_from( - 'u16u64u2', msg, i) - i += 82 - - self.integ.iod_sat[k] = iod_sat - - sys_t, nsys = self.decode_mask(mask_c, 16, ofst=0) - svid_t, nsat = self.decode_mask(mask_s, 64) + # constellation specific part + sys_t, nsys = self.decode_mask(mask_c, 12, ofst=0) for sys in sys_t: - self.integ.band[sys] = {} self.integ.cnr[sys] = {} + self.integ.agc_t[sys] = {} self.integ.agc[sys] = {} - for svid in svid_t: - ofst = 192 if sys == uGNSS.QZS else 0 - prn = svid+ofst - sat = prn2sat(sys, prn) + mask_s, iod_sm = bs.unpack_from('u64u2', msg, i) + i += 66 - # GNSS Signal Mask DFi033 - # Issue of GNSS Signal Mask DFi012 - mask_sig, iod_sig = bs.unpack_from('u32u2', msg, i) - i += 34 - sig_t, nsig = self.decode_mask(mask_sig, 32, ofst=0) + sat_t, nsat = self.decode_mask(mask_s, 64, ofst=1) - self.integ.band[sys][sat] = np.zeros(nsig) - self.integ.cnr[sys][sat] = np.zeros(nsig) - self.integ.agc[sys][sat] = np.zeros(nsig) + for sat in sat_t: + mask_f, iod_fm = bs.unpack_from('u8u2', msg, i) + i += 10 - for sig in sig_t: - # Frequency band ID DFi135 - band = bs.unpack_from('u5', msg, i)[0] - i += 5 - self.integ.band[sys][sat][sig] = band + sig_t, nsig = self.decode_mask(mask_f, 8, ofst=0) for sig in sig_t: # CNR carrier to noise ratio DFi133 - cnr = bs.unpack_from('u8', msg, i)[0] - i += 8 + cnr, agc_t, agc = bs.unpack_from('u8u1u8', msg, i)[0] + i += 17 self.integ.cnr[sys][sat][sig] = cnr - - for sig in sig_t: - # AGC, Automatic Gain Control DFi134 - agc = bs.unpack_from('u8', msg, i)[0] - i += 8 + self.integ.agc_t[sys][sat][sig] = agc_t self.integ.agc[sys][sat][sig] = agc + return i def decode_integrity_vmap(self, msg, i): - """ RTCM SC-134 Satellite Visibility Map Message (MT9) """ + """ RTCM SC-134 Satellite Visibility Map Message (MT9, 2071) """ - # GPS Epoch Time (TOW) DFi008 + # GNSS Epoch Time (TOW) DFi008 # number of area points DFi201 # Message Continuation Flag DFi021 - - tow, narea, mi = bs.unpack_from('u30u8u1', msg, i) - i += 39 # Multiple Message Sequence Number DFi079 - self.integ.seq = bs.unpack_from('u5', msg, i)[0] - i += 5 + tow, narea, mi, seq = bs.unpack_from('u30u8u1u5', msg, i) + i += 44 + self.integ.seq = seq self.integ.tow = tow*1e-3 self.integ.narea = narea self.mi = mi @@ -3132,9 +3086,9 @@ def decode_integrity_vmap(self, msg, i): return i def decode_integrity_mmap(self, msg, i): - """ RTCM SC-134 Multipath Map Message (MT10) """ + """ RTCM SC-134 Multipath Map Message (MT10, 2072) """ - # GPS Epoch Time (TOW) DFi008 + # GNSS Epoch Time (TOW) DFi008 # number of area points DFi201 # multipath model ID DFi209: 0:GMM,1:MBM,2:JM tow, narea, mm_id, self.mi, self.integ.seq = bs.unpack_from( @@ -3145,15 +3099,13 @@ def decode_integrity_mmap(self, msg, i): self.integ.pos = np.zeros((narea, 3)) self.integ.mm_id = mm_id - sigmask = np.zeros(narea, dtype=np.int32) - self.integ.np = np.zeros(narea, dtype=np.int32) self.integ.mm_param = {} n = 4 if mm_id == 1 else 5 for k in range(narea): if mm_id in [1, 2]: - sigmask[k] = bs.unpack_from('u8', msg, i)[0] + mask_s = bs.unpack_from('u8', msg, i)[0] i += 8 # Area Point - Lat DFi202 # Area Point - Lon DFi203 @@ -3184,9 +3136,9 @@ def decode_integrity_mmap(self, msg, i): self.integ.mm_param[k] = prm - elif mm_id == 1 or mm_id == 2: + elif mm_id in [1, 2]: # mm_id=1: Mats Brenner Model Data - sig_t, nsig = self.decode_mask(sigmask[k], 8, ofst=0) + sig_t, nsig = self.decode_mask(mask_s, 8, ofst=0) self.integ.np[k] = nsig prm = np.zeros((nsig, n)) @@ -3264,9 +3216,9 @@ def decode_sc134_test_header(self, msg, i): # Working Group DFi020 uint4 # Sub-Message DFi028 uint8 # Message Revision DFi00x uint4 => uint8? - wg, self.subtype, self.ver = bs.unpack_from('u4u8u8', msg, i) + wg, self.subtype, self.ver = bs.unpack_from('u4u8u4', msg, i) self.wg = wg-1 - i += 20 + i += 16 return i def decode(self, msg, subtype=None, scanmode=False): @@ -3404,31 +3356,34 @@ def decode(self, msg, subtype=None, scanmode=False): i = self.decode_ssr_iono(msg, i) # test messages for SC-134 - elif self.msgtype == 3: # minimum integrity + elif self.msgtype in [3, 2000]: # minimum integrity self.subtype = sRTCM.INTEG_MIN self.decode_integrity_min(msg, i) - elif self.msgtype == 4: # extended integrity + elif self.msgtype in [4, 2005]: # extended integrity self.subtype = sRTCM.INTEG_EXT self.decode_integrity_ext(msg, i) - elif self.msgtype == 5: # sis integrity/local error + elif self.msgtype in [5, 2006]: # sis integrity/local error self.subtype = sRTCM.INTEG_EXT_SIS self.decode_integrity_ext_sis_local(msg, i) - elif self.msgtype == 6: # extended sercice area + elif self.msgtype in [6, 2007]: # primary sercice area + self.subtype = sRTCM.INTEG_PRI_AREA + self.decode_integrity_pri_service_area(msg, i) + elif self.msgtype == 2008: # extended sercice area self.subtype = sRTCM.INTEG_EXT_AREA self.decode_integrity_ext_service_area(msg, i) - elif self.msgtype == 7: # quality indicator + elif self.msgtype in [7, 2051]: # quality indicator self.subtype = sRTCM.INTEG_QUALITY self.decode_integrity_quality(msg, i) - elif self.msgtype == 8: # CNR/ACG SIS Monitoring + elif self.msgtype in [8, 2091]: # CNR/ACG SIS Monitoring self.subtype = sRTCM.INTEG_CNR self.decode_integrity_cnr_acg(msg, i) - elif self.msgtype == 9: # satellite visibility map + elif self.msgtype == 2071: # satellite visibility map self.subtype = sRTCM.INTEG_VMAP self.decode_integrity_vmap(msg, i) - elif self.msgtype == 10: # multipath map + elif self.msgtype == 2072: # multipath map self.subtype = sRTCM.INTEG_MMAP self.decode_integrity_mmap(msg, i) - elif self.msgtype == 11: # SSR integrity + elif self.msgtype in [11, 2011]: # SSR integrity self.subtype = sRTCM.INTEG_SSR self.decode_integrity_ssr(msg, i) elif self.msgtype == 12: # SSR integrity Iono From c66ddda9efd9f688b148868911b2ddacbc1da3ce Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sun, 18 Jan 2026 23:25:33 +0900 Subject: [PATCH 26/48] - initial support of RTCM SSR encoder (experimental). --- src/cssrlib/cssrlib.py | 98 +++- src/cssrlib/gnss.py | 79 ++- src/cssrlib/rtcm.py | 1090 +++++++++++++++++++++++++++++++++------- 3 files changed, 1035 insertions(+), 232 deletions(-) diff --git a/src/cssrlib/cssrlib.py b/src/cssrlib/cssrlib.py index 58c74f2..a4fe349 100644 --- a/src/cssrlib/cssrlib.py +++ b/src/cssrlib/cssrlib.py @@ -217,20 +217,29 @@ class local_corr: def __init__(self): self.inet = -1 self.inet_ref = -1 - self.ng = -1 + self.ng = 0 + self.ofst = 0 self.pbias = {} self.cbias = {} self.yaw = {} self.dyaw = {} self.iode = None self.dorb = None + self.ddorb = None self.dclk = None + self.ddclk = None + self.dddclk = None self.hclk = None + self.ura = None self.stec = None self.trph = None self.trpw = None - self.ci = None - self.ct = None + self.dth = None # trop hydro-static (residual term) + self.dtw = None # trop wet (residual term) + self.maph = None # mapping function parameters (ah, bh, ch) (hs) + self.mapw = None # mapping function parameters (aw, bw, cw) (wet) + self.ct = None # trop parameters (functional term) + self.ci = None # iono parameters (functional term) self.quality_trp = None self.quality_stec = None self.sat_n = [] @@ -238,7 +247,7 @@ def __init__(self): self.cstat = 0 # status for receiving CSSR message self.t0s = {} sc_t = [sCType.CLOCK, sCType.ORBIT, sCType.CBIAS, sCType.PBIAS, - sCType.HCLOCK] + sCType.HCLOCK, sCType.URA, sCType.TROP, sCType.STEC] for sc in sc_t: self.t0s[sc] = gtime_t() @@ -279,6 +288,8 @@ def __init__(self, foutname=None): self.cbias = [] self.pbias = [] self.inet = -1 + self.datum = 0 + self.udi = np.zeros(sCType.MAX, dtype=int) self.facility = -1 self.pattern = -1 self.sid = -1 @@ -322,7 +333,6 @@ def __init__(self, foutname=None): self.iodssr_c = np.ones(16, dtype=np.int32)*-1 self.sig_n_p = [] self.mi = 0 - self.uint = 0 # SSR Update Interval [s] DF391 self.udint_t = [1, 2, 5, 10, 15, 30, 60, 120, @@ -541,7 +551,7 @@ def decode_head(self, msg, i, st=-1): if self.week >= 0: self.time = gpst2time(self.week, self.tow) fmt = 'u4u1u4' - names = ['uint', 'mi', 'iodssr'] + names = ['udi', 'mi', 'iodssr'] dfm = bs.unpack_from_dict(fmt, names, msg, i) i += 9 return (dfm, i) @@ -676,6 +686,7 @@ def decode_cssr_orb(self, msg, i, inet=0): """decode MT4073,2 Orbit Correction message """ head, i = self.decode_head(msg, i) self.flg_net = False + self.udi[sCType.ORBIT] = head['udi'] self.lc[inet].dorb = {} self.lc[inet].iode = {} @@ -692,6 +703,7 @@ def decode_cssr_clk(self, msg, i, inet=0): """decode MT4073,3 Clock Correction message """ head, i = self.decode_head(msg, i) self.flg_net = False + self.udi[sCType.CLOCK] = head['udi'] # if self.iodssr != head['iodssr']: # return -1 @@ -722,6 +734,7 @@ def decode_cssr_cbias(self, msg, i, inet=0): head, i = self.decode_head(msg, i) nsat = self.nsat_n self.flg_net = False + self.udi[sCType.CBIAS] = head['udi'] self.lc[inet].cbias = {} for k in range(nsat): sat = self.sat_n[k] @@ -741,6 +754,7 @@ def decode_cssr_pbias(self, msg, i, inet=0): head, i = self.decode_head(msg, i) nsat = self.nsat_n self.flg_net = False + self.udi[sCType.PBIAS] = head['udi'] self.lc[inet].pbias = {} self.lc[inet].di = {} for k in range(nsat): @@ -761,6 +775,7 @@ def decode_cssr_bias(self, msg, i, inet=0): """decode MT4073,6 Bias Correction message """ nsat = self.nsat_n head, i = self.decode_head(msg, i) + # if self.iodssr != head['iodssr']: # return -1 dfm = bs.unpack_from_dict('b1b1b1', ['cb', 'pb', 'net'], msg, i) @@ -776,9 +791,12 @@ def decode_cssr_bias(self, msg, i, inet=0): if dfm['cb']: self.lc[inet].cbias = {} + self.udi[sCType.CBIAS] = head['udi'] + if dfm['pb']: self.lc[inet].pbias = {} self.lc[inet].di = {} + self.udi[sCType.PBIAS] = head['udi'] ki = 0 for k in range(self.nsat_n): if not self.isset(v['svmaskn'], self.nsat_n, k): @@ -811,18 +829,19 @@ def decode_cssr_bias(self, msg, i, inet=0): return i - def decode_cssr_ura(self, msg, i): + def decode_cssr_ura(self, msg, i, inet=0): """decode MT4073,7 URA message """ head, i = self.decode_head(msg, i) if self.iodssr != head['iodssr']: return -1 - self.ura = {} + self.udi[sCType.URA] = head['udi'] + self.lc[inet].ura = {} for k in range(self.nsat_n): sat = self.sat_n[k] v = bs.unpack_from_dict('u3u3', ['class', 'val'], msg, i) - self.ura[sat] = self.quality_idx(v['class'], v['val']) + self.lc[inet].ura[sat] = self.quality_idx(v['class'], v['val']) i += 6 - self.lc[0].cstat |= (1 << sCType.URA) + self.lc[inet].cstat |= (1 << sCType.URA) self.set_t0(ctype=sCType.URA, t=self.time) return i @@ -855,6 +874,7 @@ def decode_cssr_stec(self, msg, i): if self.iodssr != head['iodssr']: return -1 self.flg_net = True + self.udi[sCType.STEC] = head['udi'] dfm = bs.unpack_from_dict('u2u5u'+str(self.nsat_n), ['stype', 'inet', 'svmaskn'], msg, i) inet = dfm['inet'] @@ -887,6 +907,7 @@ def decode_cssr_grid(self, msg, i): ['ttype', 'range', 'inet', 'svmaskn', 'class', 'value', 'ng'], msg, i) self.flg_net = True + self.udi[sCType.TROP] = head['udi'] inet = dfm['inet'] self.netmask[inet] = netmask = dfm['svmaskn'] self.lc[inet].sat_n = self.decode_local_sat(netmask) @@ -899,16 +920,16 @@ def decode_cssr_grid(self, msg, i): sz = 7 if dfm['range'] == 0 else 16 fmt = 's'+str(sz) self.lc[inet].stec = np.zeros(ng, dtype=list) - self.lc[inet].dtd = np.zeros(ng) + self.lc[inet].dth = np.zeros(ng) self.lc[inet].dtw = np.zeros(ng) for j in range(ng): self.lc[inet].stec[j] = {} if dfm['ttype'] > 0: - vd = bs.unpack_from_dict('s9s8', ['dtd', 'dtw'], msg, i) + dth, dtw = bs.unpack_from('s9s8', msg, i) i += 17 - self.lc[inet].dtd[j] = self.sval(vd['dtd'], 9, 0.004)+2.3 - self.lc[inet].dtw[j] = self.sval(vd['dtw'], 8, 0.004) + self.lc[inet].dth[j] = self.sval(dth, 9, 0.004)+2.3 + self.lc[inet].dtw[j] = self.sval(dtw, 8, 0.004) for k in range(nsat): sat = self.lc[inet].sat_n[k] @@ -960,8 +981,10 @@ def decode_cssr_comb(self, msg, i, inet=0): if dfm['orb']: self.lc[inet].dorb = {} self.lc[inet].iode = {} + self.udi[sCType.ORBIT] = head['udi'] if dfm['clk']: self.lc[inet].dclk = {} + self.udi[sCType.CLOCK] = head['udi'] for k in range(self.nsat_n): if self.flg_net and not self.isset(svmask, self.nsat_n, k): @@ -995,6 +1018,7 @@ def decode_cssr_atmos(self, msg, i): self.flg_net = True inet = dfm['inet'] self.lc[inet].ng = ng = dfm['ng'] + self.lc[inet].ofst = 0 self.lc[inet].flg_trop = dfm['trop'] self.lc[inet].flg_stec = dfm['stec'] i += 15 @@ -1004,33 +1028,37 @@ def decode_cssr_atmos(self, msg, i): self.lc[inet].trop_quality = self.quality_idx(v['class'], v['value']) i += 6 + self.udi[sCType.TROP] = head['udi'] + + self.lc[inet].ct = np.zeros((2, 4)) + if dfm['trop'] & 2: # functional term self.lc[inet].ttype = ttype = bs.unpack_from('u2', msg, i)[0] i += 2 vt = bs.unpack_from_dict('s9', ['t00'], msg, i) i += 9 - self.lc[inet].ct = np.zeros(4) - self.lc[inet].ct[0] = self.sval(vt['t00'], 9, 0.004) + self.lc[inet].ct[0, 0] = self.sval(vt['t00'], 9, 0.004) if ttype > 0: vt = bs.unpack_from_dict('s7s7', ['t01', 't10'], msg, i) i += 14 - self.lc[inet].ct[1] = self.sval(vt['t01'], 7, 0.002) - self.lc[inet].ct[2] = self.sval(vt['t10'], 7, 0.002) + self.lc[inet].ct[0, 1] = self.sval(vt['t01'], 7, 0.002) + self.lc[inet].ct[0, 2] = self.sval(vt['t10'], 7, 0.002) if ttype > 1: vt = bs.unpack_from_dict('s7', ['t11'], msg, i) i += 7 - self.lc[inet].ct[3] = self.sval(vt['t11'], 7, 0.001) + self.lc[inet].ct[0, 3] = self.sval(vt['t11'], 7, 0.001) if dfm['trop'] & 1: # residual term vh = bs.unpack_from_dict('u1u4', ['sz', 'ofst'], msg, i) i += 5 - trop_ofst = vh['ofst']*0.02 + self.lc[inet].ct[1, 0] = vh['ofst']*0.02 sz = 6 if vh['sz'] == 0 else 8 vtr = bs.unpack_from(('s'+str(sz))*ng, msg, i) i += sz*ng + self.lc[inet].dtw = np.zeros(ng) for k in range(ng): - self.lc[inet].dtw[k] = self.sval(vtr[k], sz, 0.004)+trop_ofst + self.lc[inet].dtw[k] = self.sval(vtr[k], sz, 0.004) # STEC netmask = bs.unpack_from('u'+str(self.nsat_n), msg, i)[0] @@ -1039,6 +1067,7 @@ def decode_cssr_atmos(self, msg, i): self.lc[inet].sat_n = self.decode_local_sat(netmask) self.lc[inet].nsat_n = nsat = len(self.lc[inet].sat_n) self.lc[inet].stec_quality = {} + self.udi[sCType.STEC] = head['udi'] if dfm['stec'] & 2 > 0: # functional term is available self.lc[inet].ci = {} self.lc[inet].stype = {} @@ -1294,9 +1323,14 @@ def get_trop(self, dlat=0.0, dlon=0.0): trph = 0 trpw = 0 if self.lc[inet].flg_trop & 2: - trph = 2.3+self.lc[inet].ct@[1, dlat, dlon, dlat*dlon] + p = [1, dlat, dlon, dlat*dlon] + trph = 2.3+self.lc[inet].ct[0, :]@p + trpw = self.lc[inet].ct[1, :]@p if self.lc[inet].flg_trop & 1: - trpw = self.lc[inet].dtw[self.grid_index-1]@self.grid_weight + if self.lc[inet].dth is not None: + trph += self.lc[inet].dth[self.grid_index-1]@self.grid_weight + if self.lc[inet].dtw is not None: + trpw += self.lc[inet].dtw[self.grid_index-1]@self.grid_weight return trph, trpw def get_stec(self, dlat=0.0, dlon=0.0): @@ -1306,10 +1340,13 @@ def get_stec(self, dlat=0.0, dlon=0.0): stec = np.zeros(nsat) for i in range(nsat): sat = self.lc[inet].sat_n[i] - if self.lc[inet].flg_stec & 2: + if self.lc[inet].flg_stec & 2: # functional term ci = self.lc[inet].ci[sat] - stec[i] = [1, dlat, dlon, dlat*dlon, dlat**2, dlon**2]@ci - if self.lc[inet].flg_stec & 1: + if len(ci) == 3: + stec[i] = [1, dlat, dlon]@ci + else: + stec[i] = [1, dlat, dlon, dlat*dlon, dlat**2, dlon**2]@ci + if self.lc[inet].flg_stec & 1: # residual term dstec = self.lc[inet].dstec[sat][self.grid_index-1] \ @ self.grid_weight stec[i] += dstec @@ -1419,6 +1456,7 @@ def __init__(self): self.dlen = 0 self.msgtype = 0 self.iodssr = 0 + self.udi = np.zeros(sCType.MAX, dtype=int) def encode_mask(self, v, bitlen, ofst=1): """ encode n-bit mask with offset """ @@ -1428,3 +1466,11 @@ def encode_mask(self, v, bitlen, ofst=1): d |= 1 << (bitlen-k-1) return d + + def quality2qi(self, y): + """ convert from quality to quality index (class, value) """ + n3 = np.log(3) + n_ = np.log(y*1000+1)/n3 + cls_ = int(n_) + val_ = int((np.exp((n_-cls_)*n3)-1)/0.25) + return cls_, val_ diff --git a/src/cssrlib/gnss.py b/src/cssrlib/gnss.py index 157940f..6098ea5 100644 --- a/src/cssrlib/gnss.py +++ b/src/cssrlib/gnss.py @@ -1545,16 +1545,17 @@ def meteo(hgt, humi): return pres, temp, e -def mapf(el, a, b, c): +def mapf(sE, a, b, c): """ simple tropospheric mapping function """ - sinel = np.sin(el) - return (1.0+a/(1.0+b/(1.0+c)))/(sinel+(a/(sinel+b/(sinel+c)))) + return (1.0+a/(1.0+b/(1.0+c)))/(sE+(a/(sE+b/(sE+c)))) -def tropmapfNiell(t, pos, el): - """ tropospheric mapping function by Niell (NMF) """ +def mapfParam(t, pos, el): + """ mapping function parameters based on lat,alt,time """ if pos[2] < -1e3 or pos[2] > 20e3 or el <= 0.0: return 0.0, 0.0 + # lat = [15,30,45,60,75] + # hs(average) [a,b,c] hs(amplitude) [a,b,c] wet [a,b,c] coef = np.array([ [1.2769934E-3, 1.2683230E-3, 1.2465397E-3, 1.2196049E-3, 1.2045996E-3], [2.9153695E-3, 2.9152299E-3, 2.9288445E-3, 2.9022565E-3, 2.9024912E-3], @@ -1566,9 +1567,8 @@ def tropmapfNiell(t, pos, el): [1.4275268E-3, 1.5138625E-3, 1.4572752E-3, 1.5007428E-3, 1.7599082E-3], [4.3472961E-2, 4.6729510E-2, 4.3908931E-2, 4.4626982E-2, 5.4736038E-2], ]) - aht = [2.53E-5, 5.49E-3, 1.14E-3] lat = np.rad2deg(pos[0]) - hgt = pos[2] + y = (time2doy(t)-28.0)/365.25 if lat < 0.0: y += 0.5 @@ -1577,9 +1577,29 @@ def tropmapfNiell(t, pos, el): c = interpc(coef, lat) ah = c[0:3]-c[3:6]*cosy aw = c[6:9] - dm = (1.0/np.sin(el)-mapf(el, aht[0], aht[1], aht[2]))*hgt*1e-3 - mapfh = mapf(el, ah[0], ah[1], ah[2])+dm - mapfw = mapf(el, aw[0], aw[1], aw[2]) + + # return [ah,bh,ch], [aw,bw,cw] + return ah, aw + + +def tropmapfNiell(t, pos, el, ah=None, aw=None): + """ tropospheric mapping function by Niell (NMF) """ + + hgt = pos[2] # ellipsoidal height [m] + + if hgt < -1e3 or hgt > 20e3 or el <= 0.0: + return 0.0, 0.0 + + if ah is None or aw is None: + ah, aw = mapfParam(t, pos, el) + + aht = [2.53E-5, 5.49E-3, 1.14E-3] # height correction + + sE = np.sin(el) + dm = (1.0/sE-mapf(sE, aht[0], aht[1], aht[2]))*hgt*1e-3 + mapfh = mapf(sE, ah[0], ah[1], ah[2])+dm + mapfw = mapf(sE, aw[0], aw[1], aw[2]) + return mapfh, mapfw @@ -1640,8 +1660,8 @@ def tropmapfSBAS(el): return mf, mf -def tropmodelSBAS(t, pos, el=np.pi/2): - """ SBAS tropospheric delay model """ +def atmosParam(t, lat, el=np.pi/2): + """ atmospheric model parameters (P[mbar], T[K], e[mbar], beta[K/m], lam) """ # average of P[mbar], T[K], e[mbar], beta[K/m], lam tbl_a = np.array([ @@ -1661,8 +1681,6 @@ def tropmodelSBAS(t, pos, el=np.pi/2): [-0.50, 14.50, 3.39, 0.62e-3, 0.30], ]) - lat = pos[0]*rCST.R2D - hgt = pos[2] dmin = 28 if lat >= 0 else 211 y = (time2doy(t)-dmin)/365.25 cosy = np.cos(2.0*np.pi*y) @@ -1670,18 +1688,41 @@ def tropmodelSBAS(t, pos, el=np.pi/2): c = interpc(np.c_[tbl_a, tbl_v].T, np.abs(lat)) pres, temp, e, beta, lam = c[0:5]-c[5:10]*cosy + return pres, temp, e, beta, lam + + +def tropheightCorr(t, temp, beta, lam, hgt): + """ tropspheric delay height correction """ + + Rd, g = 287.054, 9.80665 + + p1, p2 = 1-beta*hgt/temp, g/(Rd*beta) + sf_hs = np.pow(p1, p2) + sf_wet = np.pow(p1, (lam+1)*p2-1) + + return sf_hs, sf_wet + + +def tropmodelSBAS(t, pos, el=np.pi/2): + """ SBAS tropospheric delay model """ + + lat = pos[0]*rCST.R2D + hgt = pos[2] + + pres, temp, e, beta, lam = atmosParam(t, lat, el) + sf_hs, sf_wet = tropheightCorr(t, temp, beta, lam, hgt) + Rd, gm = 287.054, 9.784 # zero-altitude zenith delay term z_hyd = 77.604e-6*Rd*pres/gm z_wet = 0.382*Rd*e/((gm*(lam+1.0)-beta*Rd)*temp) - g = 9.80665 - d_hyd = np.pow(1-beta*hgt/temp, g/(Rd*beta))*z_hyd - d_wet = np.pow(1-beta*hgt/temp, (lam+1)*g/(Rd*beta)-1)*z_wet + d_hyd = sf_hs*z_hyd + d_wet = sf_wet*z_wet # mapping function - s_el = np.sin(el) - mf = 1.001/np.sqrt(0.002001+s_el**2) + sE = np.sin(el) + mf = 1.001/np.sqrt(0.002001+sE**2) trop_hyd, trop_wet = d_hyd*mf, d_wet*mf diff --git a/src/cssrlib/rtcm.py b/src/cssrlib/rtcm.py index e8a2684..aace082 100644 --- a/src/cssrlib/rtcm.py +++ b/src/cssrlib/rtcm.py @@ -20,7 +20,7 @@ from cssrlib.gnss import uGNSS, sat2id, gpst2time, timediff, time2str, sat2prn from cssrlib.gnss import uTYP, uSIG, rSigRnx, bdt2time, bdt2gpst, glo2time from cssrlib.gnss import time2bdt, gpst2bdt, rCST, time2gpst, utc2gpst, timeadd -from cssrlib.gnss import gtime_t, sys2str, sat2prn, sat2id, gpst2utc +from cssrlib.gnss import gtime_t, sys2str, gpst2utc from crccheck.crc import Crc24LteA from enum import IntEnum from cssrlib.gnss import Eph, Obs, Geph, Seph @@ -202,20 +202,6 @@ def __init__(self): self.mm_param = None -class TropModel(): - """ Tropospheric delay correction model for RTCM SSR """ - - def __init__(self): - self.dah = 0.0 # DF+38 - self.dbh = 0.0 # DF+39 - self.dch = 0.0 # DF+40 - self.daw = 0.0 # DF+41 - self.dbw = 0.0 # DF+42 - self.dcw = 0.0 # DF+43 - self.rh = np.array([]) - self.rw = np.array([]) - - class rtcm(cssr): """ class to decode RTCM3 messages """ @@ -230,7 +216,6 @@ def __init__(self, foutname=None): self.pid = 0 # SSR Provider ID self.sid = 0 # SSR Solution Type - self.uint = 0 # Update Interval self.mi = False self.nrtk_r = {} @@ -250,6 +235,11 @@ def __init__(self, foutname=None): uGNSS.QZS: 1044, uGNSS.GAL: 1046 } + self.sc_t = {sCSSR.ORBIT: sCType.ORBIT, sCSSR.CLOCK: sCType.CLOCK, + sCSSR.MASK: sCType.MASK, sCSSR.CBIAS: sCType.CBIAS, + sCSSR.PBIAS: sCType.PBIAS, sCSSR.URA: sCType.URA, + sCSSR.GRID: sCType.TROP, sCSSR.STEC: sCType.STEC} + self.glo_bias = None # GLONASS receiver bias self.pos_arp = None # receiver position self.ant_desc = "unknown" @@ -396,8 +386,10 @@ def ssig2rsig(self, sys: uGNSS, utyp: uTYP, ssig): 2: uSIG.L1C, 3: uSIG.L2S, 4: uSIG.L2L, + 5: uSIG.L2X, 6: uSIG.L5I, 7: uSIG.L5Q, + 8: uSIG.L5X, 9: uSIG.L6S, 10: uSIG.L6L, 11: uSIG.L6X, @@ -453,7 +445,6 @@ def msm2rsig(self, sys: uGNSS, utyp: uTYP, ssig): 11: uSIG.L4B, 12: uSIG.L4X, 13: uSIG.L6A, - 13: uSIG.L6B, 14: uSIG.L6X, 15: uSIG.L3I, 16: uSIG.L3Q, @@ -571,7 +562,7 @@ def checksum(self, msg, k, maxlen=0): self.dlen = len_+6 return cs == 0 - def decode_head(self, msg, i, sys): + def decode_head(self, msg, i, sys=uGNSS.NONE): """ decode the header of ssr message """ if self.msgtype == 4076 or sys != uGNSS.GLO: blen = 20 @@ -580,8 +571,11 @@ def decode_head(self, msg, i, sys): self.tow = bs.unpack_from('u'+str(blen), msg, i)[0] self.time = gpst2time(self.week, self.tow) i += blen - uint, mi = bs.unpack_from('u4b1', msg, i) - i += 5 + + if self.subtype not in [sRTCM.SSR_PBIAS_EX]: + udi, mi = bs.unpack_from('u4b1', msg, i) + i += 5 + v = {'udi': udi, 'mi': mi} if self.subtype in (sCSSR.ORBIT, sCSSR.COMBINED): self.datum = bs.unpack_from('u1', msg, i)[0] @@ -590,28 +584,25 @@ def decode_head(self, msg, i, sys): iodssr, pid, sid = bs.unpack_from('u4u16u4', msg, i) i += 24 + v['iodssr'] = iodssr + self.pid = pid + self.sid = sid + if self.subtype == sCSSR.PBIAS: ci, mw = bs.unpack_from('u1u1', msg, i) i += 2 elif self.subtype == sRTCM.SSR_PBIAS: # Satellite Yaw Information Indicator DF486 # Extended Phase Bias Property ID DF+2 - iyaw, self.iexpb = bs.unpack_from('b1u4', msg, i) + v['iyaw'], self.iexpb = bs.unpack_from('b1u4', msg, i) i += 5 + v['iexpb'] = self.iexpb - if self.subtype not in [sCSSR.VTEC, sRTCM.SSR_PBIAS_EX, sRTCM.SSR_STEC]: + if self.subtype not in [sCSSR.VTEC, sRTCM.SSR_PBIAS_EX, sRTCM.SSR_STEC, + sRTCM.SSR_TROP]: nsat = bs.unpack_from('u6', msg, i)[0] + v['nsat'] = nsat i += 6 - else: - nsat = 0 - - self.pid = pid - self.sid = sid - - v = {'iodssr': iodssr, 'uint': uint, 'mi': mi, 'nsat': nsat} - if self.subtype == sRTCM.SSR_PBIAS: - v['iyaw'] = iyaw - v['iexpb'] = self.iexpb return i, v @@ -671,9 +662,9 @@ def decode_clk_sat(self, msg, i, k, inet=0): def decode_hclk_sat(self, msg, i, k, inet=0): """ decoder high-rate clock correction of cssr """ - dclk, ddclk, dddclk = bs.unpack_from('s22', msg, i)[0] + hclk = bs.unpack_from('s22', msg, i)[0] i += 22 - self.dclk_n[k] = self.sval(dclk, 22, 0.1e-3) + self.dclk_n[k] = self.sval(hclk, 22, 0.1e-3) return i def get_ssr_sys(self, msgtype): @@ -749,7 +740,7 @@ def decode_cssr_orb(self, msg, i, inet=0): self.lc[inet].ddorb = {} self.iodssr = v['iodssr'] - self.uint = v['uint'] + self.udi[sCType.ORBIT] = v['udi'] self.mi = v['mi'] sat = [] for k in range(nsat): @@ -767,7 +758,7 @@ def decode_cssr_orb(self, msg, i, inet=0): self.sat_n += sat self.iodssr_c[sCType.ORBIT] = v['iodssr'] - self.lc[0].cstat |= (1 << sCType.ORBIT) + self.lc[inet].cstat |= (1 << sCType.ORBIT) self.lc[inet].t0s[sCType.ORBIT] = self.time return i @@ -790,7 +781,7 @@ def decode_cssr_clk(self, msg, i, inet=0): self.dddclk_n = np.zeros(nsat) self.iodssr = v['iodssr'] - self.uint = v['uint'] + self.udi[sCType.CLOCK] = v['udi'] self.mi = v['mi'] for k in range(nsat): @@ -803,7 +794,7 @@ def decode_cssr_clk(self, msg, i, inet=0): self.set_t0(inet, sat_, sCType.CLOCK, self.time) self.iodssr_c[sCType.CLOCK] = v['iodssr'] - self.lc[0].cstat |= (1 << sCType.CLOCK) + self.lc[inet].cstat |= (1 << sCType.CLOCK) self.lc[inet].t0s[sCType.CLOCK] = self.time return i @@ -821,7 +812,7 @@ def decode_cssr_cbias(self, msg, i, inet=0): self.lc[inet].cbias = {} self.iodssr = v['iodssr'] - self.uint = v['uint'] + self.udi[sCType.CBIAS] = v['udi'] self.mi = v['mi'] for k in range(nsat): @@ -843,7 +834,7 @@ def decode_cssr_cbias(self, msg, i, inet=0): self.lc[inet].cbias[sat_][rsig] = self.sval(cb, 14, 0.01) self.iodssr_c[sCType.CBIAS] = v['iodssr'] - self.lc[0].cstat |= (1 << sCType.CBIAS) + self.lc[inet].cstat |= (1 << sCType.CBIAS) self.lc[inet].t0s[sCType.CBIAS] = self.time return i @@ -862,9 +853,10 @@ def decode_cssr_pbias(self, msg, i, inet=0): self.lc[inet].pbias = {} self.lc[inet].si = {} self.lc[inet].cnt = {} + self.lc[inet].wl = {} self.iodssr = v['iodssr'] - self.uint = v['uint'] + self.udi[sCType.PBIAS] = v['udi'] self.mi = v['mi'] self.iyaw = v['iyaw'] @@ -890,6 +882,7 @@ def decode_cssr_pbias(self, msg, i, inet=0): self.lc[inet].pbias[sat_] = {} self.lc[inet].si[sat_] = {} self.lc[inet].cnt[sat_] = {} + self.lc[inet].wl[sat_] = {} for j in range(nsig): # tracking mode: DF461 @@ -897,22 +890,23 @@ def decode_cssr_pbias(self, msg, i, inet=0): sig, si = bs.unpack_from('u5b1', msg, i) i += 6 - if self.subtype != sRTCM.SSR_PBIAS: + rsig = self.ssig2rsig(sys, uTYP.L, sig).str() + if self.subtype != sRTCM.SSR_PBIAS: # IGS-SSR wl = bs.unpack_from('u2', msg, i)[0] i += 2 + self.lc[inet].wl[sat_][rsig] = wl # signal discontinuity indicator: DF485 # phase bias: DF482 cnt, pb = bs.unpack_from('u4s20', msg, i) i += 24 - rsig = self.ssig2rsig(sys, uTYP.L, sig).str() self.lc[inet].pbias[sat_][rsig] = self.sval(pb, 20, 1e-4) self.lc[inet].si[sat_][rsig] = si self.lc[inet].cnt[sat_][rsig] = cnt self.iodssr_c[sCType.PBIAS] = v['iodssr'] - self.lc[0].cstat |= (1 << sCType.PBIAS) + self.lc[inet].cstat |= (1 << sCType.PBIAS) self.lc[inet].t0s[sCType.PBIAS] = self.time return i @@ -941,7 +935,8 @@ def decode_cssr_comb(self, msg, i, inet=0): self.lc[inet].ddorb = {} self.iodssr = v['iodssr'] - self.uint = v['uint'] + self.udi[sCType.ORBIT] = v['udi'] + self.udi[sCType.CLOCK] = v['udi'] self.mi = v['mi'] sat = [] @@ -967,8 +962,8 @@ def decode_cssr_comb(self, msg, i, inet=0): self.iodssr_c[sCType.ORBIT] = v['iodssr'] self.iodssr_c[sCType.CLOCK] = v['iodssr'] - self.lc[0].cstat |= (1 << sCType.ORBIT) - self.lc[0].cstat |= (1 << sCType.CLOCK) + self.lc[inet].cstat |= (1 << sCType.ORBIT) + self.lc[inet].cstat |= (1 << sCType.CLOCK) self.lc[inet].t0s[sCType.ORBIT] = self.time return i @@ -980,21 +975,21 @@ def decode_cssr_ura(self, msg, i, inet=0): nsat = v['nsat'] if timediff(self.time, self.lc[inet].t0s[sCType.URA]) > 0: - self.ura = np.zeros(self.nsat_n) + self.lc[inet].ura = {} self.iodssr = v['iodssr'] - self.uint = v['uint'] + self.udi[sCType.URA] = v['udi'] self.mi = v['mi'] for k in range(nsat): i, sat_ = self.decode_sat(msg, i, sys) - s = self.sat_n.index(sat_) - cls_, val = bs.unpack_from_dict('u3u3', msg, i) - self.ura[s] = self.quality_idx(cls_, val) + cls_, val = bs.unpack_from('u3u3', msg, i) + i += 6 + self.lc[inet].ura[sat_] = self.quality_idx(cls_, val) self.set_t0(inet, sat_, sCType.URA, self.time) self.iodssr_c[sCType.URA] = v['iodssr'] - self.lc[0].cstat |= (1 << sCType.URA) + self.lc[inet].cstat |= (1 << sCType.URA) self.lc[inet].t0s[sCType.URA] = self.time return i @@ -1007,20 +1002,20 @@ def decode_cssr_hclk(self, msg, i, inet=0): # return -1 if timediff(self.time, self.lc[inet].t0s[sCType.HCLOCK]) > 0: - self.lc[inet].dclk = {} + self.lc[inet].hclk = {} self.iodssr = v['iodssr'] - self.uint = v['uint'] + self.udi[sCType.HCLOCK] = v['udi'] self.mi = v['mi'] for k in range(v['nsat']): i, sat_ = self.decode_sat(msg, i, sys) i = self.decode_hclk_sat(msg, i, k) - self.lc[inet].dclk[sat_] = self.dclk_n[k] + self.lc[inet].hclk[sat_] = self.dclk_n[k] self.set_t0(inet, sat_, sCType.HCLOCK, self.time) self.iodssr_c[sCType.HCLOCK] = v['iodssr'] - self.lc[0].cstat |= (1 << sCType.HCLOCK) + self.lc[inet].cstat |= (1 << sCType.HCLOCK) self.lc[inet].t0s[sCType.HCLOCK] = self.time return i @@ -1054,8 +1049,8 @@ def decode_vtec(self, msg, i, inet=0): s[l_, j] = self.sval(s_, 16, 5e-3) self.iodssr_c[sCType.VTEC] = v['iodssr'] - self.lc[0].cstat |= (1 << sCType.VTEC) - self.lc[0].t0[sCType.VTEC] = self.time + self.lc[inet].cstat |= (1 << sCType.VTEC) + self.lc[inet].t0[sCType.VTEC] = self.time self.set_t0(ctype=sCType.VTEC, t=self.time) return i @@ -1225,6 +1220,19 @@ def out_log_ssr_orb(self, sys, flg_vel=True): self.lc[0].ddorb[sat_][2]*1e3)) self.fh.write("\n") + def out_log_ssr_ura(self, sys): + """ output ssr URA to log file """ + self.fh.write(f" IODSSR:{self.iodssr} ProvID:{self.pid} " + + f"SolID:{self.sid}\n") + self.fh.write(" {:s}\t{:s}\n".format("SatID", "ura [m]")) + + for k, sat_ in enumerate(self.lc[0].ura): + sys_, _ = sat2prn(sat_) + if sys_ != sys: + continue + self.fh.write(" {:s}\t{:8.4f}\n".format(sat2id(sat_), + self.lc[0].ura[sat_])) + def out_log(self, obs=None, eph=None, geph=None, seph=None): """ output ssr message to log file """ sys = -1 @@ -1235,7 +1243,8 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): time2str(self.time))) if sys > 0 and self.subtype not in [sRTCM.SSR_META, sRTCM.SSR_GRID]: - self.fh.write(f" Update Interval: {self.udint_t[self.uint]}[s]") + j = self.sc_t[self.subtype] + self.fh.write(f" Update Interval: {self.udint_t[self.udi[j]]}[s]") self.fh.write(f" MultiMsg: {self.mi}\n") @@ -1249,6 +1258,9 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.out_log_ssr_clk(sys) self.out_log_ssr_orb(sys) + if self.subtype == sCSSR.URA: + self.out_log_ssr_ura(sys) + if self.subtype == sRTCM.SSR_META: self.fh.write(f" ProvID:{self.pid} SolID:{self.sid} " + f"NumEnt:{self.nm}\n") @@ -1257,22 +1269,28 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write(f" IODSSR:{self.iodssr} ProvID:{self.pid} " + f"SolID:{self.sid} GridID:{self.gid}\n") self.fh.write(f" Residiaul Model Indicator: {self.rmi}\n") + + ah, bh, ch = self.lc[inet].maph + aw, bw, cw = self.lc[inet].mapw + ct = self.lc[inet].ct + self.fh.write(f" a(h/w): {ah:7.4f} {aw:7.4f}\n") + self.fh.write(f" b(h/w): {bh:7.4f} {bw:7.4f}\n") + self.fh.write(f" c(h/w): {ch:7.4f} {cw:7.4f}\n") + self.fh.write(f" c00(h/w)[m]: {ct[0, 0]:7.4f}" + + f" {ct[1, 0]:7.4f}\n") + self.fh.write(f" c10(h/w)[mm/deg]: {ct[0, 2]*1e3:7.4f}" + + f" {ct[1, 2]*1e3:7.4f}\n") + self.fh.write(f" c01(h/w)[mm/deg]: {ct[0, 1]*1e3:7.4f}" + + f" {ct[1, 1]*1e3:7.4f}\n") + if self.rmi: - trop = self.lc[inet].trop - self.fh.write(f" a(d/w): {trop.ah:7.4f} {trop.aw:7.4f}\n") - self.fh.write(f" b(d/w): {trop.bh:7.4f} {trop.bw:7.4f}\n") - self.fh.write(f" c(d/w): {trop.ch:7.4f} {trop.cw:7.4f}\n") - self.fh.write(f" ofst(d/w)[m]: {trop.ofsth:7.4f}" - + f" {trop.ofstw:7.4f}\n") - self.fh.write(f" ge(d/w)[mm/deg]: {trop.geh*1e3:7.4f}" - + f" {trop.gew*1e3:7.4f}\n") - self.fh.write(f" gn(d/w)[mm/deg]: {trop.gnh*1e3:7.4f}" - + f" {trop.gnw*1e3:7.4f}\n") - - ngp = trop.rh.shape[0] - for k in range(ngp): - self.fh.write(f" res(d/w)[m] {trop.rh[k]:7.4f}" + - f" {trop.rw[k]:7.4f}\n") + dth = self.lc[inet].dth + dtw = self.lc[inet].dtw + ng = self.lc[inet].ng + ofst = self.lc[inet].ofst + for k in range(ofst, ofst+ng): + self.fh.write(f" res(d/w)[m] {dth[k]:7.4f}" + + f" {dtw[k]:7.4f}\n") if self.subtype == sRTCM.SSR_STEC: self.fh.write(f" IODSSR:{self.iodssr} ProvID:{self.pid} " + @@ -1282,18 +1300,19 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): for sat in self.lc[inet].sat_n: self.fh.write(f" {sat2id(sat):s}") ci = self.lc[inet].ci[sat] - self.fh.write(f" C00 {ci[0]:7.4f}") - if self.pgi: - self.fh.write(f" C01 {ci[1]:7.4f} C10 {ci[2]:7.4f}") + self.fh.write(f" c00 {ci[0]:7.4f}") + if self.lc[inet].stype[sat] > 0: + self.fh.write(f" c01 {ci[1]:7.4f} c10 {ci[2]:7.4f}") self.fh.write("\n") self.fh.write(f" Residual[TECU]: {self.rmi}\n") if self.rmi: for sat in self.lc[inet].sat_n: self.fh.write(f" {sat2id(sat):s}") - stec = self.lc[inet].stec[sat] - ngp = len(stec) - for k in range(ngp): - self.fh.write(f" {stec[k]:7.4f}") + dstec = self.lc[inet].dstec[sat] + ng = self.lc[inet].ng + ofst = self.lc[inet].ofst + for k in range(ofst, ofst+ng): + self.fh.write(f" {dstec[k]:7.4f}") self.fh.write("\n") if self.subtype == sRTCM.SSR_GRID: @@ -2242,25 +2261,34 @@ def decode_ssr_metadata(self, msg, i): # Model/Correction Part # Model/Correction Type Indicator: DF+64 - # Model/Correction Application Indicator: MT+65 - # Non-Default Model/Correction Indicator: MT+66 + # 1:satellite antenna PCV, 2:satellite antenna GDV + # 3:solid earth tides, 4:ocean loading, 5:pole tides, 6:relatively + # 7:GNSS BE reference + + # Model/Correction Application Indicator: DF+65 0:not applied,1:applied + # Non-Default Model/Correction Indicator: DF+66 0:default,1:non default mt, ap, mi = bs.unpack_from('u5u1b1', msg, i) i += 7 if mi: - mci = bs.unpack_from('u3', msg, i)[0] # model/correction id + # non-default model/correction id DF+67 + mci = bs.unpack_from('u3', msg, i)[0] i += 3 - iodmi = bs.unpack_from('b1', msg, i)[0] + iodmi = bs.unpack_from('b1', msg, i)[0] # DF+68 i += 1 if iodmi: + # DF+69 model/correction data IOD iodm = bs.unpack_from('u6', msg, i)[0] i += 6 if mi: + # DF+070 1:additional model parameter present mpi = bs.unpack_from('b1', msg, i)[0] i += 1 if mpi: + # number of additional model parameter bits DF+071 nb = bs.unpack_from('u8', msg, i)[0] i += 8 if mi and mpi: + # aditional model parameter DF+072 data = bs.unpack_from('u'+str(nb), msg, i)[0] i += nb @@ -2411,7 +2439,7 @@ def decode_ssr_grid(self, msg, i): return i - def decode_ssr_pbias_ex(self, msg, i): + def decode_ssr_pbias_ex(self, msg, i, inet=0): """ decode SSR Extended Satellite Phase Bias Message """ """ @@ -2430,76 +2458,91 @@ def decode_ssr_pbias_ex(self, msg, i): sys = self.get_ssr_sys(self.msgtype) i, v = self.decode_head(msg, i, sys) - nsig = 0 # TBD - # Extended Phase Bias Property ID - self.eid = bs.unpack_from('u4', msg, i)[0] # DF+002 + iexpb = bs.unpack_from('u4', msg, i)[0] # DF+002 i += 4 - self.lc[0].wl = np.zeros(nsig) - for j in range(nsig): - self.lc[0].wl[j] = bs.unpack_from('u2', msg, i)[0] - i += 2 + if iexpb != self.iexpb: + return -1 + + self.lc[inet].wl = {} + for sat_ in self.lc[inet].pbias.keys(): + self.lc[inet].wl[sat_] = {} + for rsig in self.lc[inet].pbias[sat_].keys(): + wl = bs.unpack_from('u2', msg, i)[0] + i += 2 + self.lc[inet].wl[sat_][rsig] = wl + return i def decode_ssr_trop(self, msg, i, inet=0): """ decode SSR Tropspheric Correction Message """ - tow, uint, mi = bs.unpack_from('u20u4u1', msg, i) - i += 25 - iodssr, pid, sid = bs.unpack_from('u4u16u4', msg, i) - i += 24 + i, v = self.decode_head(msg, i) + # Grid ID: DF+022 + # Residual model indicator DF+009 gid, rmi = bs.unpack_from('u10b1', msg, i) i += 11 + inet = gid # atmospheric model part dah, dbh, dch, daw, dbw, dcw = bs.unpack_from( 's11s9s9s13s6s5', msg, i) i += 53 - ofsth, geh, gnh = bs.unpack_from('s13s15s15', msg, i) + ct00h, ct10h, ct01h = bs.unpack_from('s13s15s15', msg, i) i += 43 - ofstw, gew, gnw = bs.unpack_from('s13s15s15', msg, i) + ct00w, ct10w, ct01w = bs.unpack_from('s13s15s15', msg, i) i += 43 - inet = gid - - trop = TropModel() # mapping function for hydrostatic/wet term - trop.ah = self.sval(dah, 11, 2.5e-7)+0.00118 - trop.bh = self.sval(dbh, 9, 5e-6)+0.00298 - trop.ch = self.sval(dch, 9, 2e-4)+0.0682 - trop.aw = self.sval(daw, 13, 1e-6)+0.000104 - trop.bw = self.sval(dbw, 6, 2.5e-5)+0.0015 - trop.cw = self.sval(dcw, 5, 2e-3)+0.048 - trop.aht = 2.53e-5 - trop.bht = 5.49e-3 - trop.cht = 1.14e-3 + ah = self.sval(dah, 11, 2.5e-7)+0.00118 + bh = self.sval(dbh, 9, 5e-6)+0.00298 + ch = self.sval(dch, 9, 2e-4)+0.0682 + aw = self.sval(daw, 13, 1e-6)+0.000104 + bw = self.sval(dbw, 6, 2.5e-5)+0.0015 + cw = self.sval(dcw, 5, 2e-3)+0.048 + + ct = np.zeros((2, 4)) # hydrostatic - trop.ofsth = ofsth*0.1e-3+2.3 # offset [m] - trop.geh = geh*0.01e-3 # west-east gradient [m/deg] - trop.gnh = gnh*0.01e-3 # south-north gradient [m/deg] + ct[0, 0] = self.sval(ct00h, 13, 0.1e-3)+2.3 # offset c00 [m] + # south-north gradient c01 [m/deg] + ct[0, 1] = self.sval(ct01h, 15, 0.01e-3) + # west-east gradient c10 [m/deg] + ct[0, 2] = self.sval(ct10h, 15, 0.01e-3) + # wet - trop.ofstw = ofstw*0.1e-3+0.252 # offset [m] - trop.gew = gew*0.01e-3 # west-east gradient [m/deg] - trop.gnw = gnw*0.01e-3 # south-north gradient [m/deg] + ct[1, 0] = self.sval(ct00w, 13, 0.1e-3)+0.252 # offset c00 [m] + # south-north gradient c01 [m/deg] + ct[1, 1] = self.sval(ct01w, 15, 0.01e-3) + # west-east gradient c10 [m/deg] + ct[1, 2] = self.sval(ct10w, 15, 0.01e-3) if rmi: # residual information part - ofstg, ngp, n, m = bs.unpack_from('u12u12u4u4', msg, i) + ofst, ng, n, m = bs.unpack_from('u12u12u4u4', msg, i) i += 32 - trop.rh = np.zeros(ofstg+ngp) - trop.rw = np.zeros(ofstg+ngp) + rh = np.zeros(ofst+ng) + rw = np.zeros(ofst+ng) - for k in range(ofstg, ofstg+ngp): - rh, rw = bs.unpack_from(f's{n}s{m}', msg, i) + for k in range(ofst, ofst+ng): + rh_, rw_ = bs.unpack_from(f's{n}s{m}', msg, i) i += n+m # hydrostatic grid point residual [m] - trop.rh[k] = self.sval(rh, n, 0.1e-3) + rh[k] = self.sval(rh_, n, 0.1e-3) # wet grid point residual [m] - trop.rw[k] = self.sval(rw, m, 0.1e-3) + rw[k] = self.sval(rw_, m, 0.1e-3) - self.lc[inet].trop = trop + # functional term parameters + self.lc[inet].ct = ct + # mapping function parameters + self.lc[inet].maph = np.array([ah, bh, ch]) + self.lc[inet].mapw = np.array([aw, bw, cw]) + self.lc[inet].dth = rh + self.lc[inet].dtw = rw - self.iodssr = iodssr + self.lc[inet].ng = ng + self.lc[inet].ofst = ofst + + self.iodssr = v['iodssr'] self.rmi = rmi return i @@ -2527,6 +2570,7 @@ def decode_ssr_iono(self, msg, i, inet=0): i += 1 sz = 3 if self.pgi else 1 self.lc[inet].ci = {} + self.lc[inet].stype = {} for k in range(nsat): sat_ = sat[k] self.lc[inet].ci[sat_] = np.zeros(sz) @@ -2534,10 +2578,14 @@ def decode_ssr_iono(self, msg, i, inet=0): i += 17 self.lc[inet].ci[sat_][0] = self.sval(c00, 17, 0.01) if self.pgi: # polynomial grandient indicator is true + self.lc[inet].stype[sat_] = 1 + c01, c10 = bs.unpack_from('s18s18', msg, i) i += 36 self.lc[inet].ci[sat_][1] = self.sval(c01, 18, 1e-3) self.lc[inet].ci[sat_][2] = self.sval(c10, 18, 1e-3) + else: + self.lc[inet].stype[sat_] = 0 if rmi: # residual model information part ofst, ng, rlen = bs.unpack_from('u12u12u5', msg, i) @@ -2551,16 +2599,19 @@ def decode_ssr_iono(self, msg, i, inet=0): else: scl = 1.0 - self.lc[inet].stec = {} + self.lc[inet].dstec = {} fmt = 's'+str(rlen) for k in range(nsat): sat_ = sat[k] - self.lc[inet].stec[sat_] = np.zeros(ofst+ng) + self.lc[inet].dstec[sat_] = np.zeros(ofst+ng) for j in range(ofst, ofst+ng): res = bs.unpack_from(fmt, msg, i)[0] # DF+062 i += rlen - self.lc[inet].stec[sat_][j] = self.sval(res, rlen, scl) + self.lc[inet].dstec[sat_][j] = self.sval(res, rlen, scl) + + self.lc[inet].ng = ng + self.lc[inet].ofst = ofst self.gid = gid self.pmi = pmi @@ -2670,7 +2721,7 @@ def decode_integrity_ext(self, msg, i): self.integ.pid = pid self.integ.tow = tow self.integ.ilvl = ilvl - self.intrg.stype = stype + self.integ.stype = stype # GNSS Constellation Mask DFi013 # Validity Period DFi065 (0-15) @@ -2711,6 +2762,8 @@ def decode_integrity_ext(self, msg, i): self.integ.tau_p = {} self.integ.tau_c = {} + self.integ.sig_p = {} + self.integ.sig_c = {} self.integ.tau_cp = {} self.integ.tau_cc = {} @@ -2746,15 +2799,18 @@ def decode_integrity_ext(self, msg, i): self.integ.tau_p[sys] = tau_p self.integ.tau_c[sys] = tau_c + + self.integ.sig_p[sys] = sig_p + self.integ.sig_c[sys] = sig_c + self.integ.tau_cp[sys] = tau_cp self.integ.tau_cc[sys] = tau_cc - svid_t, nsat = self.decode_mask(mask_sat, 64) + ofst = 193 if sys == uGNSS.QZS else 1 + prn_t, nsat = self.decode_mask(mask_sat, 64, ofst=ofst) # satellite-specific part - for svid in svid_t: - ofst = 192 if sys == uGNSS.QZS else 0 - prn = svid+ofst + for prn in prn_t: sat = prn2sat(sys, prn) # Single Satellite Pseudorange Augmentation Message Fault # Probability DFi046 @@ -2816,7 +2872,8 @@ def decode_integrity_ext_sis_local(self, msg, i): mask_sat, iod_mask = bs.unpack_from('u64u2', msg, i) i += 66 - svid_t, nsat = self.decode_mask(mask_sat, 64) + ofst = 193 if sys == uGNSS.QZS else 1 + prn_t, nsat = self.decode_mask(mask_sat, 64, ofst=ofst) self.integ.sig_pd[sys] = {} self.integ.sig_cd[sys] = {} @@ -2830,9 +2887,7 @@ def decode_integrity_ext_sis_local(self, msg, i): self.integ.sig_trp[sys] = {} self.integ.idx_trp[sys] = {} - for svid in svid_t: - ofst = 192 if sys == uGNSS.QZS else 0 - prn = svid+ofst + for prn in prn_t: sat = prn2sat(sys, prn) # Bound on rate of change of PR error stdev DFi120 @@ -2941,13 +2996,22 @@ def decode_integrity_ext_service_area(self, msg, i): f_ir, f_ttd, f_ext, f_exs = bs.unpack_from('u2u2u3u3', msg, i) i += 10 - self.integ.iod_sa = iod_sa self.integ.tow = tow + self.integ.iod_sa = iod_sa self.integ.pid = pid + self.integ.aid = area self.integ.vp = vp self.integ.uri = uri + self.integ.atype = atype + self.integ.cf = cf + self.integ.seq = seq - if atype == 1: # Validity Area Parameters defined in Table 10.3-5 + self.integ.f_ir = f_ir + self.integ.f_ttd = f_ttd + self.integ.f_ext = f_ext + self.integ.f_exs = f_exs + + if atype == 1: # Validity Area Parameters # number of area points DFi201 narea = bs.unpack_from('u8', msg, i)[0] i += 8 @@ -2959,7 +3023,7 @@ def decode_integrity_ext_service_area(self, msg, i): i += 69 self.integ.pos[k, :] = [lat, lon, 0] - elif atype == 0: # Validity Radius Data defined in Table 10.3-6 + elif atype == 0: # Validity Radius Data # Area Point - Lat DFi202 # Area Point - Lon DFi203 # Validity Radius DF057 @@ -2998,7 +3062,7 @@ def decode_integrity_quality(self, msg, i): return i def decode_integrity_cnr_acg(self, msg, i): - """ RTCM SC-134 CNR/ACG Signal In Space Monitoring Message (MT8, 2091) """ + """ RTCM SC-134 CNR/ACG SIS Monitoring Message (MT8, 2091) """ # augmentation service provider id DFi027 # GPS Epoch Time (TOW) DFi008 @@ -3013,9 +3077,11 @@ def decode_integrity_cnr_acg(self, msg, i): self.integ.uri = uri # constellation specific part - sys_t, nsys = self.decode_mask(mask_c, 12, ofst=0) + sys_t, nsys = self.decode_mask(mask_c, 16, ofst=0) + + for sys_ in sys_t: + sys = self.integ.sys_tbl[sys_] - for sys in sys_t: self.integ.cnr[sys] = {} self.integ.agc_t[sys] = {} self.integ.agc[sys] = {} @@ -3023,12 +3089,18 @@ def decode_integrity_cnr_acg(self, msg, i): mask_s, iod_sm = bs.unpack_from('u64u2', msg, i) i += 66 - sat_t, nsat = self.decode_mask(mask_s, 64, ofst=1) + ofst = 193 if sys == uGNSS.QZS else 1 + prn_t, nsat = self.decode_mask(mask_s, 64, ofst=ofst) + + for prn in prn_t: + sat = prn2sat(sys, prn) + + self.integ.cnr[sys][sat] = {} + self.integ.agc_t[sys][sat] = {} + self.integ.agc[sys][sat] = {} - for sat in sat_t: mask_f, iod_fm = bs.unpack_from('u8u2', msg, i) i += 10 - sig_t, nsig = self.decode_mask(mask_f, 8, ofst=0) for sig in sig_t: @@ -3441,52 +3513,342 @@ def __init__(self): uGNSS.QZS: 1044, uGNSS.GAL: 1046 } - def set_sync(self, msg, k): - msg[k] = 0xd3 + self.sc_t = {sCSSR.ORBIT: sCType.ORBIT, sCSSR.CLOCK: sCType.CLOCK, + sCSSR.MASK: sCType.MASK, sCSSR.CBIAS: sCType.CBIAS, + sCSSR.PBIAS: sCType.PBIAS, sCSSR.URA: sCType.URA, + sCSSR.GRID: sCType.TROP, sCSSR.STEC: sCType.STEC} - def set_len(self, msg, k, len_): - st.pack_into('>H', msg, k+1, len_ & 0x3ff) - self.len = len_ - self.dlen = len_+6 - return len_ + def get_ssr_sys(self, msgtype): + """ get system from ssr message type """ + if msgtype == 4076: + return self.sysref + else: + if msgtype >= 1057 and msgtype < 1063: + return uGNSS.GPS + elif msgtype >= 1063 and msgtype < 1069: + return uGNSS.GLO + elif msgtype >= 1240 and msgtype < 1246: + return uGNSS.GAL + elif msgtype >= 1246 and msgtype < 1252: + return uGNSS.QZS + elif msgtype >= 1252 and msgtype < 1258: + return uGNSS.SBS + elif msgtype >= 1258 and msgtype < 1264: + return uGNSS.BDS + elif msgtype >= 1265 and msgtype < 1271: # proposed phase bias + tbl_t = {1265: uGNSS.GPS, 1266: uGNSS.GLO, 1267: uGNSS.GAL, + 1268: uGNSS.QZS, 1269: uGNSS.SBS, 1270: uGNSS.BDS} + return tbl_t[msgtype] + # RTCM SSR test messages + elif msgtype >= 41 and msgtype < 47: # OBC + return uGNSS.GLO + elif msgtype in [62, 65, 68, 71, 74, 77]: # OBC + return uGNSS.GAL + elif msgtype in [63, 66, 69, 72, 75, 78]: # OBC + return uGNSS.BDS + elif msgtype in [64, 67, 70, 73, 76, 79]: # OBC + return uGNSS.QZS + elif msgtype >= 80 and msgtype < 85: # proposed satellite antenna + tbl_t = {80: uGNSS.GPS, 81: uGNSS.GLO, 82: uGNSS.GAL, + 83: uGNSS.BDS, 84: uGNSS.QZS} + return tbl_t[msgtype] + elif msgtype >= 85 and msgtype < 90: # proposed phase bias + tbl_t = {85: uGNSS.GPS, 86: uGNSS.GLO, 87: uGNSS.GAL, + 88: uGNSS.BDS, 89: uGNSS.QZS} + return tbl_t[msgtype] + elif msgtype >= 90 and msgtype < 95: # proposed phase bias + tbl_t = {90: uGNSS.GPS, 91: uGNSS.GLO, 92: uGNSS.GAL, + 93: uGNSS.BDS, 94: uGNSS.QZS} + return tbl_t[msgtype] + elif msgtype >= 96 and msgtype < 101: # regional iono + tbl_t = {96: uGNSS.GPS, 97: uGNSS.GLO, 98: uGNSS.GAL, + 99: uGNSS.BDS, 100: uGNSS.QZS} + elif msgtype in [60, 61, 95]: # METADATA, grid definition, trop + return uGNSS.NONE + elif msgtype in [11, 12, 13]: # RTCM SC-134 test messages + return uGNSS.NONE + else: + print(f"definition of {msgtype} is missing") + # return uGNSS.NONE - def set_checksum(self, msg, k, maxlen=0): - if self.len < 6: - return False - if maxlen > 0 and k+self.len >= maxlen: - return False - cs = Crc24LteA.calc(msg[k:k+self.len+3]) - st.pack_into('>H', msg, k+self.len+3, cs >> 8) - st.pack_into('>B', msg, k+self.len+5, cs & 0xff) - return cs + return tbl_t[msgtype] - def set_body(self, msg, buff, k, len_): - msg[k+3:k+len_] = buff[3:len_] + def rsig2code(self, rsig): + """ convert rSigRnx to RTCM SSR code """ + gps_tbl = { + 0: uSIG.L1C, + 1: uSIG.L1P, + 2: uSIG.L1W, + 5: uSIG.L2C, + 6: uSIG.L2D, + 7: uSIG.L2S, + 8: uSIG.L2L, + 9: uSIG.L2X, + 10: uSIG.L2P, + 11: uSIG.L2W, + 14: uSIG.L5I, + 15: uSIG.L5Q, + 16: uSIG.L5X, + 17: uSIG.L1S, + 18: uSIG.L1L, + 19: uSIG.L1X, + } + glo_tbl = { + 0: uSIG.L1C, + 1: uSIG.L1P, + 2: uSIG.L2C, + 3: uSIG.L2P, + 4: uSIG.L4A, + 5: uSIG.L4B, + 6: uSIG.L6A, + 7: uSIG.L6B, + 10: uSIG.L3I, + 11: uSIG.L3Q, + } - def round_i(self, v): - return int(v+0.5) + gal_tbl = { + 0: uSIG.L1A, + 1: uSIG.L1B, + 2: uSIG.L1C, + 3: uSIG.L1X, + 4: uSIG.L1Z, + 5: uSIG.L5I, + 6: uSIG.L5Q, + 7: uSIG.L5X, + 8: uSIG.L7I, + 9: uSIG.L7Q, + 10: uSIG.L7X, + 11: uSIG.L8I, + 12: uSIG.L8Q, + 13: uSIG.L8X, + 14: uSIG.L6A, + 15: uSIG.L6B, + 16: uSIG.L6C, + 17: uSIG.L6X, + 18: uSIG.L6Z, + } - def is_msmtype(self, msgtype): - """ check if the message type is MSM """ - for sys_ in self.msm_t.keys(): - if msgtype >= self.msm_t[sys_] and msgtype <= self.msm_t[sys_]+6: - return True - return False + bds_tbl = { + 0: uSIG.L2I, + 1: uSIG.L2Q, + 2: uSIG.L2X, + 3: uSIG.L6I, + 4: uSIG.L6Q, + 5: uSIG.L6X, + 6: uSIG.L7I, + 7: uSIG.L7Q, + 8: uSIG.L7X, + 9: uSIG.L1D, + 10: uSIG.L1P, + 11: uSIG.L1X, + 12: uSIG.L5D, + 13: uSIG.L5P, + 14: uSIG.L5X, + 15: uSIG.L1A, + } - def msmtype(self, msgtype): - """ get system and msm type from message type """ - sys = uGNSS.NONE - msm = 0 - for sys_ in self.msm_t.keys(): - if msgtype >= self.msm_t[sys_] and msgtype <= self.msm_t[sys_]+6: - sys = sys_ - msm = msgtype-self.msm_t[sys_]+1 - break - return sys, msm + qzs_tbl = { + 0: uSIG.L1C, + 1: uSIG.L1S, + 2: uSIG.L1C, + 3: uSIG.L2S, + 4: uSIG.L2L, + 5: uSIG.L2X, + 6: uSIG.L5I, + 7: uSIG.L5Q, + 8: uSIG.L5X, + 9: uSIG.L6S, + 10: uSIG.L6L, + 11: uSIG.L6X, + 12: uSIG.L1X, + 17: uSIG.L6E, + 19: uSIG.L1E, # TBD + } - def encode_msm_time(self, sys, time): - """ decode msm time """ - if sys == uGNSS.GLO: + sbs_tbl = { + 0: uSIG.L1C, + 1: uSIG.L5I, + 2: uSIG.L5Q, + 3: uSIG.L5X, + } + + usig_tbl_ = { + uGNSS.GPS: gps_tbl, + uGNSS.GLO: glo_tbl, + uGNSS.GAL: gal_tbl, + uGNSS.BDS: bds_tbl, + uGNSS.QZS: qzs_tbl, + uGNSS.SBS: sbs_tbl, + } + + usig_tbl = usig_tbl_[rsig.sys] + dic = {usig_tbl[s]: s for s in usig_tbl} + + return dic[rsig.sig] + + def msm2rsig(self, sys: uGNSS, utyp: uTYP, ssig): + """ convert ssig to rSigRnx for MSM """ + gps_tbl = { + 2: uSIG.L1C, + 3: uSIG.L1P, + 4: uSIG.L1W, + 8: uSIG.L2C, + 9: uSIG.L2P, + 10: uSIG.L2W, + 15: uSIG.L2S, + 16: uSIG.L2L, + 17: uSIG.L2X, + 22: uSIG.L5I, + 23: uSIG.L5Q, + 24: uSIG.L5X, + 30: uSIG.L1S, + 31: uSIG.L1L, + 32: uSIG.L1X, + } + glo_tbl = { + 2: uSIG.L1C, + 3: uSIG.L1P, + 8: uSIG.L2C, + 9: uSIG.L2P, + 10: uSIG.L4A, + 11: uSIG.L4B, + 12: uSIG.L4X, + 13: uSIG.L6A, + 14: uSIG.L6X, + 15: uSIG.L3I, + 16: uSIG.L3Q, + 17: uSIG.L3X, + } + + gal_tbl = { + 2: uSIG.L1C, + 3: uSIG.L1A, + 4: uSIG.L1B, + 5: uSIG.L1X, + 6: uSIG.L1Z, + 8: uSIG.L6C, + 9: uSIG.L6A, + 10: uSIG.L6B, + 11: uSIG.L6X, + 12: uSIG.L6Z, + 14: uSIG.L7I, + 15: uSIG.L7Q, + 16: uSIG.L7X, + 18: uSIG.L8I, + 19: uSIG.L8Q, + 20: uSIG.L8X, + 22: uSIG.L5I, + 23: uSIG.L5Q, + 24: uSIG.L5X, + } + + bds_tbl = { + 2: uSIG.L2I, + 3: uSIG.L2Q, + 4: uSIG.L2X, + 8: uSIG.L6I, + 9: uSIG.L6Q, + 10: uSIG.L6X, + 14: uSIG.L7I, + 15: uSIG.L7Q, + 16: uSIG.L7X, + 22: uSIG.L5D, + 23: uSIG.L5P, + 24: uSIG.L5X, + 25: uSIG.L7D, + 30: uSIG.L1D, + 31: uSIG.L1P, + 32: uSIG.L1X, + } + + qzs_tbl = { + 2: uSIG.L1C, + 3: uSIG.L1E, + 9: uSIG.L6S, + 10: uSIG.L6L, + 11: uSIG.L6X, + 15: uSIG.L2S, + 16: uSIG.L2L, + 17: uSIG.L2X, + 22: uSIG.L5I, + 23: uSIG.L5Q, + 24: uSIG.L5X, + 30: uSIG.L1S, + 31: uSIG.L1L, + 32: uSIG.L1X, + } + + sbs_tbl = { + 2: uSIG.L1C, + 22: uSIG.L5I, + 23: uSIG.L5Q, + 24: uSIG.L5X, + } + + irn_tbl = { + 8: uSIG.L9A, + 22: uSIG.L5A, + } + + usig_tbl_ = { + uGNSS.GPS: gps_tbl, + uGNSS.GLO: glo_tbl, + uGNSS.GAL: gal_tbl, + uGNSS.BDS: bds_tbl, + uGNSS.QZS: qzs_tbl, + uGNSS.SBS: sbs_tbl, + uGNSS.IRN: irn_tbl, + } + + usig_tbl = usig_tbl_[sys] + return rSigRnx(sys, utyp, usig_tbl[ssig]) + + def set_sync(self, msg, k): + msg[k] = 0xd3 + + def set_len(self, msg, k, len_): + st.pack_into('>H', msg, k+1, len_ & 0x3ff) + self.len = len_ + self.dlen = len_+6 + return len_ + + def set_checksum(self, msg, k, maxlen=0): + if self.len < 6: + return False + if maxlen > 0 and k+self.len >= maxlen: + return False + cs = Crc24LteA.calc(msg[k:k+self.len+3]) + st.pack_into('>H', msg, k+self.len+3, cs >> 8) + st.pack_into('>B', msg, k+self.len+5, cs & 0xff) + return cs + + def set_body(self, msg, buff, k, len_): + msg[k+3:k+len_] = buff[3:len_] + + def round_i(self, v): + return int(v+0.5) + + def is_msmtype(self, msgtype): + """ check if the message type is MSM """ + for sys_ in self.msm_t.keys(): + if msgtype >= self.msm_t[sys_] and msgtype <= self.msm_t[sys_]+6: + return True + return False + + def msmtype(self, msgtype): + """ get system and msm type from message type """ + sys = uGNSS.NONE + msm = 0 + for sys_ in self.msm_t.keys(): + if msgtype >= self.msm_t[sys_] and msgtype <= self.msm_t[sys_]+6: + sys = sys_ + msm = msgtype-self.msm_t[sys_]+1 + break + return sys, msm + + def encode_msm_time(self, sys, time): + """ decode msm time """ + if sys == uGNSS.GLO: time = timeadd(gpst2utc(time), 10800.0) week, tow = time2gpst(time) dow = int(tow/86400.0) # day of week @@ -3681,6 +4043,319 @@ def encode_integrity_ssr(self, msg, i): return i + def encode_ssr_metadata(self, msg, i): + """ encode SSR meta-information message (MT60) """ + + # nm: nomber of ssr model/correction entries + bs.pack_into('u4u16u4u5', msg, i, self.iodssr, self.pid, + self.sid, self.nm) + i += 29 + + if self.nm == 0: # no metadata + i += 7 + return i + + # TBD + return i + + def encode_ssr_grid(self, msg, i): + """ encode SSR grid definition message (MT61) """ + + bs.pack_into('u16b1u10u3', msg, i, self.pid, + self.mi, self.gid, self.gtype) + i += 30 + + self.inet = self.gid + + if self.gtype == 0: # grid type 0 + grid = self.grid[self.grid['nid'] == self.gid] + self.ng = grid.shape[0] + + gid, ofst, lat, lon, alt = grid[0] + + lat_i = int(lat/1e-3) + lon_i = int(lon/1e-3) + alt_i = int((alt+1000)/12.5) + + bs.pack_into('s18s19u10u12u8', msg, i, lat_i, lon_i, alt_i, + self.ofst, self.ng) + i += 67 + + for k in range(self.ng): + gid, ofst, lat_, lon_, alt_ = grid[k] + dlat = lat_-lat + dlon = lon_-lon + dalt = alt_-alt + lat = lat_ + lon = lon_ + alt = alt_ + + dlat_i = int(dlat/1e-3) + dlon_i = int(dlon/1e-3) + dalt_i = int(dalt/12.5) + + bs.pack_into('s13s14s9', msg, i, dlat_i, dlon_i, dalt_i) + i += 36 + + elif self.gtype == 1: # grid type 1 + grid = self.grid(self.grid['nid'] == self.inet) + + gid, ofst, lat, lon, alt = grid[0] + + lat_ = int(lat/1e-3) + lon_ = int(lon/1e-3) + + bs.pack_into('s18s19u12u8', msg, i, lat_, lon_, self.ofst, self.ng) + i += 57 + + for k in range(self.ng): + gid, ofst, lat, lon, _ = grid[k] + dlat_ = lat-lat_ + dlon_ = lon-lon_ + lat_ = lat + lon_ = lon + + dlat = int(dlat_/1e-3) + dlon = int(dlon_/1e-3) + bs.pack_into('s13s14', msg, i, dlat, dlon) + i += 27 + + return i + + def encode_head(self, msg, i, sys=uGNSS.NONE): + """ encode the header of ssr message """ + if self.msgtype == 4076 or sys != uGNSS.GLO: + blen = 20 + else: + blen = 17 + bs.pack_into('u'+str(blen), msg, i, self.tow) + i += blen + + if self.subtype not in [sRTCM.SSR_PBIAS_EX]: + bs.pack_into('u4b1', msg, i, self.udi[self.sc_t[self.subtype]], + self.mi) + i += 5 + + if self.subtype in (sCSSR.ORBIT, sCSSR.COMBINED): + bs.pack_into('u1', msg, i, self.datum) + i += 1 + + bs.pack_into('u4u16u4', msg, i, self.iodssr, self.pid, self.sid) + i += 24 + + if self.subtype == sCSSR.PBIAS: + bs.pack_into('u1u1', msg, i, self.ci, self.mw) + i += 2 + elif self.subtype == sRTCM.SSR_PBIAS: + # Satellite Yaw Information Indicator DF486 + # Extended Phase Bias Property ID DF+2 + bs.pack_into('b1u4', msg, i, self.iyaw, self.iexpb) + i += 5 + + if self.subtype not in [sCSSR.VTEC, sRTCM.SSR_PBIAS, sRTCM.SSR_STEC, + sRTCM.SSR_TROP]: + nsat = 0 + for sat in self.sat_n: + sys_, _ = sat2prn(sat) + if sys_ != sys: + continue + nsat += 1 + self.nsat_n = nsat + + bs.pack_into('u6', msg, i, nsat) + i += 6 + + return i + + def encode_sat(self, msg, i, sat): + """ encode satellite id """ + + sys, prn = sat2prn(sat) + svid = prn + + if self.msgtype == 4076: + blen = 6 + else: + if sys == uGNSS.QZS: + blen = 4 + svid = prn-192 + else: + blen = 6 + + bs.pack_into('u'+str(blen), msg, i, svid) + i += blen + + return i + + def encode_orb_sat(self, msg, i, sat, inet=0): + """ encode orbit correction of cssr """ + sys, _ = sat2prn(sat) + + if self.msgtype == 4076: + blen = 8 + else: + if sys == uGNSS.GAL: + blen = 10 + elif sys == uGNSS.SBS: + blen = 24 + else: + blen = 8 + bs.pack_into('u'+str(blen), msg, i, self.lc[inet].iode[sat]) + i += blen + + dx = int(self.lc[inet].dorb[sat][0]/0.1e-3) + dy = int(self.lc[inet].dorb[sat][1]/0.4e-3) + dz = int(self.lc[inet].dorb[sat][2]/0.4e-3) + + bs.pack_into('s22s20s20', msg, i, dx, dy, dz) + i += 62 + + if self.lc[inet].ddorb is not None: + ddx = int(self.lc[inet].ddorb[sat][0]/1e-6) + ddy = int(self.lc[inet].ddorb[sat][1]/4e-6) + ddz = int(self.lc[inet].ddorb[sat][2]/4e-6) + else: + ddx, ddy, ddz = 0, 0, 0 + + bs.pack_into('s21s19s19', msg, i, ddx, ddy, ddz) + i += 59 + + return i + + def encode_clk_sat(self, msg, i, sat, inet=0): + """ encoder clock correction of cssr """ + + dclk = int(self.lc[inet].dclk[sat]/0.1e-3) + if self.lc[inet].ddclk is not None: + ddclk = int(self.lc[inet].ddclk[sat]/0.4e-3) + dddclk = int(self.lc[inet].dddclk[sat]/4e-6) + else: + ddclk, dddclk = 0, 0 + + bs.pack_into('s22s21s27', msg, i, dclk, ddclk, dddclk) + i += 70 + return i + + def encode_hclk_sat(self, msg, i, sat, inet=0): + """ encoder high-rate clock correction of cssr """ + if self.lc[inet].hclk is not None: + hclk = int(self.lc[inet].hclk[sat]/0.1e-3) + else: + hclk = 0 + + bs.pack_into('s22', msg, i, hclk) + i += 22 + return i + + def encode_cssr_orb(self, msg, i, inet=0): + """ encode RTCM SSR Orbit Correction message """ + sys = self.get_ssr_sys(self.msgtype) + i = self.encode_head(msg, i, sys) + + for sat in self.sat_n: + sys_, _ = sat2prn(sat) + if sys_ != sys: + continue + i = self.encode_sat(msg, i, sat) + i = self.encode_orb_sat(msg, i, sat) + + return i + + def encode_cssr_clk(self, msg, i, inet=0): + """encode RTCM SSR Clock Correction message """ + sys = self.get_ssr_sys(self.msgtype) + i = self.encode_head(msg, i, sys) + + for sat in self.sat_n: + sys_, _ = sat2prn(sat) + if sys_ != sys: + continue + i = self.encode_sat(msg, i, sat) + i = self.encode_clk_sat(msg, i, sat) + + return i + + def encode_cssr_ura(self, msg, i, inet=0): + """ encode RTCM SSR URA message """ + sys = self.get_ssr_sys(self.msgtype) + i = self.encode_head(msg, i, sys) + ura = self.lc[inet].ura + + for sat in ura: + sys_, _ = sat2prn(sat) + if sys_ != sys: + continue + i = self.encode_sat(msg, i, sat) + cls_, val_ = self.quality2qi(ura[sat]) + bs.pack_into('u3u3', msg, i, cls_, val_) + i += 6 + + return i + + def encode_cssr_cbias(self, msg, i, inet=0): + """ encode RTCM SSR code bias message """ + sys = self.get_ssr_sys(self.msgtype) + i = self.encode_head(msg, i, sys) + + for sat in self.sat_n: + sys_, _ = sat2prn(sat) + if sys_ != sys: + continue + i = self.encode_sat(msg, i, sat) + cbias = self.lc[inet].cbias[sat] + nsig = len(cbias) + bs.pack_into('u5', msg, i, nsig) + i += 5 + + for rsig_ in cbias: + cb = int(cbias[rsig_]/0.01) + code = self.rsig2code(rsig_) + bs.pack_into('u5s14', msg, i, code, cb) + i += 19 + + return i + + def encode_cssr_pbias(self, msg, i, inet=0): + """ encode RTCM SSR phase bias message """ + sys = self.get_ssr_sys(self.msgtype) + i = self.encode_head(msg, i, sys) + + for sat in self.sat_n: + sys_, _ = sat2prn(sat) + if sys_ != sys: + continue + i = self.encode_sat(msg, i, sat) + pbias = self.lc[inet].pbias[sat] + nsig = len(pbias) + bs.pack_into('u5', msg, i, nsig) + i += 5 + + if sat in self.lc[inet].yaw: + yaw = int(self.lc[inet].yaw[sat]*256.0) + dyaw = int(self.lc[inet].dyaw[sat]*8192.0) + bs.pack_into('u9s8', msg, i, yaw, dyaw) + i += 17 + + for rsig_ in pbias: + + pb = int(pbias[rsig_]/1e-4) + code = self.rsig2code(rsig_) + si = self.lc[inet].si[sat][rsig_] + cnt = self.lc[inet].cnt[sat][rsig_] + + bs.pack_into('u5b1', msg, i, code, si) + i += 6 + + if self.subtype != sRTCM.SSR_PBIAS: # IGS-SSR + wl = self.lc[inet].wl[sat][rsig_] + bs.pack_into('u2', msg, i, wl) + i += 2 + + bs.pack_into('u4s20', msg, i, cnt, pb) + i += 24 + + return i + def encode(self, msg, obs=None): """ encode RTCM messages """ i = 24 @@ -3694,4 +4369,45 @@ def encode(self, msg, obs=None): elif self.msgtype in (11, 12, 13): # SSR integrity (test) i = self.encode_integrity_ssr(msg, i) + elif self.msgtype == 60: + self.subtype = sRTCM.SSR_META + i = self.encode_ssr_metadata(msg, i) + elif self.msgtype == 61: + self.subtype = sRTCM.SSR_GRID + i = self.encode_ssr_grid(msg, i) + elif self.msgtype in [1057, 41, 62, 63, 64]: + self.subtype = sCSSR.ORBIT + i = self.encode_cssr_orb(msg, i) + elif self.msgtype in [1058, 42, 65, 66, 67]: + self.subtype = sCSSR.CLOCK + i = self.encode_cssr_clk(msg, i) + elif self.msgtype in [1060, 44, 71, 72, 73]: + self.subtype = sCSSR.COMBINED + i = self.encode_cssr_comb(msg, i) + elif self.msgtype in [1061, 45, 74, 75, 76]: + self.subtype = sCSSR.URA + i = self.encode_cssr_ura(msg, i) + elif self.msgtype in [1062, 46, 77, 78, 79]: + self.subtype = sCSSR.CLOCK + i = self.encode_cssr_hclk(msg, i) + elif self.msgtype in [1059, 43, 68, 69, 70]: + self.subtype = sCSSR.CBIAS + i = self.encode_cssr_cbias(msg, i) + elif self.msgtype >= 80 and self.msgtype < 85: + self.subtype = sRTCM.SSR_SATANT + # i = self.encode_ssr_satant(msg, i) + elif self.msgtype >= 85 and self.msgtype < 90: + if not self.mask_pbias: + self.subtype = sRTCM.SSR_PBIAS + i = self.encode_cssr_pbias(msg, i) + elif self.msgtype >= 90 and self.msgtype < 95: + self.subtype = sRTCM.SSR_PBIAS_EX + # i = self.encode_ssr_pbias_ex(msg, i) + elif self.msgtype == 95: + self.subtype = sRTCM.SSR_TROP + i = self.encode_ssr_trop(msg, i) + elif self.msgtype >= 96 and self.msgtype < 101: + self.subtype = sRTCM.SSR_STEC + i = self.encode_ssr_iono(msg, i) + return i From 0816d6b8331af9b7b15319e62e2a97cc408a0c9f Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Mon, 19 Jan 2026 21:32:47 +0900 Subject: [PATCH 27/48] - update udi. --- src/cssrlib/cssr_bds.py | 2 +- src/cssrlib/cssr_has.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cssrlib/cssr_bds.py b/src/cssrlib/cssr_bds.py index 77f9c53..e64ff58 100644 --- a/src/cssrlib/cssr_bds.py +++ b/src/cssrlib/cssr_bds.py @@ -107,7 +107,7 @@ def decode_head(self, msg, i, st=-1): if self.week >= 0: self.time = bdt2gpst(bdt2time(self.week, self.tow)) - head = {'uint': 0, 'mi': 0, 'iodssr': iodssr} + head = {'udi': 0, 'mi': 0, 'iodssr': iodssr} return head, i def add_gnss(self, mask, blen, gnss): diff --git a/src/cssrlib/cssr_has.py b/src/cssrlib/cssr_has.py index 6ec1c78..e9577f4 100644 --- a/src/cssrlib/cssr_has.py +++ b/src/cssrlib/cssr_has.py @@ -61,9 +61,9 @@ def decode_head(self, msg, i, st=-1): """ decode header part of HAS messages """ if st == sCSSR.MASK: - ui = 0 + udi = 0 else: - ui = bs.unpack_from('u4', msg, i)[0] + udi = bs.unpack_from('u4', msg, i)[0] i += 4 if self.tow0 >= 0: @@ -71,7 +71,7 @@ def decode_head(self, msg, i, st=-1): if self.week >= 0: self.time = gpst2time(self.week, self.tow) - head = {'uint': ui, 'mi': 0, 'iodssr': self.iodssr} + head = {'udi': udi, 'mi': 0, 'iodssr': self.iodssr} return head, i def decode_cssr(self, msg, i=0): From 91d3851a69b7e437e6666a56a6d2e39e92d26d74 Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sun, 25 Jan 2026 08:24:37 +0900 Subject: [PATCH 28/48] - added experimental RTCM SSR support. --- src/cssrlib/cssrlib.py | 12 +- src/cssrlib/gnss.py | 13 +- src/cssrlib/rtcm.py | 281 +++++++++++++++++++++++++++++++++++------ 3 files changed, 256 insertions(+), 50 deletions(-) diff --git a/src/cssrlib/cssrlib.py b/src/cssrlib/cssrlib.py index a4fe349..02ffa37 100644 --- a/src/cssrlib/cssrlib.py +++ b/src/cssrlib/cssrlib.py @@ -14,7 +14,7 @@ from enum import IntEnum from cssrlib.gnss import gpst2time, rCST, prn2sat, uGNSS, gtime_t, rSigRnx from cssrlib.gnss import uSIG, uTYP, sat2prn, time2str, sat2id, timediff -from cssrlib.gnss import copy_buff, time2gpst +from cssrlib.gnss import copy_buff, time2gpst, mapfParam class sCSSRTYPE(IntEnum): @@ -223,6 +223,9 @@ def __init__(self): self.cbias = {} self.yaw = {} self.dyaw = {} + self.di = {} # pbias discontinuity indicator + self.wl = {} # pbias wide-lane indicator + self.si = {} # pbias integer indicator self.iode = None self.dorb = None self.ddorb = None @@ -1030,6 +1033,11 @@ def decode_cssr_atmos(self, msg, i): i += 6 self.udi[sCType.TROP] = head['udi'] + grid = self.grid[self.grid['nid'] == inet] + ah, aw = mapfParam(self.time, grid['lat'][0]) + self.lc[inet].maph = ah + self.lc[inet].mapw = aw + self.lc[inet].ct = np.zeros((2, 4)) if dfm['trop'] & 2: # functional term @@ -1037,7 +1045,7 @@ def decode_cssr_atmos(self, msg, i): i += 2 vt = bs.unpack_from_dict('s9', ['t00'], msg, i) i += 9 - self.lc[inet].ct[0, 0] = self.sval(vt['t00'], 9, 0.004) + self.lc[inet].ct[0, 0] = self.sval(vt['t00'], 9, 0.004)+2.3 if ttype > 0: vt = bs.unpack_from_dict('s7s7', ['t01', 't10'], msg, i) i += 14 diff --git a/src/cssrlib/gnss.py b/src/cssrlib/gnss.py index 6098ea5..c743432 100644 --- a/src/cssrlib/gnss.py +++ b/src/cssrlib/gnss.py @@ -1550,10 +1550,8 @@ def mapf(sE, a, b, c): return (1.0+a/(1.0+b/(1.0+c)))/(sE+(a/(sE+b/(sE+c)))) -def mapfParam(t, pos, el): - """ mapping function parameters based on lat,alt,time """ - if pos[2] < -1e3 or pos[2] > 20e3 or el <= 0.0: - return 0.0, 0.0 +def mapfParam(t, lat): + """ mapping function parameters based on lat ,time """ # lat = [15,30,45,60,75] # hs(average) [a,b,c] hs(amplitude) [a,b,c] wet [a,b,c] coef = np.array([ @@ -1567,14 +1565,11 @@ def mapfParam(t, pos, el): [1.4275268E-3, 1.5138625E-3, 1.4572752E-3, 1.5007428E-3, 1.7599082E-3], [4.3472961E-2, 4.6729510E-2, 4.3908931E-2, 4.4626982E-2, 5.4736038E-2], ]) - lat = np.rad2deg(pos[0]) - y = (time2doy(t)-28.0)/365.25 if lat < 0.0: y += 0.5 cosy = np.cos(2.0*np.pi*y) - lat = np.abs(lat) - c = interpc(coef, lat) + c = interpc(coef, np.abs(np.rad2deg(lat))) ah = c[0:3]-c[3:6]*cosy aw = c[6:9] @@ -1591,7 +1586,7 @@ def tropmapfNiell(t, pos, el, ah=None, aw=None): return 0.0, 0.0 if ah is None or aw is None: - ah, aw = mapfParam(t, pos, el) + ah, aw = mapfParam(t, pos[0]) aht = [2.53E-5, 5.49E-3, 1.14E-3] # height correction diff --git a/src/cssrlib/rtcm.py b/src/cssrlib/rtcm.py index aace082..036993e 100644 --- a/src/cssrlib/rtcm.py +++ b/src/cssrlib/rtcm.py @@ -8,6 +8,7 @@ Integrity for High Accuracy GNSS based Applications - Version 0.91, 2025 [3] IGS SSR Format version 1.00, 2020 +[4] RTCM SSR Standard, Draft version 0.9.5 Amendment 2, 2026 @author Rui Hirokawa @@ -238,7 +239,10 @@ def __init__(self, foutname=None): self.sc_t = {sCSSR.ORBIT: sCType.ORBIT, sCSSR.CLOCK: sCType.CLOCK, sCSSR.MASK: sCType.MASK, sCSSR.CBIAS: sCType.CBIAS, sCSSR.PBIAS: sCType.PBIAS, sCSSR.URA: sCType.URA, - sCSSR.GRID: sCType.TROP, sCSSR.STEC: sCType.STEC} + sCSSR.GRID: sCType.TROP, sCSSR.STEC: sCType.STEC, + sRTCM.SSR_PBIAS: sCType.PBIAS, + sRTCM.SSR_STEC: sCType.STEC, + } self.glo_bias = None # GLONASS receiver bias self.pos_arp = None # receiver position @@ -297,11 +301,11 @@ def ssrtype(self, msgtype): break return sys, ssr - def svid2sat(self, sys, svid, f_rtcm=False): + def svid2sat(self, sys, svid): """ convert svid to sat """ prn = svid if sys == uGNSS.QZS: - prn += uGNSS.MINPRNQZS-11 if f_rtcm else uGNSS.MINPRNQZS-1 + prn += uGNSS.MINPRNQZS-1 elif sys == uGNSS.SBS: prn += uGNSS.MINPRNSBS-1 return prn2sat(sys, prn) @@ -852,7 +856,7 @@ def decode_cssr_pbias(self, msg, i, inet=0): self.sat_b = [] self.lc[inet].pbias = {} self.lc[inet].si = {} - self.lc[inet].cnt = {} + self.lc[inet].di = {} self.lc[inet].wl = {} self.iodssr = v['iodssr'] @@ -860,6 +864,8 @@ def decode_cssr_pbias(self, msg, i, inet=0): self.mi = v['mi'] self.iyaw = v['iyaw'] + inet = self.inet + for k in range(nsat): i, sat_ = self.decode_sat(msg, i, sys) self.set_t0(inet, sat_, sCType.PBIAS, self.time) @@ -881,7 +887,7 @@ def decode_cssr_pbias(self, msg, i, inet=0): if sat_ not in self.lc[inet].pbias: self.lc[inet].pbias[sat_] = {} self.lc[inet].si[sat_] = {} - self.lc[inet].cnt[sat_] = {} + self.lc[inet].di[sat_] = {} self.lc[inet].wl[sat_] = {} for j in range(nsig): @@ -898,12 +904,12 @@ def decode_cssr_pbias(self, msg, i, inet=0): # signal discontinuity indicator: DF485 # phase bias: DF482 - cnt, pb = bs.unpack_from('u4s20', msg, i) + di, pb = bs.unpack_from('u4s20', msg, i) i += 24 self.lc[inet].pbias[sat_][rsig] = self.sval(pb, 20, 1e-4) self.lc[inet].si[sat_][rsig] = si - self.lc[inet].cnt[sat_][rsig] = cnt + self.lc[inet].di[sat_][rsig] = di self.iodssr_c[sCType.PBIAS] = v['iodssr'] self.lc[inet].cstat |= (1 << sCType.PBIAS) @@ -1289,8 +1295,16 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): ng = self.lc[inet].ng ofst = self.lc[inet].ofst for k in range(ofst, ofst+ng): - self.fh.write(f" res(d/w)[m] {dth[k]:7.4f}" + - f" {dtw[k]:7.4f}\n") + self.fh.write(" res(d/w)[m]") + if dth is not None: + self.fh.write(f" {dth[k]:7.4f}") + else: + self.fh.write(" ") + if dtw is not None: + self.fh.write(f" {dtw[k]:7.4f}") + else: + self.fh.write(" ") + self.fh.write("\n") if self.subtype == sRTCM.SSR_STEC: self.fh.write(f" IODSSR:{self.iodssr} ProvID:{self.pid} " + @@ -1300,7 +1314,7 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): for sat in self.lc[inet].sat_n: self.fh.write(f" {sat2id(sat):s}") ci = self.lc[inet].ci[sat] - self.fh.write(f" c00 {ci[0]:7.4f}") + self.fh.write(f" c00 {ci[0]:8.4f}") if self.lc[inet].stype[sat] > 0: self.fh.write(f" c01 {ci[1]:7.4f} c10 {ci[2]:7.4f}") self.fh.write("\n") @@ -1366,7 +1380,7 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write("{:s}\t{:7.4f}\t{:1d}\t{:2d}\t" .format(sig, self.lc[0].pbias[sat_][sig], self.lc[0].si[sat_][sig], - self.lc[0].cnt[sat_][sig])) + self.lc[0].di[sat_][sig])) self.fh.write("\n") if self.subtype == sRTCM.ANT_DESC: @@ -2520,16 +2534,21 @@ def decode_ssr_trop(self, msg, i, inet=0): if rmi: # residual information part ofst, ng, n, m = bs.unpack_from('u12u12u4u4', msg, i) i += 32 - rh = np.zeros(ofst+ng) - rw = np.zeros(ofst+ng) + + rh = np.zeros(ofst+ng) if n > 0 else None + rw = np.zeros(ofst+ng) if m > 0 else None for k in range(ofst, ofst+ng): - rh_, rw_ = bs.unpack_from(f's{n}s{m}', msg, i) - i += n+m - # hydrostatic grid point residual [m] - rh[k] = self.sval(rh_, n, 0.1e-3) - # wet grid point residual [m] - rw[k] = self.sval(rw_, m, 0.1e-3) + if n > 0: + rh_ = bs.unpack_from(f's{n}', msg, i)[0] + i += n + # hydrostatic grid point residual [m] + rh[k] = self.sval(rh_, n, 0.1e-3) + if m > 0: + rw_ = bs.unpack_from(f's{m}', msg, i)[0] + i += m + # wet grid point residual [m] + rw[k] = self.sval(rw_, m, 0.1e-3) # functional term parameters self.lc[inet].ct = ct @@ -2552,8 +2571,10 @@ def decode_ssr_iono(self, msg, i, inet=0): i, v = self.decode_head(msg, i, sys) # Grid ID DF+22 # Polynomial Model Indicator DF+96 - # Residual Model Indicator - gid, pmi, rmi, satmask = bs.unpack_from('u10b1b1u64', msg, i) + # Residual Model Indicator DF+099 + # Satellite mask DF394 + self.gid, self.pmi, self.rmi, satmask = bs.unpack_from( + 'u10b1b1u64', msg, i) i += 76 # Zenith-mapped STEC Polynomial Gradient Indicator DF+60 svid, nsat = self.decode_mask(satmask, 64, 1) @@ -2562,10 +2583,10 @@ def decode_ssr_iono(self, msg, i, inet=0): # sat[k] = self.svid2sat(sys, svid[k], True) sat[k] = self.svid2sat(sys, svid[k]) - inet = gid + inet = self.gid self.lc[inet].sat_n = sat - if pmi: # polynomial model information part + if self.pmi: # polynomial model information part self.pgi = bs.unpack_from('b1', msg, i)[0] i += 1 sz = 3 if self.pgi else 1 @@ -2587,7 +2608,7 @@ def decode_ssr_iono(self, msg, i, inet=0): else: self.lc[inet].stype[sat_] = 0 - if rmi: # residual model information part + if self.rmi: # residual model information part ofst, ng, rlen = bs.unpack_from('u12u12u5', msg, i) i += 29 if rlen > 0: @@ -2613,10 +2634,6 @@ def decode_ssr_iono(self, msg, i, inet=0): self.lc[inet].ng = ng self.lc[inet].ofst = ofst - self.gid = gid - self.pmi = pmi - self.rmi = rmi - return i def decode_integrity_min(self, msg, i): @@ -3516,7 +3533,11 @@ def __init__(self): self.sc_t = {sCSSR.ORBIT: sCType.ORBIT, sCSSR.CLOCK: sCType.CLOCK, sCSSR.MASK: sCType.MASK, sCSSR.CBIAS: sCType.CBIAS, sCSSR.PBIAS: sCType.PBIAS, sCSSR.URA: sCType.URA, - sCSSR.GRID: sCType.TROP, sCSSR.STEC: sCType.STEC} + sCSSR.GRID: sCType.TROP, sCSSR.STEC: sCType.STEC, + sRTCM.SSR_PBIAS: sCType.PBIAS, + sRTCM.SSR_TROP: sCType.TROP, + sRTCM.SSR_STEC: sCType.STEC, + } def get_ssr_sys(self, msgtype): """ get system from ssr message type """ @@ -3846,6 +3867,15 @@ def msmtype(self, msgtype): break return sys, msm + def sat2svid(self, sat): + """ convert sat number to svid """ + sys, svid = sat2prn(sat) + if sys == uGNSS.QZS: + svid -= uGNSS.MINPRNQZS-1 + elif sys == uGNSS.SBS: + svid -= uGNSS.MINPRNSBS-1 + return svid + def encode_msm_time(self, sys, time): """ decode msm time """ if sys == uGNSS.GLO: @@ -4077,11 +4107,15 @@ def encode_ssr_grid(self, msg, i): lon_i = int(lon/1e-3) alt_i = int((alt+1000)/12.5) + # Latitude of Reference Point DF+024 + # Longitude of Reference Point DF+025 + # Ellipsoidal Height of Reference Point DF+026 + # Number of Relative Points: DF+027 bs.pack_into('s18s19u10u12u8', msg, i, lat_i, lon_i, alt_i, - self.ofst, self.ng) + self.ofst, self.ng-1) i += 67 - for k in range(self.ng): + for k in range(self.ng-1): gid, ofst, lat_, lon_, alt_ = grid[k] dlat = lat_-lat dlon = lon_-lon @@ -4297,7 +4331,7 @@ def encode_cssr_cbias(self, msg, i, inet=0): sys = self.get_ssr_sys(self.msgtype) i = self.encode_head(msg, i, sys) - for sat in self.sat_n: + for sat in self.lc[inet].cbias: sys_, _ = sat2prn(sat) if sys_ != sys: continue @@ -4320,7 +4354,17 @@ def encode_cssr_pbias(self, msg, i, inet=0): sys = self.get_ssr_sys(self.msgtype) i = self.encode_head(msg, i, sys) - for sat in self.sat_n: + inet = self.inet + + nsat = 0 + for sat in self.lc[inet].pbias: + sys_, _ = sat2prn(sat) + if sys_ == sys: + nsat += 1 + bs.pack_into('u6', msg, i, nsat) + i += 6 + + for sat in self.lc[inet].pbias: sys_, _ = sat2prn(sat) if sys_ != sys: continue @@ -4340,8 +4384,11 @@ def encode_cssr_pbias(self, msg, i, inet=0): pb = int(pbias[rsig_]/1e-4) code = self.rsig2code(rsig_) - si = self.lc[inet].si[sat][rsig_] - cnt = self.lc[inet].cnt[sat][rsig_] + if sat in self.lc[inet].si: + si = self.lc[inet].si[sat][rsig_] + else: + si = 0 + di = self.lc[inet].di[sat][rsig_] bs.pack_into('u5b1', msg, i, code, si) i += 6 @@ -4351,11 +4398,168 @@ def encode_cssr_pbias(self, msg, i, inet=0): bs.pack_into('u2', msg, i, wl) i += 2 - bs.pack_into('u4s20', msg, i, cnt, pb) + bs.pack_into('u4s20', msg, i, di, pb) i += 24 return i + def encode_ssr_trop(self, msg, i, inet=0): + """ encode SSR Tropspheric Correction Message """ + i = self.encode_head(msg, i) + inet = self.gid + rmi = self.lc[inet].flg_trop & 1 + # Grid ID: DF+022 + # Residual model indicator DF+009 + bs.pack_into('u10b1', msg, i, self.gid, rmi) + i += 11 + + # mapping function parameters + ah, bh, ch = self.lc[inet].maph + aw, bw, cw = self.lc[inet].mapw + + dah = int((ah-0.00118)/2.5e-7) + dbh = int((bh-0.00298)/5e-6) + dch = int((ch-0.0682)/2e-4) + daw = int((aw-0.000104)/1e-6) + dbw = int((bw-0.0015)/2.5e-5) + dcw = int((cw-0.048)/2e-3) + + # atmospheric model part + bs.pack_into( + 's11s9s9s13s6s5', msg, i, dah, dbh, dch, daw, dbw, dcw) + i += 53 + + # functional term parameters + ct = self.lc[inet].ct + + # hydrostatic + ct00h = int((ct[0, 0]-2.3)/0.1e-3) + ct01h = int(ct[0, 1]/0.01e-3) + ct10h = int(ct[0, 2]/0.01e-3) + # wet + ct00w = int((ct[1, 0]-0.252)/0.1e-3) + ct01w = int(ct[1, 1]/0.01e-3) + ct10w = int(ct[1, 2]/0.01e-3) + + bs.pack_into('s13s15s15', msg, i, ct00h, ct10h, ct01h) + i += 43 + bs.pack_into('s13s15s15', msg, i, ct00w, ct10w, ct01w) + i += 43 + + n = self.nlen + m = self.mlen + + if n > 0: + rh = self.lc[inet].dth + if m > 0: + rw = self.lc[inet].dtw + + if rmi: # residual information part + ng = self.lc[inet].ng + ofst = self.lc[inet].ofst + + bs.pack_into('u12u12u4u4', msg, i, ofst, ng, n, m) + i += 32 + + for k in range(ofst, ofst+ng): + if n > 0: + rh_ = int(rh[k]/0.1e-3) + bs.pack_into(f's{n}', msg, i, rh_) + i += n + if m > 0: + rw_ = int(rw[k]/0.1e-3) + bs.pack_into(f's{m}', msg, i, rw_) + i += m + + return i + + def encode_ssr_iono(self, msg, i, inet=0): + """ encode SSR Regional Ionospheric Correction message (E96,E97,E98,E99,E100) """ + sys_m = self.get_ssr_sys(self.msgtype) + i = self.encode_head(msg, i, sys_m) + + inet = self.inet + + pmi = (self.lc[inet].flg_stec >> 1) & 1 + rmi = self.lc[inet].flg_stec & 1 + + # Grid ID DF+22 + # Polynomial Model Indicator DF+96 + # Residual Model Indicator + bs.pack_into('u10b1b1', msg, i, self.gid, pmi, rmi) + i += 12 + + inet = self.gid + + svid = [] + sat = [] + stype = 0 + for sat_ in self.lc[inet].ci: + sys, _ = sat2prn(sat_) + if sys != sys_m: + continue + sat.append(sat_) + svid.append(self.sat2svid(sat_)) + if stype < self.lc[inet].stype[sat_]: + stype = self.lc[inet].stype[sat_] + + satmask = self.encode_mask(svid, 64) + bs.pack_into('u64', msg, i, satmask) + i += 64 + + # Zenith-mapped STEC Polynomial Gradient Indicator DF+60 + if pmi: # polynomial model information part + pgi = stype > 0 + bs.pack_into('b1', msg, i, pgi) + i += 1 + + for sat_ in sat: + ci = self.lc[inet].ci[sat_] + + c00i = int(ci[0]/0.01) + bs.pack_into('s17', msg, i, c00i) + i += 17 + if pgi: + c01i = int(ci[1]/1e-3) + c10i = int(ci[2]/1e-3) + bs.pack_into('s18s18', msg, i, c01i, c10i) + i += 36 + + if rmi: # residual model information part + ng = self.lc[inet].ng + ofst = self.lc[inet].ofst + rlen = 18 # TBD + scl = 2e-3 # TBD + + grid = self.grid[self.grid['nid'] == self.inet] + lat0 = grid[0]['lat'] + lon0 = grid[0]['lon'] + + bs.pack_into('u12u12u5', msg, i, ofst, ng, rlen) + i += 29 + if rlen > 0: + # Zenith-mapped STEC Grid Point Residual + # Resolution Scale Factor Exponent + sf = np.log(scl/1e-3)/np.log(2) + bs.pack_into('u3', msg, i, sf) # DF+073 + i += 3 + + fmt = 's'+str(rlen) + for sat_ in sat: + ci = self.lc[inet].ci[sat_] + for j in range(ofst, ofst+ng): + dstec = self.lc[inet].dstec[sat_][j] + if np.abs(ci[3]) > 0: + dlat = grid[j]['lat']-lat0 + dlon = grid[j]['lon']-lon0 + dstec += [dlat*dlon, dlat**2, dlon**2]@ci[3:] + + res = int(dstec/scl) + bs.pack_into(fmt, msg, i, res) + i += rlen + + return i + def encode(self, msg, obs=None): """ encode RTCM messages """ i = 24 @@ -4397,9 +4601,8 @@ def encode(self, msg, obs=None): self.subtype = sRTCM.SSR_SATANT # i = self.encode_ssr_satant(msg, i) elif self.msgtype >= 85 and self.msgtype < 90: - if not self.mask_pbias: - self.subtype = sRTCM.SSR_PBIAS - i = self.encode_cssr_pbias(msg, i) + self.subtype = sRTCM.SSR_PBIAS + i = self.encode_cssr_pbias(msg, i) elif self.msgtype >= 90 and self.msgtype < 95: self.subtype = sRTCM.SSR_PBIAS_EX # i = self.encode_ssr_pbias_ex(msg, i) From 372ad94ca6ea71ee539bbb8c65c34ad26a9c7eef Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sun, 25 Jan 2026 19:01:23 +0900 Subject: [PATCH 29/48] - defined common utility class for rtcm --- src/cssrlib/cssrlib.py | 7 +- src/cssrlib/gnss.py | 20 +- src/cssrlib/rtcm.py | 1314 +++++++++++++++++++--------------------- 3 files changed, 637 insertions(+), 704 deletions(-) diff --git a/src/cssrlib/cssrlib.py b/src/cssrlib/cssrlib.py index 02ffa37..8c27f9b 100644 --- a/src/cssrlib/cssrlib.py +++ b/src/cssrlib/cssrlib.py @@ -75,7 +75,12 @@ class sCType(IntEnum): AUTH = 8 HCLOCK = 9 VTEC = 10 - MAX = 11 + OC = 11 # orbit and clock + PBIAS_EX = 12 + SATANT = 13 + META = 14 + GRID = 15 + MAX = 16 class sSigGPS(IntEnum): diff --git a/src/cssrlib/gnss.py b/src/cssrlib/gnss.py index c743432..c82f6ba 100644 --- a/src/cssrlib/gnss.py +++ b/src/cssrlib/gnss.py @@ -1273,10 +1273,14 @@ def char2sys(c): gnss_tbl = {'G': uGNSS.GPS, 'R': uGNSS.GLO, 'E': uGNSS.GAL, 'C': uGNSS.BDS, 'J': uGNSS.QZS, 'S': uGNSS.SBS, 'I': uGNSS.IRN} - if c not in gnss_tbl: - return uGNSS.NONE + if len(c) == 1: + s = uGNSS.NONE if c not in gnss_tbl else gnss_tbl[c] else: - return gnss_tbl[c] + s = [] + for k in range(len(c)): + u = uGNSS.NONE if c[k] not in gnss_tbl else gnss_tbl[c[k]] + s.append(u) + return s def sys2char(sys): @@ -1284,10 +1288,14 @@ def sys2char(sys): gnss_tbl = {uGNSS.GPS: 'G', uGNSS.GLO: 'R', uGNSS.GAL: 'E', uGNSS.BDS: 'C', uGNSS.QZS: 'J', uGNSS.SBS: 'S', uGNSS.IRN: 'I'} - if sys not in gnss_tbl: - return "?" + if len(sys) == 1: + s = '?' if sys not in gnss_tbl else gnss_tbl[sys] else: - return gnss_tbl[sys] + s = [] + for sys_ in sys: + u = '?' if sys_ not in gnss_tbl else gnss_tbl[sys_] + s.append(u) + return s def sys2str(sys): diff --git a/src/cssrlib/rtcm.py b/src/cssrlib/rtcm.py index 036993e..134faf5 100644 --- a/src/cssrlib/rtcm.py +++ b/src/cssrlib/rtcm.py @@ -63,6 +63,7 @@ class sRTCM(IntEnum): SSR_TROP = 95 SSR_STEC = 96 SSR_HCLK = 46 + SSR_CBIAS = 68 SSR_PBIAS = 85 SSR_PBIAS_EX = 90 @@ -203,115 +204,157 @@ def __init__(self): self.mm_param = None -class rtcm(cssr): - """ class to decode RTCM3 messages """ - - def __init__(self, foutname=None): - super().__init__(foutname) - self.len = 0 - self.monlevel = 1 - self.sysref = -1 - self.nsig_max = 4 - self.lock = {} - self.mask_pbias = False - - self.pid = 0 # SSR Provider ID - self.sid = 0 # SSR Solution Type - self.mi = False - - self.nrtk_r = {} - - self.msm_t = { - uGNSS.GPS: 1071, uGNSS.GLO: 1081, uGNSS.GAL: 1091, - uGNSS.SBS: 1101, uGNSS.QZS: 1111, uGNSS.BDS: 1121 - } - - self.ssr_t = { - uGNSS.GPS: 1057, uGNSS.GLO: 1063, uGNSS.GAL: 1240, - uGNSS.QZS: 1246, uGNSS.SBS: 1252, uGNSS.BDS: 1258 - } - - self.eph_t = { - uGNSS.GPS: 1019, uGNSS.GLO: 1020, uGNSS.BDS: 1042, - uGNSS.QZS: 1044, uGNSS.GAL: 1046 - } - - self.sc_t = {sCSSR.ORBIT: sCType.ORBIT, sCSSR.CLOCK: sCType.CLOCK, - sCSSR.MASK: sCType.MASK, sCSSR.CBIAS: sCType.CBIAS, - sCSSR.PBIAS: sCType.PBIAS, sCSSR.URA: sCType.URA, - sCSSR.GRID: sCType.TROP, sCSSR.STEC: sCType.STEC, - sRTCM.SSR_PBIAS: sCType.PBIAS, - sRTCM.SSR_STEC: sCType.STEC, - } - - self.glo_bias = None # GLONASS receiver bias - self.pos_arp = None # receiver position - self.ant_desc = "unknown" - self.ant_id = "" - self.ant_serial = "" - self.rcv_type = "" - self.firm_ver = "unknown" - self.rcv_serial = "" - - self.antc = {} - self.integ = Integrity() - self.test_mode = False # for interop testing in SC134 - - def is_ssrtype(self, msgtype, tstmsg=False): - """ check if the message type is MSM """ - for sys_ in self.ssr_t.keys(): - if msgtype >= self.ssr_t[sys_] and msgtype <= self.ssr_t[sys_]+6: - return True - if tstmsg and msgtype >= 60 and msgtype <= 100: # SSR test message - return True - return False - - def is_msmtype(self, msgtype): - """ check if the message type is MSM """ - for sys_ in self.msm_t.keys(): - if msgtype >= self.msm_t[sys_] and msgtype <= self.msm_t[sys_]+6: - return True - return False - - def adjustweek(self, week: int, tref: gtime_t): - """ adjust week number considering reference time """ - week_, _ = time2gpst(tref) - week_ref = (week_//1024)*1024 - return (week % 1024) + week_ref - - def msmtype(self, msgtype): - """ get system and msm type from message type """ - sys = uGNSS.NONE - msm = 0 - for sys_ in self.msm_t.keys(): - if msgtype >= self.msm_t[sys_] and msgtype <= self.msm_t[sys_]+6: - sys = sys_ - msm = msgtype-self.msm_t[sys_]+1 - break - return sys, msm +class rtcmUtil: + """ class to define common parameters and utilities for RTCM """ + + msm_t = { + uGNSS.GPS: 1071, uGNSS.GLO: 1081, uGNSS.GAL: 1091, + uGNSS.SBS: 1101, uGNSS.QZS: 1111, uGNSS.BDS: 1121 + } + + ssr_t = { + uGNSS.GPS: 1057, uGNSS.GLO: 1063, uGNSS.GAL: 1240, + uGNSS.QZS: 1246, uGNSS.SBS: 1252, uGNSS.BDS: 1258 + } + + eph_t = { + uGNSS.GPS: 1019, uGNSS.GLO: 1020, uGNSS.BDS: 1042, + uGNSS.QZS: 1044, uGNSS.GAL: 1046 + } + + sc_t = {sCSSR.ORBIT: sCType.ORBIT, sCSSR.CLOCK: sCType.CLOCK, + sCSSR.MASK: sCType.MASK, sCSSR.CBIAS: sCType.CBIAS, + sCSSR.PBIAS: sCType.PBIAS, sCSSR.URA: sCType.URA, + sCSSR.GRID: sCType.TROP, sCSSR.STEC: sCType.STEC, + sRTCM.SSR_PBIAS: sCType.PBIAS, + sRTCM.SSR_TROP: sCType.TROP, + sRTCM.SSR_STEC: sCType.STEC, + } + + ssrtype_t = { + 1057: (uGNSS.GPS, sCType.ORBIT), + 1058: (uGNSS.GPS, sCType.CLOCK), + 1059: (uGNSS.GPS, sCType.CBIAS), + 1060: (uGNSS.GPS, sCType.OC), + 1061: (uGNSS.GPS, sCType.URA), + 1062: (uGNSS.GPS, sCType.HCLOCK), + 41: (uGNSS.GLO, sCType.ORBIT), + 42: (uGNSS.GLO, sCType.CLOCK), + 43: (uGNSS.GLO, sCType.CBIAS), + 44: (uGNSS.GLO, sCType.OC), + 45: (uGNSS.GLO, sCType.URA), + 46: (uGNSS.GLO, sCType.HCLOCK), + 60: (uGNSS.NONE, sCType.META), + 61: (uGNSS.NONE, sCType.GRID), + 62: (uGNSS.GAL, sCType.ORBIT), + 63: (uGNSS.BDS, sCType.ORBIT), + 64: (uGNSS.QZS, sCType.ORBIT), + 65: (uGNSS.GAL, sCType.CLOCK), + 66: (uGNSS.BDS, sCType.CLOCK), + 67: (uGNSS.QZS, sCType.CLOCK), + 68: (uGNSS.GAL, sCType.CBIAS), + 69: (uGNSS.BDS, sCType.CBIAS), + 70: (uGNSS.QZS, sCType.CBIAS), + 71: (uGNSS.GAL, sCType.OC), + 72: (uGNSS.BDS, sCType.OC), + 73: (uGNSS.QZS, sCType.OC), + 74: (uGNSS.GAL, sCType.URA), + 75: (uGNSS.BDS, sCType.URA), + 76: (uGNSS.QZS, sCType.URA), + 77: (uGNSS.GAL, sCType.HCLOCK), + 78: (uGNSS.BDS, sCType.HCLOCK), + 79: (uGNSS.QZS, sCType.HCLOCK), + 80: (uGNSS.GPS, sCType.SATANT), + 81: (uGNSS.GLO, sCType.SATANT), + 82: (uGNSS.GAL, sCType.SATANT), + 83: (uGNSS.BDS, sCType.SATANT), + 84: (uGNSS.QZS, sCType.SATANT), + 85: (uGNSS.GPS, sCType.PBIAS), + 86: (uGNSS.GLO, sCType.PBIAS), + 87: (uGNSS.GAL, sCType.PBIAS), + 88: (uGNSS.BDS, sCType.PBIAS), + 89: (uGNSS.QZS, sCType.PBIAS), + 90: (uGNSS.GPS, sCType.PBIAS_EX), + 91: (uGNSS.GLO, sCType.PBIAS_EX), + 92: (uGNSS.GAL, sCType.PBIAS_EX), + 93: (uGNSS.BDS, sCType.PBIAS_EX), + 94: (uGNSS.QZS, sCType.PBIAS_EX), + 95: (uGNSS.NONE, sCType.TROP), + 96: (uGNSS.GPS, sCType.STEC), + 97: (uGNSS.GLO, sCType.STEC), + 98: (uGNSS.GAL, sCType.STEC), + 99: (uGNSS.BDS, sCType.STEC), + 100: (uGNSS.QZS, sCType.STEC), + } + + def mt2sct(self, s): + """ SSR Message type to SCType """ + return self.ssrtype_t[s] + + def sct2mt(self, sct: sCType, sys: uGNSS = uGNSS.NONE): + """ SCType to SSR Message type """ + for k, v in self.ssrtype_t.items(): + if v[0] == sys and v[1] == sct: + return k + return -1 - def ssrtype(self, msgtype): - """ get system and ssr type from message type """ - sys = uGNSS.NONE - ssr = 0 - for sys_ in self.ssr_t.keys(): - if msgtype >= self.ssr_t[sys_] and msgtype <= self.ssr_t[sys_]+6: - sys = sys_ - ssr = msgtype-self.ssr_t[sys_]+1 - break - return sys, ssr + def get_ssr_sys(self, msgtype): + """ get system from ssr message type """ + if msgtype == 4076: + return self.sysref + else: + if msgtype >= 1057 and msgtype < 1063: + return uGNSS.GPS + elif msgtype >= 1063 and msgtype < 1069: + return uGNSS.GLO + elif msgtype >= 1240 and msgtype < 1246: + return uGNSS.GAL + elif msgtype >= 1246 and msgtype < 1252: + return uGNSS.QZS + elif msgtype >= 1252 and msgtype < 1258: + return uGNSS.SBS + elif msgtype >= 1258 and msgtype < 1264: + return uGNSS.BDS + elif msgtype >= 1265 and msgtype < 1271: # proposed phase bias + tbl_t = {1265: uGNSS.GPS, 1266: uGNSS.GLO, 1267: uGNSS.GAL, + 1268: uGNSS.QZS, 1269: uGNSS.SBS, 1270: uGNSS.BDS} + return tbl_t[msgtype] + # RTCM SSR test messages + elif msgtype >= 41 and msgtype < 47: # OBC + return uGNSS.GLO + elif msgtype in [62, 65, 68, 71, 74, 77]: # OBC + return uGNSS.GAL + elif msgtype in [63, 66, 69, 72, 75, 78]: # OBC + return uGNSS.BDS + elif msgtype in [64, 67, 70, 73, 76, 79]: # OBC + return uGNSS.QZS + elif msgtype >= 80 and msgtype < 85: # proposed satellite antenna + tbl_t = {80: uGNSS.GPS, 81: uGNSS.GLO, 82: uGNSS.GAL, + 83: uGNSS.BDS, 84: uGNSS.QZS} + return tbl_t[msgtype] + elif msgtype >= 85 and msgtype < 90: # proposed phase bias + tbl_t = {85: uGNSS.GPS, 86: uGNSS.GLO, 87: uGNSS.GAL, + 88: uGNSS.BDS, 89: uGNSS.QZS} + return tbl_t[msgtype] + elif msgtype >= 90 and msgtype < 95: # proposed phase bias + tbl_t = {90: uGNSS.GPS, 91: uGNSS.GLO, 92: uGNSS.GAL, + 93: uGNSS.BDS, 94: uGNSS.QZS} + return tbl_t[msgtype] + elif msgtype >= 96 and msgtype < 101: # regional iono + tbl_t = {96: uGNSS.GPS, 97: uGNSS.GLO, 98: uGNSS.GAL, + 99: uGNSS.BDS, 100: uGNSS.QZS} + elif msgtype in [60, 61, 95]: # METADATA, grid definition, trop + return uGNSS.NONE + elif msgtype in [11, 12, 13]: # RTCM SC-134 test messages + return uGNSS.NONE + else: + print(f"definition of {msgtype} is missing") + # return uGNSS.NONE - def svid2sat(self, sys, svid): - """ convert svid to sat """ - prn = svid - if sys == uGNSS.QZS: - prn += uGNSS.MINPRNQZS-1 - elif sys == uGNSS.SBS: - prn += uGNSS.MINPRNSBS-1 - return prn2sat(sys, prn) + return tbl_t[msgtype] - def ssig2rsig(self, sys: uGNSS, utyp: uTYP, ssig): - """ convert ssig to rSigRnx """ + def rsig2code(self, rsig): + """ convert rSigRnx to RTCM SSR code """ gps_tbl = { 0: uSIG.L1C, 1: uSIG.L1P, @@ -418,8 +461,10 @@ def ssig2rsig(self, sys: uGNSS, utyp: uTYP, ssig): uGNSS.SBS: sbs_tbl, } - usig_tbl = usig_tbl_[sys] - return rSigRnx(sys, utyp, usig_tbl[ssig]) + usig_tbl = usig_tbl_[rsig.sys] + dic = {usig_tbl[s]: s for s in usig_tbl} + + return dic[rsig.sig] def msm2rsig(self, sys: uGNSS, utyp: uTYP, ssig): """ convert ssig to rSigRnx for MSM """ @@ -538,6 +583,81 @@ def msm2rsig(self, sys: uGNSS, utyp: uTYP, ssig): usig_tbl = usig_tbl_[sys] return rSigRnx(sys, utyp, usig_tbl[ssig]) + def is_ssrtype(self, msgtype, tstmsg=False): + """ check if the message type is MSM """ + for sys_ in self.ssr_t.keys(): + if msgtype >= self.ssr_t[sys_] and msgtype <= self.ssr_t[sys_]+6: + return True + if tstmsg and msgtype >= 60 and msgtype <= 100: # SSR test message + return True + return False + + def is_msmtype(self, msgtype): + """ check if the message type is MSM """ + for sys_ in self.msm_t.keys(): + if msgtype >= self.msm_t[sys_] and msgtype <= self.msm_t[sys_]+6: + return True + return False + + def msmtype(self, msgtype): + """ get system and msm type from message type """ + sys = uGNSS.NONE + msm = 0 + for sys_ in self.msm_t.keys(): + if msgtype >= self.msm_t[sys_] and msgtype <= self.msm_t[sys_]+6: + sys = sys_ + msm = msgtype-self.msm_t[sys_]+1 + break + return sys, msm + + def nrtktype(self, msgtype): + """ get system from nrtk message type """ + gnss_t = {1030: uGNSS.GPS, 1031: uGNSS.GLO, + 1303: uGNSS.BDS, 1304: uGNSS.GAL, 1305: uGNSS.QZS} + + sys = uGNSS.NONE + nrtk = 0 + if msgtype in gnss_t.keys(): + nrtk = 1 + sys = gnss_t[msgtype] + + return sys, nrtk + + def ssrtype(self, msgtype): + """ get system and ssr type from message type """ + sys = uGNSS.NONE + ssr = 0 + for sys_ in self.ssr_t.keys(): + if msgtype >= self.ssr_t[sys_] and msgtype <= self.ssr_t[sys_]+6: + sys = sys_ + ssr = msgtype-self.ssr_t[sys_]+1 + break + return sys, ssr + + def sat2svid(self, sat): + """ convert sat number to svid """ + sys, svid = sat2prn(sat) + if sys == uGNSS.QZS: + svid -= uGNSS.MINPRNQZS-1 + elif sys == uGNSS.SBS: + svid -= uGNSS.MINPRNSBS-1 + return svid + + def svid2sat(self, sys, svid): + """ convert svid to sat """ + prn = svid + if sys == uGNSS.QZS: + prn += uGNSS.MINPRNQZS-1 + elif sys == uGNSS.SBS: + prn += uGNSS.MINPRNSBS-1 + return prn2sat(sys, prn) + + def adjustweek(self, week: int, tref: gtime_t): + """ adjust week number considering reference time """ + week_, _ = time2gpst(tref) + week_ref = (week_//1024)*1024 + return (week % 1024) + week_ref + def sys2str(self, sys: uGNSS): """ convert system enum to string """ gnss_t = {uGNSS.GPS: "GPS", uGNSS.GLO: "GLO", uGNSS.GAL: "GAL", @@ -547,38 +667,181 @@ def sys2str(self, sys: uGNSS): return "" return gnss_t[sys] - def sync(self, buff, k): - """ check if the buffer has a sync pattern """ - return buff[k] == 0xd3 - - def checksum(self, msg, k, maxlen=0): - """ check the checksum of the message """ - len_ = st.unpack_from('>H', msg, k+1)[0] & 0x3ff - if len_ < 6: - return False - if maxlen > 0 and k+len_ >= maxlen: - return False - cs = Crc24LteA.calc(msg[k:k+len_+6]) - if self.monlevel > 0 and cs != 0: - msgtype = (st.unpack_from('>H', msg, k+3)[0] >> 4) & 0xfff - print(f"checksum error: len={len_} msgtype={msgtype}") - self.len = len_ - self.dlen = len_+6 - return cs == 0 - def decode_head(self, msg, i, sys=uGNSS.NONE): - """ decode the header of ssr message """ - if self.msgtype == 4076 or sys != uGNSS.GLO: - blen = 20 - else: - blen = 17 - self.tow = bs.unpack_from('u'+str(blen), msg, i)[0] - self.time = gpst2time(self.week, self.tow) - i += blen +class rtcm(cssr, rtcmUtil): + """ class to decode RTCM3 messages """ - if self.subtype not in [sRTCM.SSR_PBIAS_EX]: - udi, mi = bs.unpack_from('u4b1', msg, i) - i += 5 + def __init__(self, foutname=None): + super().__init__(foutname) + self.len = 0 + self.monlevel = 1 + self.sysref = -1 + self.nsig_max = 4 + self.lock = {} + self.mask_pbias = False + + self.pid = 0 # SSR Provider ID + self.sid = 0 # SSR Solution Type + self.mi = False + + self.nrtk_r = {} + + self.glo_bias = None # GLONASS receiver bias + self.pos_arp = None # receiver position + self.ant_desc = "unknown" + self.ant_id = "" + self.ant_serial = "" + self.rcv_type = "" + self.firm_ver = "unknown" + self.rcv_serial = "" + + self.antc = {} + self.integ = Integrity() + self.test_mode = False # for interop testing in SC134 + + def ssig2rsig(self, sys: uGNSS, utyp: uTYP, ssig): + """ convert ssig to rSigRnx """ + gps_tbl = { + 0: uSIG.L1C, + 1: uSIG.L1P, + 2: uSIG.L1W, + 5: uSIG.L2C, + 6: uSIG.L2D, + 7: uSIG.L2S, + 8: uSIG.L2L, + 9: uSIG.L2X, + 10: uSIG.L2P, + 11: uSIG.L2W, + 14: uSIG.L5I, + 15: uSIG.L5Q, + 16: uSIG.L5X, + 17: uSIG.L1S, + 18: uSIG.L1L, + 19: uSIG.L1X, + } + glo_tbl = { + 0: uSIG.L1C, + 1: uSIG.L1P, + 2: uSIG.L2C, + 3: uSIG.L2P, + 4: uSIG.L4A, + 5: uSIG.L4B, + 6: uSIG.L6A, + 7: uSIG.L6B, + 10: uSIG.L3I, + 11: uSIG.L3Q, + } + + gal_tbl = { + 0: uSIG.L1A, + 1: uSIG.L1B, + 2: uSIG.L1C, + 3: uSIG.L1X, + 4: uSIG.L1Z, + 5: uSIG.L5I, + 6: uSIG.L5Q, + 7: uSIG.L5X, + 8: uSIG.L7I, + 9: uSIG.L7Q, + 10: uSIG.L7X, + 11: uSIG.L8I, + 12: uSIG.L8Q, + 13: uSIG.L8X, + 14: uSIG.L6A, + 15: uSIG.L6B, + 16: uSIG.L6C, + 17: uSIG.L6X, + 18: uSIG.L6Z, + } + + bds_tbl = { + 0: uSIG.L2I, + 1: uSIG.L2Q, + 2: uSIG.L2X, + 3: uSIG.L6I, + 4: uSIG.L6Q, + 5: uSIG.L6X, + 6: uSIG.L7I, + 7: uSIG.L7Q, + 8: uSIG.L7X, + 9: uSIG.L1D, + 10: uSIG.L1P, + 11: uSIG.L1X, + 12: uSIG.L5D, + 13: uSIG.L5P, + 14: uSIG.L5X, + 15: uSIG.L1A, + } + + qzs_tbl = { + 0: uSIG.L1C, + 1: uSIG.L1S, + 2: uSIG.L1C, + 3: uSIG.L2S, + 4: uSIG.L2L, + 5: uSIG.L2X, + 6: uSIG.L5I, + 7: uSIG.L5Q, + 8: uSIG.L5X, + 9: uSIG.L6S, + 10: uSIG.L6L, + 11: uSIG.L6X, + 12: uSIG.L1X, + 17: uSIG.L6E, + 19: uSIG.L1E, # TBD + } + + sbs_tbl = { + 0: uSIG.L1C, + 1: uSIG.L5I, + 2: uSIG.L5Q, + 3: uSIG.L5X, + } + + usig_tbl_ = { + uGNSS.GPS: gps_tbl, + uGNSS.GLO: glo_tbl, + uGNSS.GAL: gal_tbl, + uGNSS.BDS: bds_tbl, + uGNSS.QZS: qzs_tbl, + uGNSS.SBS: sbs_tbl, + } + + usig_tbl = usig_tbl_[sys] + return rSigRnx(sys, utyp, usig_tbl[ssig]) + + def sync(self, buff, k): + """ check if the buffer has a sync pattern """ + return buff[k] == 0xd3 + + def checksum(self, msg, k, maxlen=0): + """ check the checksum of the message """ + len_ = st.unpack_from('>H', msg, k+1)[0] & 0x3ff + if len_ < 6: + return False + if maxlen > 0 and k+len_ >= maxlen: + return False + cs = Crc24LteA.calc(msg[k:k+len_+6]) + if self.monlevel > 0 and cs != 0: + msgtype = (st.unpack_from('>H', msg, k+3)[0] >> 4) & 0xfff + print(f"checksum error: len={len_} msgtype={msgtype}") + self.len = len_ + self.dlen = len_+6 + return cs == 0 + + def decode_head(self, msg, i, sys=uGNSS.NONE): + """ decode the header of ssr message """ + if self.msgtype == 4076 or sys != uGNSS.GLO: + blen = 20 + else: + blen = 17 + self.tow = bs.unpack_from('u'+str(blen), msg, i)[0] + self.time = gpst2time(self.week, self.tow) + i += blen + + if self.subtype not in [sRTCM.SSR_PBIAS_EX]: + udi, mi = bs.unpack_from('u4b1', msg, i) + i += 5 v = {'udi': udi, 'mi': mi} if self.subtype in (sCSSR.ORBIT, sCSSR.COMBINED): @@ -671,61 +934,6 @@ def decode_hclk_sat(self, msg, i, k, inet=0): self.dclk_n[k] = self.sval(hclk, 22, 0.1e-3) return i - def get_ssr_sys(self, msgtype): - """ get system from ssr message type """ - if msgtype == 4076: - return self.sysref - else: - if msgtype >= 1057 and msgtype < 1063: - return uGNSS.GPS - elif msgtype >= 1063 and msgtype < 1069: - return uGNSS.GLO - elif msgtype >= 1240 and msgtype < 1246: - return uGNSS.GAL - elif msgtype >= 1246 and msgtype < 1252: - return uGNSS.QZS - elif msgtype >= 1252 and msgtype < 1258: - return uGNSS.SBS - elif msgtype >= 1258 and msgtype < 1264: - return uGNSS.BDS - elif msgtype >= 1265 and msgtype < 1271: # proposed phase bias - tbl_t = {1265: uGNSS.GPS, 1266: uGNSS.GLO, 1267: uGNSS.GAL, - 1268: uGNSS.QZS, 1269: uGNSS.SBS, 1270: uGNSS.BDS} - return tbl_t[msgtype] - # RTCM SSR test messages - elif msgtype >= 41 and msgtype < 47: # OBC - return uGNSS.GLO - elif msgtype in [62, 65, 68, 71, 74, 77]: # OBC - return uGNSS.GAL - elif msgtype in [63, 66, 69, 72, 75, 78]: # OBC - return uGNSS.BDS - elif msgtype in [64, 67, 70, 73, 76, 79]: # OBC - return uGNSS.QZS - elif msgtype >= 80 and msgtype < 85: # proposed satellite antenna - tbl_t = {80: uGNSS.GPS, 81: uGNSS.GLO, 82: uGNSS.GAL, - 83: uGNSS.BDS, 84: uGNSS.QZS} - return tbl_t[msgtype] - elif msgtype >= 85 and msgtype < 90: # proposed phase bias - tbl_t = {85: uGNSS.GPS, 86: uGNSS.GLO, 87: uGNSS.GAL, - 88: uGNSS.BDS, 89: uGNSS.QZS} - return tbl_t[msgtype] - elif msgtype >= 90 and msgtype < 95: # proposed phase bias - tbl_t = {90: uGNSS.GPS, 91: uGNSS.GLO, 92: uGNSS.GAL, - 93: uGNSS.BDS, 94: uGNSS.QZS} - return tbl_t[msgtype] - elif msgtype >= 96 and msgtype < 101: # regional iono - tbl_t = {96: uGNSS.GPS, 97: uGNSS.GLO, 98: uGNSS.GAL, - 99: uGNSS.BDS, 100: uGNSS.QZS} - elif msgtype in [60, 61, 95]: # METADATA, grid definition, trop - return uGNSS.NONE - elif msgtype in [11, 12, 13]: # RTCM SC-134 test messages - return uGNSS.NONE - else: - print(f"definition of {msgtype} is missing") - # return uGNSS.NONE - - return tbl_t[msgtype] - def decode_cssr_orb(self, msg, i, inet=0): """ decode RTCM Orbit Correction message """ sys = self.get_ssr_sys(self.msgtype) @@ -1102,19 +1310,6 @@ def decode_igsssr(self, msg, i=0): return i - def nrtktype(self, msgtype): - """ get system from nrtk message type """ - gnss_t = {1030: uGNSS.GPS, 1031: uGNSS.GLO, - 1303: uGNSS.BDS, 1304: uGNSS.GAL, 1305: uGNSS.QZS} - - sys = uGNSS.NONE - nrtk = 0 - if msgtype in gnss_t.keys(): - nrtk = 1 - sys = gnss_t[msgtype] - - return sys, nrtk - def decode_nrtk_time(self, msg, i): """ decode Network RTK Time Message """ sys, nrtk = self.nrtktype(self.msgtype) @@ -1147,7 +1342,7 @@ def decode_nrtk_residual(self, msg, i=0): s0c, s0d, s0h, sic, sid = bs.unpack_from('u8u9u6u10u10', msg, i) i += 43 self.nrtk_r[sat] = np.array([s0c*5e-4, s0d*1e-8, s0h*1e-7, - sic*5e-4, sid*1e-8]) + sic*5e-4, sid*1e-8]) return i @@ -1339,7 +1534,7 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write(" dpos: {:6.3f} {:6.3f} {:9.3f}\n".format( self.dpos[k][0], self.dpos[k][1], self.dpos[k][2])) - if self.subtype in [sCSSR.CBIAS, sCSSR.BIAS]: + if self.subtype in [sCSSR.CBIAS, sCSSR.BIAS, sRTCM.SSR_CBIAS]: self.fh.write(f" IODSSR:{self.iodssr} ProvID:{self.pid} " + f"SolID:{self.sid}\n") self.fh.write(" {:s}\t{:s}\t{:s}\t{:s}\n" @@ -1365,7 +1560,7 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write("\t{:s}\t{:s}\t{:s}\t{:s}\t{:s}\n" .format("SigID", "PBias[m]", "i", "cnt", "...")) - for k, sat_ in enumerate(self.lc[0].pbias.keys()): + for k, sat_ in enumerate(self.lc[inet].pbias.keys()): sys_, _ = sat2prn(sat_) if sys_ != sys: continue @@ -1373,14 +1568,14 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): if self.iyaw: self.fh.write("\t{:7.4f}\t{:7.4f}\t" - .format(self.lc[0].yaw[sat_], - self.lc[0].dyaw[sat_])) + .format(self.lc[inet].yaw[sat_], + self.lc[inet].dyaw[sat_])) - for sig in self.lc[0].pbias[sat_].keys(): + for sig in self.lc[inet].pbias[sat_].keys(): self.fh.write("{:s}\t{:7.4f}\t{:1d}\t{:2d}\t" - .format(sig, self.lc[0].pbias[sat_][sig], - self.lc[0].si[sat_][sig], - self.lc[0].di[sat_][sig])) + .format(sig, self.lc[inet].pbias[sat_][sig], + self.lc[inet].si[sat_][sig], + self.lc[inet].di[sat_][sig])) self.fh.write("\n") if self.subtype == sRTCM.ANT_DESC: @@ -2285,13 +2480,13 @@ def decode_ssr_metadata(self, msg, i): i += 7 if mi: # non-default model/correction id DF+67 - mci = bs.unpack_from('u3', msg, i)[0] + # mci = bs.unpack_from('u3', msg, i)[0] i += 3 iodmi = bs.unpack_from('b1', msg, i)[0] # DF+68 i += 1 if iodmi: # DF+69 model/correction data IOD - iodm = bs.unpack_from('u6', msg, i)[0] + # iodm = bs.unpack_from('u6', msg, i)[0] i += 6 if mi: # DF+070 1:additional model parameter present @@ -2303,7 +2498,7 @@ def decode_ssr_metadata(self, msg, i): i += 8 if mi and mpi: # aditional model parameter DF+072 - data = bs.unpack_from('u'+str(nb), msg, i)[0] + # data = bs.unpack_from('u'+str(nb), msg, i)[0] i += nb if mt == 7: # GNSS BE Reference @@ -2402,9 +2597,15 @@ def decode_ssr_grid(self, msg, i): elif gtype == 1: # grid type 1 lat0, lon0, ofst, npnt = bs.unpack_from('s18s19u12u8', msg, i) i += 57 + lat = self.sval(lat0, 18, 1e-3) lon = self.sval(lon0, 19, 1e-3) - self.grid = np.array([], dtype=dtype0) + + self.grid = np.array([(gid, ofst, lat, lon, 0)], dtype=dtype0) + self.dpos = np.zeros((npnt, 3)) + self.pos0 = np.array([lat, lon, 0]) + self.ofst = ofst + for k in range(npnt): dlat, dlon = bs.unpack_from('s13s14', msg, i) i += 27 @@ -2412,6 +2613,7 @@ def decode_ssr_grid(self, msg, i): lon += self.sval(dlon, 14, 1e-3) d = np.array([(gid, k, lat, lon, 0)], dtype=dtype0) self.grid = np.append(self.grid, d) + self.dpos[k-ofst, :] = (dlat*1e-3, dlon*1e-3, 0) elif gtype == 2: # grid type 2 lat0_, lon0_, nrow, ncol, dlat_, dlon_, gma = bs.unpack_from( @@ -3407,422 +3609,131 @@ def decode(self, msg, subtype=None, scanmode=False): self.subtype = sRTCM.SSR_META i = self.decode_ssr_metadata(msg, i) elif self.msgtype == 61: - self.subtype = sRTCM.SSR_GRID - i = self.decode_ssr_grid(msg, i) - elif self.msgtype in [1057, 41, 62, 63, 64]: - self.subtype = sCSSR.ORBIT - i = self.decode_cssr_orb(msg, i) - elif self.msgtype in [1058, 42, 65, 66, 67]: - self.subtype = sCSSR.CLOCK - i = self.decode_cssr_clk(msg, i) - elif self.msgtype in [1060, 44, 71, 72, 73]: - self.subtype = sCSSR.COMBINED - i = self.decode_cssr_comb(msg, i) - elif self.msgtype in [1061, 45, 74, 75, 76]: - self.subtype = sCSSR.URA - i = self.decode_cssr_ura(msg, i) - elif self.msgtype in [1062, 46, 77, 78, 79]: - self.subtype = sCSSR.CLOCK - i = self.decode_cssr_hclk(msg, i) - elif self.msgtype in [1059, 43, 68, 69, 70]: - self.subtype = sCSSR.CBIAS - i = self.decode_cssr_cbias(msg, i) - elif self.msgtype >= 80 and self.msgtype < 85: - self.subtype = sRTCM.SSR_SATANT - i = self.decode_ssr_satant(msg, i) - elif self.msgtype >= 85 and self.msgtype < 90: - if not self.mask_pbias: - self.subtype = sRTCM.SSR_PBIAS - i = self.decode_cssr_pbias(msg, i) - elif self.msgtype >= 90 and self.msgtype < 95: - self.subtype = sRTCM.SSR_PBIAS_EX - i = self.decode_ssr_pbias_ex(msg, i) - elif self.msgtype == 95: - self.subtype = sRTCM.SSR_TROP - i = self.decode_ssr_trop(msg, i) - elif self.msgtype >= 96 and self.msgtype < 101: - self.subtype = sRTCM.SSR_STEC - i = self.decode_ssr_iono(msg, i) - - # test messages for SC-134 - elif self.msgtype in [3, 2000]: # minimum integrity - self.subtype = sRTCM.INTEG_MIN - self.decode_integrity_min(msg, i) - elif self.msgtype in [4, 2005]: # extended integrity - self.subtype = sRTCM.INTEG_EXT - self.decode_integrity_ext(msg, i) - elif self.msgtype in [5, 2006]: # sis integrity/local error - self.subtype = sRTCM.INTEG_EXT_SIS - self.decode_integrity_ext_sis_local(msg, i) - elif self.msgtype in [6, 2007]: # primary sercice area - self.subtype = sRTCM.INTEG_PRI_AREA - self.decode_integrity_pri_service_area(msg, i) - elif self.msgtype == 2008: # extended sercice area - self.subtype = sRTCM.INTEG_EXT_AREA - self.decode_integrity_ext_service_area(msg, i) - elif self.msgtype in [7, 2051]: # quality indicator - self.subtype = sRTCM.INTEG_QUALITY - self.decode_integrity_quality(msg, i) - elif self.msgtype in [8, 2091]: # CNR/ACG SIS Monitoring - self.subtype = sRTCM.INTEG_CNR - self.decode_integrity_cnr_acg(msg, i) - elif self.msgtype == 2071: # satellite visibility map - self.subtype = sRTCM.INTEG_VMAP - self.decode_integrity_vmap(msg, i) - elif self.msgtype == 2072: # multipath map - self.subtype = sRTCM.INTEG_MMAP - self.decode_integrity_mmap(msg, i) - elif self.msgtype in [11, 2011]: # SSR integrity - self.subtype = sRTCM.INTEG_SSR - self.decode_integrity_ssr(msg, i) - elif self.msgtype == 12: # SSR integrity Iono - self.subtype = sRTCM.INTEG_SSR_IONO - self.decode_integrity_ssr(msg, i) - elif self.msgtype == 13: # SSR integrity Trop - self.subtype = sRTCM.INTEG_SSR_TROP - self.decode_integrity_ssr(msg, i) - elif self.msgtype == 54: # SSR integrity test msg - i = self.decode_sc134_test_header(msg, i) - if self.subtype == 9: - self.decode_integrity_vmap(msg, i) - elif self.subtype == 10: - self.decode_integrity_mmap(msg, i) - else: - self.subtype = -1 - - if self.monlevel > 0 and self.fh is not None: - self.out_log(obs, eph, geph, seph) - - return i, obs, eph, geph, seph - - -class rtcme(cssre): - """ class for RTCM message encoder """ - - def __init__(self): - super().__init__() - self.integ = Integrity() - - self.pid = 0 # SSR Provider ID - self.sid = 0 # SSR Solution Type - - self.mi = False # multiple message bit - self.iods = 0 # issue of data station - self.refid = 0 # reference station ID - - self.csi = 0 # clock streering indicator - self.eci = 0 # external clock indicator - self.si = 0 # smoothing indicator - self.smi = 0 # smoothing interval - - self.msm_t = { - uGNSS.GPS: 1071, uGNSS.GLO: 1081, uGNSS.GAL: 1091, - uGNSS.SBS: 1101, uGNSS.QZS: 1111, uGNSS.BDS: 1121 - } - - self.ssr_t = { - uGNSS.GPS: 1057, uGNSS.GLO: 1063, uGNSS.GAL: 1240, - uGNSS.QZS: 1246, uGNSS.SBS: 1252, uGNSS.BDS: 1258 - } - - self.eph_t = { - uGNSS.GPS: 1019, uGNSS.GLO: 1020, uGNSS.BDS: 1042, - uGNSS.QZS: 1044, uGNSS.GAL: 1046 - } - - self.sc_t = {sCSSR.ORBIT: sCType.ORBIT, sCSSR.CLOCK: sCType.CLOCK, - sCSSR.MASK: sCType.MASK, sCSSR.CBIAS: sCType.CBIAS, - sCSSR.PBIAS: sCType.PBIAS, sCSSR.URA: sCType.URA, - sCSSR.GRID: sCType.TROP, sCSSR.STEC: sCType.STEC, - sRTCM.SSR_PBIAS: sCType.PBIAS, - sRTCM.SSR_TROP: sCType.TROP, - sRTCM.SSR_STEC: sCType.STEC, - } - - def get_ssr_sys(self, msgtype): - """ get system from ssr message type """ - if msgtype == 4076: - return self.sysref - else: - if msgtype >= 1057 and msgtype < 1063: - return uGNSS.GPS - elif msgtype >= 1063 and msgtype < 1069: - return uGNSS.GLO - elif msgtype >= 1240 and msgtype < 1246: - return uGNSS.GAL - elif msgtype >= 1246 and msgtype < 1252: - return uGNSS.QZS - elif msgtype >= 1252 and msgtype < 1258: - return uGNSS.SBS - elif msgtype >= 1258 and msgtype < 1264: - return uGNSS.BDS - elif msgtype >= 1265 and msgtype < 1271: # proposed phase bias - tbl_t = {1265: uGNSS.GPS, 1266: uGNSS.GLO, 1267: uGNSS.GAL, - 1268: uGNSS.QZS, 1269: uGNSS.SBS, 1270: uGNSS.BDS} - return tbl_t[msgtype] - # RTCM SSR test messages - elif msgtype >= 41 and msgtype < 47: # OBC - return uGNSS.GLO - elif msgtype in [62, 65, 68, 71, 74, 77]: # OBC - return uGNSS.GAL - elif msgtype in [63, 66, 69, 72, 75, 78]: # OBC - return uGNSS.BDS - elif msgtype in [64, 67, 70, 73, 76, 79]: # OBC - return uGNSS.QZS - elif msgtype >= 80 and msgtype < 85: # proposed satellite antenna - tbl_t = {80: uGNSS.GPS, 81: uGNSS.GLO, 82: uGNSS.GAL, - 83: uGNSS.BDS, 84: uGNSS.QZS} - return tbl_t[msgtype] - elif msgtype >= 85 and msgtype < 90: # proposed phase bias - tbl_t = {85: uGNSS.GPS, 86: uGNSS.GLO, 87: uGNSS.GAL, - 88: uGNSS.BDS, 89: uGNSS.QZS} - return tbl_t[msgtype] - elif msgtype >= 90 and msgtype < 95: # proposed phase bias - tbl_t = {90: uGNSS.GPS, 91: uGNSS.GLO, 92: uGNSS.GAL, - 93: uGNSS.BDS, 94: uGNSS.QZS} - return tbl_t[msgtype] - elif msgtype >= 96 and msgtype < 101: # regional iono - tbl_t = {96: uGNSS.GPS, 97: uGNSS.GLO, 98: uGNSS.GAL, - 99: uGNSS.BDS, 100: uGNSS.QZS} - elif msgtype in [60, 61, 95]: # METADATA, grid definition, trop - return uGNSS.NONE - elif msgtype in [11, 12, 13]: # RTCM SC-134 test messages - return uGNSS.NONE - else: - print(f"definition of {msgtype} is missing") - # return uGNSS.NONE - - return tbl_t[msgtype] - - def rsig2code(self, rsig): - """ convert rSigRnx to RTCM SSR code """ - gps_tbl = { - 0: uSIG.L1C, - 1: uSIG.L1P, - 2: uSIG.L1W, - 5: uSIG.L2C, - 6: uSIG.L2D, - 7: uSIG.L2S, - 8: uSIG.L2L, - 9: uSIG.L2X, - 10: uSIG.L2P, - 11: uSIG.L2W, - 14: uSIG.L5I, - 15: uSIG.L5Q, - 16: uSIG.L5X, - 17: uSIG.L1S, - 18: uSIG.L1L, - 19: uSIG.L1X, - } - glo_tbl = { - 0: uSIG.L1C, - 1: uSIG.L1P, - 2: uSIG.L2C, - 3: uSIG.L2P, - 4: uSIG.L4A, - 5: uSIG.L4B, - 6: uSIG.L6A, - 7: uSIG.L6B, - 10: uSIG.L3I, - 11: uSIG.L3Q, - } - - gal_tbl = { - 0: uSIG.L1A, - 1: uSIG.L1B, - 2: uSIG.L1C, - 3: uSIG.L1X, - 4: uSIG.L1Z, - 5: uSIG.L5I, - 6: uSIG.L5Q, - 7: uSIG.L5X, - 8: uSIG.L7I, - 9: uSIG.L7Q, - 10: uSIG.L7X, - 11: uSIG.L8I, - 12: uSIG.L8Q, - 13: uSIG.L8X, - 14: uSIG.L6A, - 15: uSIG.L6B, - 16: uSIG.L6C, - 17: uSIG.L6X, - 18: uSIG.L6Z, - } - - bds_tbl = { - 0: uSIG.L2I, - 1: uSIG.L2Q, - 2: uSIG.L2X, - 3: uSIG.L6I, - 4: uSIG.L6Q, - 5: uSIG.L6X, - 6: uSIG.L7I, - 7: uSIG.L7Q, - 8: uSIG.L7X, - 9: uSIG.L1D, - 10: uSIG.L1P, - 11: uSIG.L1X, - 12: uSIG.L5D, - 13: uSIG.L5P, - 14: uSIG.L5X, - 15: uSIG.L1A, - } - - qzs_tbl = { - 0: uSIG.L1C, - 1: uSIG.L1S, - 2: uSIG.L1C, - 3: uSIG.L2S, - 4: uSIG.L2L, - 5: uSIG.L2X, - 6: uSIG.L5I, - 7: uSIG.L5Q, - 8: uSIG.L5X, - 9: uSIG.L6S, - 10: uSIG.L6L, - 11: uSIG.L6X, - 12: uSIG.L1X, - 17: uSIG.L6E, - 19: uSIG.L1E, # TBD - } - - sbs_tbl = { - 0: uSIG.L1C, - 1: uSIG.L5I, - 2: uSIG.L5Q, - 3: uSIG.L5X, - } - - usig_tbl_ = { - uGNSS.GPS: gps_tbl, - uGNSS.GLO: glo_tbl, - uGNSS.GAL: gal_tbl, - uGNSS.BDS: bds_tbl, - uGNSS.QZS: qzs_tbl, - uGNSS.SBS: sbs_tbl, - } + self.subtype = sRTCM.SSR_GRID + i = self.decode_ssr_grid(msg, i) + elif self.msgtype in [1057, 41, 62, 63, 64]: + self.subtype = sCSSR.ORBIT + i = self.decode_cssr_orb(msg, i) + elif self.msgtype in [1058, 42, 65, 66, 67]: + self.subtype = sCSSR.CLOCK + i = self.decode_cssr_clk(msg, i) + elif self.msgtype in [1060, 44, 71, 72, 73]: + self.subtype = sCSSR.COMBINED + i = self.decode_cssr_comb(msg, i) + elif self.msgtype in [1061, 45, 74, 75, 76]: + self.subtype = sCSSR.URA + i = self.decode_cssr_ura(msg, i) + elif self.msgtype in [1062, 46, 77, 78, 79]: + self.subtype = sCSSR.CLOCK + i = self.decode_cssr_hclk(msg, i) + elif self.msgtype in [1059, 43, 68, 69, 70]: + self.subtype = sCSSR.CBIAS + i = self.decode_cssr_cbias(msg, i) + elif self.msgtype >= 80 and self.msgtype < 85: + self.subtype = sRTCM.SSR_SATANT + i = self.decode_ssr_satant(msg, i) + elif self.msgtype >= 85 and self.msgtype < 90: + if not self.mask_pbias: + self.subtype = sRTCM.SSR_PBIAS + i = self.decode_cssr_pbias(msg, i) + elif self.msgtype >= 90 and self.msgtype < 95: + self.subtype = sRTCM.SSR_PBIAS_EX + i = self.decode_ssr_pbias_ex(msg, i) + elif self.msgtype == 95: + self.subtype = sRTCM.SSR_TROP + i = self.decode_ssr_trop(msg, i) + elif self.msgtype >= 96 and self.msgtype < 101: + self.subtype = sRTCM.SSR_STEC + i = self.decode_ssr_iono(msg, i) - usig_tbl = usig_tbl_[rsig.sys] - dic = {usig_tbl[s]: s for s in usig_tbl} + # test messages for SC-134 + elif self.msgtype in [3, 2000]: # minimum integrity + self.subtype = sRTCM.INTEG_MIN + self.decode_integrity_min(msg, i) + elif self.msgtype in [4, 2005]: # extended integrity + self.subtype = sRTCM.INTEG_EXT + self.decode_integrity_ext(msg, i) + elif self.msgtype in [5, 2006]: # sis integrity/local error + self.subtype = sRTCM.INTEG_EXT_SIS + self.decode_integrity_ext_sis_local(msg, i) + elif self.msgtype in [6, 2007]: # primary sercice area + self.subtype = sRTCM.INTEG_PRI_AREA + self.decode_integrity_pri_service_area(msg, i) + elif self.msgtype == 2008: # extended sercice area + self.subtype = sRTCM.INTEG_EXT_AREA + self.decode_integrity_ext_service_area(msg, i) + elif self.msgtype in [7, 2051]: # quality indicator + self.subtype = sRTCM.INTEG_QUALITY + self.decode_integrity_quality(msg, i) + elif self.msgtype in [8, 2091]: # CNR/ACG SIS Monitoring + self.subtype = sRTCM.INTEG_CNR + self.decode_integrity_cnr_acg(msg, i) + elif self.msgtype == 2071: # satellite visibility map + self.subtype = sRTCM.INTEG_VMAP + self.decode_integrity_vmap(msg, i) + elif self.msgtype == 2072: # multipath map + self.subtype = sRTCM.INTEG_MMAP + self.decode_integrity_mmap(msg, i) + elif self.msgtype in [11, 2011]: # SSR integrity + self.subtype = sRTCM.INTEG_SSR + self.decode_integrity_ssr(msg, i) + elif self.msgtype == 12: # SSR integrity Iono + self.subtype = sRTCM.INTEG_SSR_IONO + self.decode_integrity_ssr(msg, i) + elif self.msgtype == 13: # SSR integrity Trop + self.subtype = sRTCM.INTEG_SSR_TROP + self.decode_integrity_ssr(msg, i) + elif self.msgtype == 54: # SSR integrity test msg + i = self.decode_sc134_test_header(msg, i) + if self.subtype == 9: + self.decode_integrity_vmap(msg, i) + elif self.subtype == 10: + self.decode_integrity_mmap(msg, i) + else: + self.subtype = -1 - return dic[rsig.sig] + if self.monlevel > 0 and self.fh is not None: + self.out_log(obs, eph, geph, seph) - def msm2rsig(self, sys: uGNSS, utyp: uTYP, ssig): - """ convert ssig to rSigRnx for MSM """ - gps_tbl = { - 2: uSIG.L1C, - 3: uSIG.L1P, - 4: uSIG.L1W, - 8: uSIG.L2C, - 9: uSIG.L2P, - 10: uSIG.L2W, - 15: uSIG.L2S, - 16: uSIG.L2L, - 17: uSIG.L2X, - 22: uSIG.L5I, - 23: uSIG.L5Q, - 24: uSIG.L5X, - 30: uSIG.L1S, - 31: uSIG.L1L, - 32: uSIG.L1X, - } - glo_tbl = { - 2: uSIG.L1C, - 3: uSIG.L1P, - 8: uSIG.L2C, - 9: uSIG.L2P, - 10: uSIG.L4A, - 11: uSIG.L4B, - 12: uSIG.L4X, - 13: uSIG.L6A, - 14: uSIG.L6X, - 15: uSIG.L3I, - 16: uSIG.L3Q, - 17: uSIG.L3X, - } + return i, obs, eph, geph, seph - gal_tbl = { - 2: uSIG.L1C, - 3: uSIG.L1A, - 4: uSIG.L1B, - 5: uSIG.L1X, - 6: uSIG.L1Z, - 8: uSIG.L6C, - 9: uSIG.L6A, - 10: uSIG.L6B, - 11: uSIG.L6X, - 12: uSIG.L6Z, - 14: uSIG.L7I, - 15: uSIG.L7Q, - 16: uSIG.L7X, - 18: uSIG.L8I, - 19: uSIG.L8Q, - 20: uSIG.L8X, - 22: uSIG.L5I, - 23: uSIG.L5Q, - 24: uSIG.L5X, - } - bds_tbl = { - 2: uSIG.L2I, - 3: uSIG.L2Q, - 4: uSIG.L2X, - 8: uSIG.L6I, - 9: uSIG.L6Q, - 10: uSIG.L6X, - 14: uSIG.L7I, - 15: uSIG.L7Q, - 16: uSIG.L7X, - 22: uSIG.L5D, - 23: uSIG.L5P, - 24: uSIG.L5X, - 25: uSIG.L7D, - 30: uSIG.L1D, - 31: uSIG.L1P, - 32: uSIG.L1X, - } +class rtcme(cssre, rtcmUtil): + """ class for RTCM message encoder """ - qzs_tbl = { - 2: uSIG.L1C, - 3: uSIG.L1E, - 9: uSIG.L6S, - 10: uSIG.L6L, - 11: uSIG.L6X, - 15: uSIG.L2S, - 16: uSIG.L2L, - 17: uSIG.L2X, - 22: uSIG.L5I, - 23: uSIG.L5Q, - 24: uSIG.L5X, - 30: uSIG.L1S, - 31: uSIG.L1L, - 32: uSIG.L1X, - } + def __init__(self): + super().__init__() + self.integ = Integrity() - sbs_tbl = { - 2: uSIG.L1C, - 22: uSIG.L5I, - 23: uSIG.L5Q, - 24: uSIG.L5X, - } + self.pid = 0 # SSR Provider ID + self.sid = 0 # SSR Solution Type - irn_tbl = { - 8: uSIG.L9A, - 22: uSIG.L5A, - } + self.mi = False # multiple message bit + self.iods = 0 # issue of data station + self.refid = 0 # reference station ID - usig_tbl_ = { - uGNSS.GPS: gps_tbl, - uGNSS.GLO: glo_tbl, - uGNSS.GAL: gal_tbl, - uGNSS.BDS: bds_tbl, - uGNSS.QZS: qzs_tbl, - uGNSS.SBS: sbs_tbl, - uGNSS.IRN: irn_tbl, - } + self.csi = 0 # clock streering indicator + self.eci = 0 # external clock indicator + self.si = 0 # smoothing indicator + self.smi = 0 # smoothing interval - usig_tbl = usig_tbl_[sys] - return rSigRnx(sys, utyp, usig_tbl[ssig]) + self.gtype = 0 + self.ofst = 0 + self.nm = 0 # number of metadata model + self.iyaw = False + self.iexpb = 0 + self.nlen = 0 # Hydrostatic Grid Point Residual Length DF+050 + self.mlen = 14 # Wet Grid Point Residual Length DF+051 + + # RTCM SSR grid parameters + self.ncol = 0 + self.nrow = 0 + self.dlat = 0 + self.dlon = 0 + self.gma = False + self.latmask = 0 + self.lonmask = 0 + self.egpmask = 0 def set_sync(self, msg, k): msg[k] = 0xd3 @@ -3849,32 +3760,14 @@ def set_body(self, msg, buff, k, len_): def round_i(self, v): return int(v+0.5) - def is_msmtype(self, msgtype): - """ check if the message type is MSM """ - for sys_ in self.msm_t.keys(): - if msgtype >= self.msm_t[sys_] and msgtype <= self.msm_t[sys_]+6: - return True - return False - - def msmtype(self, msgtype): - """ get system and msm type from message type """ - sys = uGNSS.NONE - msm = 0 - for sys_ in self.msm_t.keys(): - if msgtype >= self.msm_t[sys_] and msgtype <= self.msm_t[sys_]+6: - sys = sys_ - msm = msgtype-self.msm_t[sys_]+1 - break - return sys, msm + def ival(self, y, nbit, scl): + """ float to int conversion with scaling """ + if np.isnan(y): + u = -(1 << (nbit-1)) # -lim: data is not available + else: + u = round(y/scl) - def sat2svid(self, sat): - """ convert sat number to svid """ - sys, svid = sat2prn(sat) - if sys == uGNSS.QZS: - svid -= uGNSS.MINPRNQZS-1 - elif sys == uGNSS.SBS: - svid -= uGNSS.MINPRNSBS-1 - return svid + return u def encode_msm_time(self, sys, time): """ decode msm time """ @@ -3948,7 +3841,7 @@ def encode_msm(self, msg, obs, i): for i in range(nobs): for j in range(nsig): k = 0 # TBD - freq = obs.sig.frequency() + # freq = obs.sig.frequency() r[k] = self.round_i( obs.P[i, j]/(rms*rCST.P2_10))*rms*rCST.P2_10 @@ -4096,11 +3989,10 @@ def encode_ssr_grid(self, msg, i): i += 30 self.inet = self.gid + grid = self.grid[self.grid['nid'] == self.inet] + self.ng = grid.shape[0] if self.gtype == 0: # grid type 0 - grid = self.grid[self.grid['nid'] == self.gid] - self.ng = grid.shape[0] - gid, ofst, lat, lon, alt = grid[0] lat_i = int(lat/1e-3) @@ -4132,28 +4024,52 @@ def encode_ssr_grid(self, msg, i): i += 36 elif self.gtype == 1: # grid type 1 - grid = self.grid(self.grid['nid'] == self.inet) - gid, ofst, lat, lon, alt = grid[0] - lat_ = int(lat/1e-3) - lon_ = int(lon/1e-3) + lat_i = int(lat/1e-3) + lon_i = int(lon/1e-3) - bs.pack_into('s18s19u12u8', msg, i, lat_, lon_, self.ofst, self.ng) + bs.pack_into('s18s19u12u8', msg, i, lat_i, lon_i, + self.ofst, self.ng) i += 57 - for k in range(self.ng): - gid, ofst, lat, lon, _ = grid[k] - dlat_ = lat-lat_ - dlon_ = lon-lon_ - lat_ = lat - lon_ = lon + for k in range(self.ng-1): + gid, ofst, lat_, lon_, _ = grid[k] + dlat = lat_-lat + dlon = lon_-lon + lat = lat_ + lon = lon_ - dlat = int(dlat_/1e-3) - dlon = int(dlon_/1e-3) - bs.pack_into('s13s14', msg, i, dlat, dlon) + dlat_i = int(dlat/1e-3) + dlon_i = int(dlon/1e-3) + bs.pack_into('s13s14', msg, i, dlat_i, dlon_i) i += 27 + elif self.gtype == 2: # grid type 2 + gid, ofst, lat0, lon0, alt = grid[0] + + lat_i = int(lat0/1e-3) + lon_i = int(lon0/1e-3) + dlat_i = int(self.dlat/0.01) + dlon_i = int(self.dlon/0.01) + + bs.pack_into('s18s19u6u6u9u10b1', msg, i, lat_i, lon_i, + self.nrow, self.ncol, dlat_i, dlon_i, self.gma) + i += 69 + + if self.gma: + bs.pack_into('u'+str(self.nrow), msg, i, self.latmask) + i += self.nrow + bs.pack_into('u'+str(self.ncol), msg, i, self.lonmask) + i += self.ncol + + ilat, nlat = self.decode_mask(self.latmask, self.nrow, 0) + ilon, nlon = self.decode_mask(self.lonmask, self.ncol, 0) + n = nlat*nlon + bs.pack_into('u'+str(n), msg, i, self.egpmask) + i += n + # iex, nex = self.decode_mask(egpmask, n, 0) + return i def encode_head(self, msg, i, sys=uGNSS.NONE): @@ -4237,17 +4153,17 @@ def encode_orb_sat(self, msg, i, sat, inet=0): bs.pack_into('u'+str(blen), msg, i, self.lc[inet].iode[sat]) i += blen - dx = int(self.lc[inet].dorb[sat][0]/0.1e-3) - dy = int(self.lc[inet].dorb[sat][1]/0.4e-3) - dz = int(self.lc[inet].dorb[sat][2]/0.4e-3) + dx = self.ival(self.lc[inet].dorb[sat][0], 22, 0.1e-3) + dy = self.ival(self.lc[inet].dorb[sat][1], 20, 0.4e-3) + dz = self.ival(self.lc[inet].dorb[sat][2], 20, 0.4e-3) bs.pack_into('s22s20s20', msg, i, dx, dy, dz) i += 62 if self.lc[inet].ddorb is not None: - ddx = int(self.lc[inet].ddorb[sat][0]/1e-6) - ddy = int(self.lc[inet].ddorb[sat][1]/4e-6) - ddz = int(self.lc[inet].ddorb[sat][2]/4e-6) + ddx = self.ival(self.lc[inet].ddorb[sat][0], 21, 1e-6) + ddy = self.ival(self.lc[inet].ddorb[sat][1], 19, 4e-6) + ddz = self.ival(self.lc[inet].ddorb[sat][2], 19, 4e-6) else: ddx, ddy, ddz = 0, 0, 0 @@ -4259,10 +4175,11 @@ def encode_orb_sat(self, msg, i, sat, inet=0): def encode_clk_sat(self, msg, i, sat, inet=0): """ encoder clock correction of cssr """ - dclk = int(self.lc[inet].dclk[sat]/0.1e-3) + dclk = self.ival(self.lc[inet].dclk[sat], 22, 0.1e-3) + if self.lc[inet].ddclk is not None: - ddclk = int(self.lc[inet].ddclk[sat]/0.4e-3) - dddclk = int(self.lc[inet].dddclk[sat]/4e-6) + ddclk = self.ival(self.lc[inet].ddclk[sat], 21, 0.4e-3) + dddclk = self.ival(self.lc[inet].dddclk[sat], 27, 4e-6) else: ddclk, dddclk = 0, 0 @@ -4273,7 +4190,7 @@ def encode_clk_sat(self, msg, i, sat, inet=0): def encode_hclk_sat(self, msg, i, sat, inet=0): """ encoder high-rate clock correction of cssr """ if self.lc[inet].hclk is not None: - hclk = int(self.lc[inet].hclk[sat]/0.1e-3) + hclk = self.ival(self.lc[inet].hclk[sat], 22, 0.1e-3) else: hclk = 0 @@ -4320,7 +4237,10 @@ def encode_cssr_ura(self, msg, i, inet=0): if sys_ != sys: continue i = self.encode_sat(msg, i, sat) - cls_, val_ = self.quality2qi(ura[sat]) + if np.isnan(ura[sat]): + cls_, val_ = 0, 0 + else: + cls_, val_ = self.quality2qi(ura[sat]) bs.pack_into('u3u3', msg, i, cls_, val_) i += 6 @@ -4382,7 +4302,7 @@ def encode_cssr_pbias(self, msg, i, inet=0): for rsig_ in pbias: - pb = int(pbias[rsig_]/1e-4) + pb = self.ival(pbias[rsig_], 20, 1e-4) code = self.rsig2code(rsig_) if sat in self.lc[inet].si: si = self.lc[inet].si[sat][rsig_] @@ -4417,12 +4337,12 @@ def encode_ssr_trop(self, msg, i, inet=0): ah, bh, ch = self.lc[inet].maph aw, bw, cw = self.lc[inet].mapw - dah = int((ah-0.00118)/2.5e-7) - dbh = int((bh-0.00298)/5e-6) - dch = int((ch-0.0682)/2e-4) - daw = int((aw-0.000104)/1e-6) - dbw = int((bw-0.0015)/2.5e-5) - dcw = int((cw-0.048)/2e-3) + dah = self.ival((ah-0.00118), 11, 2.5e-7) + dbh = self.ival((bh-0.00298), 9, 5e-6) + dch = self.ival((ch-0.0682), 9, 2e-4) + daw = self.ival((aw-0.000104), 13, 1e-6) + dbw = self.ival((bw-0.0015), 6, 2.5e-5) + dcw = self.ival((cw-0.048), 5, 2e-3) # atmospheric model part bs.pack_into( @@ -4433,13 +4353,13 @@ def encode_ssr_trop(self, msg, i, inet=0): ct = self.lc[inet].ct # hydrostatic - ct00h = int((ct[0, 0]-2.3)/0.1e-3) - ct01h = int(ct[0, 1]/0.01e-3) - ct10h = int(ct[0, 2]/0.01e-3) + ct00h = self.ival((ct[0, 0]-2.3), 13, 0.1e-3) + ct01h = self.ival(ct[0, 1], 15, 0.01e-3) + ct10h = self.ival(ct[0, 2], 15, 0.01e-3) # wet - ct00w = int((ct[1, 0]-0.252)/0.1e-3) - ct01w = int(ct[1, 1]/0.01e-3) - ct10w = int(ct[1, 2]/0.01e-3) + ct00w = self.ival((ct[1, 0]-0.252), 13, 0.1e-3) + ct01w = self.ival(ct[1, 1], 15, 0.01e-3) + ct10w = self.ival(ct[1, 2], 15, 0.01e-3) bs.pack_into('s13s15s15', msg, i, ct00h, ct10h, ct01h) i += 43 @@ -4463,11 +4383,11 @@ def encode_ssr_trop(self, msg, i, inet=0): for k in range(ofst, ofst+ng): if n > 0: - rh_ = int(rh[k]/0.1e-3) + rh_ = self.ival(rh[k], n, 0.1e-3) bs.pack_into(f's{n}', msg, i, rh_) i += n if m > 0: - rw_ = int(rw[k]/0.1e-3) + rw_ = self.ival(rw[k], m, 0.1e-3) bs.pack_into(f's{m}', msg, i, rw_) i += m @@ -4516,12 +4436,12 @@ def encode_ssr_iono(self, msg, i, inet=0): for sat_ in sat: ci = self.lc[inet].ci[sat_] - c00i = int(ci[0]/0.01) + c00i = self.ival(ci[0], 17, 0.01) bs.pack_into('s17', msg, i, c00i) i += 17 if pgi: - c01i = int(ci[1]/1e-3) - c10i = int(ci[2]/1e-3) + c01i = self.ival(ci[1], 18, 1e-3) + c10i = self.ival(ci[2], 18, 1e-3) bs.pack_into('s18s18', msg, i, c01i, c10i) i += 36 From 46a79d3d570a8d2f995d730e85120473dca61f25 Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sun, 25 Jan 2026 19:08:27 +0900 Subject: [PATCH 30/48] - fixed error. --- src/cssrlib/gnss.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cssrlib/gnss.py b/src/cssrlib/gnss.py index c82f6ba..c618c8f 100644 --- a/src/cssrlib/gnss.py +++ b/src/cssrlib/gnss.py @@ -1288,7 +1288,7 @@ def sys2char(sys): gnss_tbl = {uGNSS.GPS: 'G', uGNSS.GLO: 'R', uGNSS.GAL: 'E', uGNSS.BDS: 'C', uGNSS.QZS: 'J', uGNSS.SBS: 'S', uGNSS.IRN: 'I'} - if len(sys) == 1: + if type(sys) == uGNSS: s = '?' if sys not in gnss_tbl else gnss_tbl[sys] else: s = [] From cd12353da69d0ac80fd754f561e4110b16d955ee Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sun, 25 Jan 2026 23:27:33 +0900 Subject: [PATCH 31/48] - update sc134 messages. --- src/cssrlib/rtcm.py | 251 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 217 insertions(+), 34 deletions(-) diff --git a/src/cssrlib/rtcm.py b/src/cssrlib/rtcm.py index 134faf5..6ed2587 100644 --- a/src/cssrlib/rtcm.py +++ b/src/cssrlib/rtcm.py @@ -21,7 +21,7 @@ from cssrlib.gnss import uGNSS, sat2id, gpst2time, timediff, time2str, sat2prn from cssrlib.gnss import uTYP, uSIG, rSigRnx, bdt2time, bdt2gpst, glo2time from cssrlib.gnss import time2bdt, gpst2bdt, rCST, time2gpst, utc2gpst, timeadd -from cssrlib.gnss import gtime_t, sys2str, gpst2utc +from cssrlib.gnss import gtime_t, sys2str, gpst2utc, sys2char from crccheck.crc import Crc24LteA from enum import IntEnum from cssrlib.gnss import Eph, Obs, Geph, Seph @@ -195,6 +195,22 @@ def __init__(self): self.sig_ob_c = {} # Ovebounding stdev of CP DFi038 self.ob_c = {} # Overbounding bias of long-term CP bias DFi039 + self.sig_pd = {} + self.sig_cd = {} + self.cbr = {} + self.sig_dp = {} + self.idx_dp = {} + + self.oc = {} + self.sig_ion = {} + self.idx_ion = {} + self.sig_trp = {} + self.idx_trp = {} + + self.cnr = {} + self.agc_t = {} + self.agc = {} + self.nsys = 0 self.nsat = {} self.sys_t = None @@ -222,6 +238,9 @@ class rtcmUtil: uGNSS.QZS: 1044, uGNSS.GAL: 1046 } + mt_integ_t = [2000, 2005, 2006, 2007, + 2008, 2011, 2051, 2071, 2072, 2091] + sc_t = {sCSSR.ORBIT: sCType.ORBIT, sCSSR.CLOCK: sCType.CLOCK, sCSSR.MASK: sCType.MASK, sCSSR.CBIAS: sCType.CBIAS, sCSSR.PBIAS: sCType.PBIAS, sCSSR.URA: sCType.URA, @@ -1689,12 +1708,137 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write("\n") self.fh.flush() - if self.msgtype == 54: # RTCM SC134 integrity messages - self.fh.write(f" Multiple Message Sequence :{self.integ.seq:}\n") + if self.msgtype == 54 or self.msgtype in self.mt_integ_t: # RTCM SC134 integrity messages + # self.fh.write(f" Multiple Message Sequence :{self.integ.seq:}\n") self.fh.write(" Message Type: {:4d}-{:-2d}\n". format(self.msgtype, self.subtype)) - if self.subtype == 9: # MT54-9 VMAP + if self.subtype == sRTCM.INTEG_MIN: + self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") + self.fh.write(f" Provider Id: {self.integ.pid}\n") + + self.fh.write(f" Validity Period: {self.integ.vp}\n") + self.fh.write(f" Update rate interval: {self.integ.uri}\n") + + self.fh.write(f" Issue of Sat Mask: {self.integ.iod_sys}\n") + self.fh.write(" Integrity Status:\n") + for sys in self.integ.sts: + self.fh.write( + f" {sys2char(sys)} {format(self.integ.sts[sys], '064b')}\n") + self.fh.write(" Monitoring Status:\n") + for sys in self.integ.mst: + self.fh.write( + f" {sys2char(sys)} {self.integ.mst[sys]}\n") + self.fh.write(f" Integrity Fault Source: {self.integ.src}\n") + self.fh.write(f" sigmask: {self.integ.sigmask}\n") + self.fh.write(f" IOD freq: {self.integ.iod_sig}\n") + self.fh.write(f" Integrity Status: {self.integ.sig_ists}\n") + self.fh.write(f" Monitoring Status: {self.integ.sig_msts}\n") + + elif self.subtype == sRTCM.INTEG_EXT: # MT2005 + self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") + self.fh.write(f" Provider Id: {self.integ.pid}\n") + self.fh.write(f" Area ID: {self.integ.aid}\n") + self.fh.write(f" Integrity Level: {self.integ.ilvl}\n") + self.fh.write(f" TTT: {self.integ.ttt}\n") + self.fh.write(f" Solution Type: {self.integ.stype}\n") + + self.fh.write(f" Validity Period: {self.integ.vp}\n") + self.fh.write(f" Update rate interval: {self.integ.uri}\n") + + self.fh.write(f" Paug: {self.integ.Paug}\n") + self.fh.write(f" IOD param: {self.integ.iod_p}\n") + self.fh.write(f" Integrity/Continuity Flag: {self.integ.fc}\n") + + self.fh.write(f" mfd single: {self.integ.mfd_s}\n") + self.fh.write(f" mfd constellation: {self.integ.mfd_c}\n") + self.fh.write(f" tau_p: {self.integ.tau_p}\n") + self.fh.write(f" sig_p: {self.integ.sig_p}\n") + self.fh.write(f" tau_c: {self.integ.tau_c}\n") + self.fh.write(f" sig_c: {self.integ.sig_c}\n") + self.fh.write(f" Pa_cp: {self.integ.Pa_cp}\n") + self.fh.write(f" Pa_cc: {self.integ.Pa_cc}\n") + + self.fh.write(f" Pa_sp: {self.integ.Pa_sp}\n") + self.fh.write(f" sig_a: {self.integ.sig_a}\n") + self.fh.write(f" Pa_sc: {self.integ.Pa_sc}\n") + self.fh.write(f" ob_p: {self.integ.ob_p}\n") + self.fh.write(f" sig_ob_c: {self.integ.sig_ob_c}\n") + self.fh.write(f" ob_c: {self.integ.ob_c}\n") + + elif self.subtype == sRTCM.INTEG_EXT_SIS: # MT2006 + self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") + self.fh.write(f" Provider Id: {self.integ.pid}\n") + self.fh.write(f" Area-ID: {self.integ.aid}\n") + self.fh.write(f" IOD param: {self.integ.iod_ip}\n") + + self.fh.write(f" Validity Period: {self.integ.vp}\n") + self.fh.write(f" Update rate interval: {self.integ.uri}\n") + self.fh.write(f" Integrity/Continuity Flag: {self.integ.fc}\n") + self.fh.write(f" bias (const/freq): {self.integ.bias}\n") + + self.fh.write(f" sig_pd: {self.integ.sig_pd}\n") + self.fh.write(f" sig_cd: {self.integ.sig_cd}\n") + self.fh.write(f" cbr: {self.integ.cbr}\n") + self.fh.write(f" sig_dp: {self.integ.sig_dp}\n") + self.fh.write(f" idx_dp: {self.integ.idx_dp}\n") + + self.fh.write(f" orbit/clock: {self.integ.oc}\n") + self.fh.write(f" stdev iono: {self.integ.sig_ion}\n") + self.fh.write(f" idx iono: {self.integ.idx_ion}\n") + self.fh.write(f" stdev trop: {self.integ.sig_trp}\n") + self.fh.write(f" idx trop: {self.integ.idx_trp}\n") + + elif self.subtype == sRTCM.INTEG_PRI_AREA: # MT2007 + self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") + self.fh.write(f" Provider ID: {self.integ.pid}\n") + self.fh.write(f" Area ID: {self.integ.aid}\n") + self.fh.write(f" IOD area: {self.integ.iod_sa}\n") + self.fh.write(f" Validity Period: {self.integ.vp}\n") + self.fh.write(f" Update rate interval: {self.integ.uri}\n") + self.fh.write(f" Continuation Flag: {self.integ.cf}\n") + self.fh.write(f" Seq: {self.integ.seq}\n") + self.fh.write(f" Area Point (lat, lon): {self.integ.pos}\n") + + elif self.subtype == sRTCM.INTEG_EXT_AREA: # MT2008 + self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") + self.fh.write(f" Provider ID: {self.integ.pid}\n") + self.fh.write(f" Area ID: {self.integ.aid}\n") + self.fh.write(f" IOD area: {self.integ.iod_sa}\n") + self.fh.write(f" Validity Period: {self.integ.vp}\n") + self.fh.write(f" Update rate interval: {self.integ.uri}\n") + self.fh.write(f" Service Area Type: {self.integ.atype}\n") + + self.fh.write(f" Continuation Flag: {self.integ.cf}\n") + self.fh.write(f" Seq: {self.integ.seq}\n") + + self.fh.write(f" f_ir: {self.integ.f_ir}\n") + self.fh.write(f" f_ttd: {self.integ.f_ttd}\n") + self.fh.write(f" f_ext: {self.integ.f_ext}\n") + self.fh.write(f" f_exs: {self.integ.f_exs}\n") + + self.fh.write(f" Area Point (lat/lon): {self.integ.pos}\n") + + elif self.subtype == sRTCM.INTEG_QUALITY: # MT2051 + self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") + self.fh.write(f" Provider Id: {self.integ.pid}\n") + self.fh.write(f" Validity Period: {self.integ.vp}\n") + self.fh.write(f" Update rate interval: {self.integ.uri}\n") + self.fh.write(f" Network ID: {self.integ.nid}\n") + self.fh.write(f" Quality Indicator: {self.integ.qi}\n") + + elif self.subtype == sRTCM.INTEG_CNR: # MT2091 + + self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") + self.fh.write(f" Provider ID: {self.integ.pid}\n") + self.fh.write(f" Area ID: {self.integ.aid}\n") + self.fh.write(f" Update rate interval: {self.integ.uri}\n") + + self.fh.write(f" CNR: {self.integ.cnr}\n") + self.fh.write(f" AGC type: {self.integ.agc_t}\n") + self.fh.write(f" AGC: {self.integ.agc}\n") + + elif self.subtype == sRTCM.INTEG_VMAP: # 2071 self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") self.fh.write(f" Number of Area Points: {self.integ.narea:}\n") self.fh.write(" Boundary Points (lat, long, alt, naz):\n") @@ -1707,7 +1851,8 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): for j in range(self.integ.naz[k]): self.fh.write(" {:3.0f}\t{:3.0f}\n".format (self.integ.azel[k][j][0], self.integ.azel[k][j][1])) - elif self.subtype == 10: + + elif self.subtype == sRTCM.INTEG_MMAP: # 2072 model = self.integ.mm_model_t[self.integ.mm_id] self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") self.fh.write(f" Number of Area Points: {self.integ.narea:}\n") @@ -2847,6 +2992,9 @@ def decode_integrity_min(self, msg, i): pid, tow, mask_sys = bs.unpack_from('u12u30u16', msg, i) i += 58 + self.integ.pid = pid + self.integ.tow = tow*1e-3 + # constellation integirity status DFi029 # constellation monitoring status DFi030 c_integ, c_status, vp, uri = bs.unpack_from('u16u16u4u16', msg, i) @@ -2882,9 +3030,12 @@ def decode_integrity_min(self, msg, i): svid_t, nsat = self.decode_mask(mask_sat, 64) + ofst = 193 if sys == uGNSS.QZS else 1 + prn_m, _ = self.decode_mask(mst, 64, ofst) + iod_sys[sys] = iod sts_tbl[sys] = sts - mst_tbl[sys] = mst + mst_tbl[sys] = prn_m src_tbl[sys] = src iod_sig[sys] = {} @@ -2909,9 +3060,6 @@ def decode_integrity_min(self, msg, i): sig_msts[sys][sat] = bs.unpack_from('u8', msg, i)[0] i += 8 - self.integ.pid = pid - self.integ.tow = tow - self.integ.iod_sys = iod_sys self.integ.sts = sts_tbl self.integ.mst = mst_tbl @@ -2937,9 +3085,11 @@ def decode_integrity_ext(self, msg, i): msg, i) i += 82 + self.integ.tow = tow*1e-3 self.integ.pid = pid - self.integ.tow = tow + self.integ.aid = area self.integ.ilvl = ilvl + self.integ.ttt = ttt self.integ.stype = stype # GNSS Constellation Mask DFi013 @@ -2953,9 +3103,9 @@ def decode_integrity_ext(self, msg, i): self.integ.uri = uri # update rate interval DFi067 (0.1) # Paug Bundling flag DFi066 - f_paug = bs.unpack_from('u1', msg, i)[0] + f_paug = bs.unpack_from('b1', msg, i)[0] i += 1 - if f_paug == 1: + if f_paug: # Paug Augmentation System Probability Falut DFi049 self.integ.Paug = bs.unpack_from('u4', msg, i)[0] i += 4 @@ -2964,7 +3114,7 @@ def decode_integrity_ext(self, msg, i): # 0: DFi040-045 are valid only for integrity monitoring # 1: DFi040-045 are valid also for continuity monitoring # integrity parameter IOD DFi006 - self.integ.fc, self.integ.iod_p = bs.unpack_from('u1u6', msg, i) + self.integ.fc, self.integ.iod_p = bs.unpack_from('b1u6', msg, i) i += 7 # constellation specific part @@ -2986,7 +3136,8 @@ def decode_integrity_ext(self, msg, i): self.integ.tau_cp = {} self.integ.tau_cc = {} - self.integ.uri = {} + self.integ.Pa_cp = {} + self.integ.Pa_cc = {} for sys_ in sys_t: sys = self.integ.sys_tbl[sys_] @@ -3005,12 +3156,12 @@ def decode_integrity_ext(self, msg, i): # Probability DFi047 # Multiple Satellite Carrier Phase Augmentation Message Fault # Probability DFi035 - tau_p, sig_p, tau_c, sig_c, tau_cp, tau_cc = bs.unpack_from( + tau_p, sig_p, tau_c, sig_c, Pa_cp, Pa_cc = bs.unpack_from( 'u4u5u4u5u4u4', msg, i) i += 26 # GNSS satellite mask DFi009 - mask_sat = bs.unpack_from('u64', msg, i) + mask_sat = bs.unpack_from('u64', msg, i)[0] i += 64 self.integ.mfd_s[sys] = mfd_s @@ -3022,8 +3173,15 @@ def decode_integrity_ext(self, msg, i): self.integ.sig_p[sys] = sig_p self.integ.sig_c[sys] = sig_c - self.integ.tau_cp[sys] = tau_cp - self.integ.tau_cc[sys] = tau_cc + self.integ.Pa_cp[sys] = Pa_cp + self.integ.Pa_cc[sys] = Pa_cc + + self.integ.Pa_sp[sys] = {} + self.integ.sig_a[sys] = {} + self.integ.Pa_sc[sys] = {} + self.integ.ob_p[sys] = {} + self.integ.sig_ob_c[sys] = {} + self.integ.ob_c[sys] = {} ofst = 193 if sys == uGNSS.QZS else 1 prn_t, nsat = self.decode_mask(mask_sat, 64, ofst=ofst) @@ -3066,18 +3224,29 @@ def decode_integrity_ext_sis_local(self, msg, i): tow, iod_ip, pid, area = bs.unpack_from('u30u6u12u10', msg, i) i += 58 + self.integ.tow = tow*1e-3 + self.integ.iod_ip = iod_ip + self.integ.pid = pid + self.integ.aid = area + # GNSS Constellation Mask DFi013 # Validity Period DFi065 (0-15) # update rate interval DFi067 (0.1) # Time correlation integrity/continuity flag DFi137 - mask_sys, vp, uri, fc = bs.unpack_from('u16u4u16u1', msg, i) + mask_sys, vp, uri, fc = bs.unpack_from('u16u4u16b1', msg, i) i += 37 + self.integ.vp = vp + self.integ.uri = uri + self.integ.fc = fc + # Bounding inter-constellation bias error parameter DFi132 # Bounding inter-frequency bias error parameter DFi136 bias_ic, bias_if = bs.unpack_from('u3u3', msg, i) i += 6 + self.integ.bias = [bias_ic, bias_if] + sys_t, nsys = self.decode_mask(mask_sys, 16, ofst=0) self.integ.vp = vp # Validity Period DFi065 (0-15) self.integ.uri = uri # update rate interval DFi067 (0.1) @@ -3152,6 +3321,12 @@ def decode_integrity_pri_service_area(self, msg, i): 'u30u6u12u10u4', msg, i) i += 62 + self.integ.iod_sa = iod_sa + self.integ.tow = tow*1e-3 + self.integ.pid = pid + self.integ.aid = area + self.integ.vp = vp + # augmentation integrity level DFi026 # GNSS Constellation Mask DFi013 @@ -3162,11 +3337,10 @@ def decode_integrity_pri_service_area(self, msg, i): uri, atype, cf, seq = bs.unpack_from('u16u2u1u5', msg, i) i += 24 - self.integ.iod_sa = iod_sa - self.integ.tow = tow - self.integ.pid = pid - self.integ.vp = vp self.integ.uri = uri + self.integ.atype = atype + self.integ.cf = cf + self.integ.seq = seq if atype == 1: # Validity Area Parameters defined in Table 10.3-5 # number of area points DFi201 @@ -3176,16 +3350,20 @@ def decode_integrity_pri_service_area(self, msg, i): for k in range(narea): # Area Point - Lat DFi202 # Area Point - Lon DFi203 - lat, lon = bs.unpack_from('s34s35', msg, i) + lat_i, lon_i = bs.unpack_from('s34s35', msg, i) i += 69 + lat = self.sval(lat_i, 34, 0.000000011) + lon = self.sval(lon_i, 35, 0.000000011) self.integ.pos[k, :] = [lat, lon, 0] elif atype == 0: # Validity Radius Data defined in Table 10.3-6 # Area Point - Lat DFi202 # Area Point - Lon DFi203 # Validity Radius DF057 - lat, lon, r = bs.unpack_from('s34s35u20', msg, i) + lat_i, lon_i, r = bs.unpack_from('s34s35u20', msg, i) i += 89 + lat = self.sval(lat_i, 34, 0.000000011) + lon = self.sval(lon_i, 35, 0.000000011) self.integ.pos = [lat, lon, r] return i @@ -3215,7 +3393,7 @@ def decode_integrity_ext_service_area(self, msg, i): f_ir, f_ttd, f_ext, f_exs = bs.unpack_from('u2u2u3u3', msg, i) i += 10 - self.integ.tow = tow + self.integ.tow = tow*1e-3 self.integ.iod_sa = iod_sa self.integ.pid = pid self.integ.aid = area @@ -3238,16 +3416,20 @@ def decode_integrity_ext_service_area(self, msg, i): for k in range(narea): # Area Point - Lat DFi202 # Area Point - Lon DFi203 - lat, lon = bs.unpack_from('s34s35', msg, i) + lat_i, lon_i = bs.unpack_from('s34s35', msg, i) i += 69 + lat = self.sval(lat_i, 34, 0.000000011) + lon = self.sval(lon_i, 35, 0.000000011) self.integ.pos[k, :] = [lat, lon, 0] elif atype == 0: # Validity Radius Data # Area Point - Lat DFi202 # Area Point - Lon DFi203 # Validity Radius DF057 - lat, lon, r = bs.unpack_from('s34s35u20', msg, i) + lat_i, lon_i, r = bs.unpack_from('s34s35u20', msg, i) i += 89 + lat = self.sval(lat_i, 34, 0.000000011) + lon = self.sval(lon_i, 35, 0.000000011) self.integ.pos = [lat, lon, r] return i @@ -3267,15 +3449,15 @@ def decode_integrity_quality(self, msg, i): uri, nid, mask_q = bs.unpack_from('u16u8u8', msg, i) i += 32 - self.integ.tow = tow + self.integ.tow = tow*1e-3 self.integ.pid = pid self.integ.vp = vp self.integ.uri = uri self.integ.nid = nid - idx_q, np = self.decode_mask(mask_q, 8, ofst=0) - self.integ.qi = np.zeros(np) - for k in range(np): + idx_q, np_ = self.decode_mask(mask_q, 8, ofst=0) + self.integ.qi = np.zeros(np_) + for k in range(np_): self.integ.qi[k] = bs.unpack_from('u8', msg, i)[0] i += 8 return i @@ -3291,9 +3473,10 @@ def decode_integrity_cnr_acg(self, msg, i): pid, tow, uri, area, mask_c = bs.unpack_from('u12u30u16u10u16', msg, i) i += 84 - self.integ.tow = tow + self.integ.tow = tow*1e-3 self.integ.pid = pid self.integ.uri = uri + self.integ.aid = area # constellation specific part sys_t, nsys = self.decode_mask(mask_c, 16, ofst=0) @@ -3324,7 +3507,7 @@ def decode_integrity_cnr_acg(self, msg, i): for sig in sig_t: # CNR carrier to noise ratio DFi133 - cnr, agc_t, agc = bs.unpack_from('u8u1u8', msg, i)[0] + cnr, agc_t, agc = bs.unpack_from('u8u1u8', msg, i) i += 17 self.integ.cnr[sys][sat][sig] = cnr self.integ.agc_t[sys][sat][sig] = agc_t From 03a6815b54050ff958c1906065135cf8bf65d8bc Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Tue, 27 Jan 2026 22:46:39 +0900 Subject: [PATCH 32/48] update output of SC134 messages --- src/cssrlib/rtcm.py | 532 ++++++++++++++++++++++++++++++++------------ 1 file changed, 384 insertions(+), 148 deletions(-) diff --git a/src/cssrlib/rtcm.py b/src/cssrlib/rtcm.py index 6ed2587..c601d68 100644 --- a/src/cssrlib/rtcm.py +++ b/src/cssrlib/rtcm.py @@ -83,7 +83,7 @@ class Integrity(): 4: uGNSS.QZS, 5: uGNSS.IRN} # Mean Failure Duration (MFD) of the augmentation system indicator (DFi034) - mfd_t = [5, 10, 30, 60, 120, 600, 900, 1800, 3600, 7200, 10800, 21600, + mfd_s_t = [5, 10, 30, 60, 120, 600, 900, 1800, 3600, 7200, 10800, 21600, 43200, 86400, 604800, -1] # overbounding standard deviation indicator (DFi036) sig_ob_c_t = [0.02, 0.04, 0.06, 0.08, 0.10, 0.12, 0.14, 0.16, 0.18, 0.20, @@ -102,7 +102,7 @@ class Integrity(): b_ob_c_t = [0.0, 0.01, 0.02, 0.03, 0.04, 0.05, 0.075, 0.10, 0.20, 0.40, 0.60, 0.80, 1.0, 3.0, 7.0, -1] # duration of augmentation system (MFD) (DFi040) - mfd_t = [0, 5, 10, 30, 60, 90, 300, 600, 1800, 3600, 7200, 14400, 28800, + mfd_c_t = [0, 5, 10, 30, 60, 90, 300, 600, 1800, 3600, 7200, 14400, 28800, 57600, 115200, -1] # correlation time of pseudorange error indicator (DFi044) tau_c_t = [0, 5, 10, 20, 30, 45, 60, 120, 180, 300, 600, 1200, 3600, 7200, @@ -111,33 +111,33 @@ class Integrity(): tau_p_t = [0, 2, 5, 10, 20, 30, 45, 60, 120, 180, 240, 300, 600, 1800, 7200, -1] # probability indicator (DFi035, DFi046, DFi047, DFi048, DFi049) - tau_p_t = [10**-11, 10**-10, 10**-9, 10**-8, 10**-7, 10**-6.2, 10**-6, - 10**-5.2, 10**-5, 10**-4.2, 10**-4, 10**-3.3, 10**-3, 10**-2.3, - 10**-2, -1] + P_t = [10**-11, 10**-10, 10**-9, 10**-8, 10**-7, 10**-6.2, 10**-6, + 10**-5.2, 10**-5, 10**-4.2, 10**-4, 10**-3.3, 10**-3, 10**-2.3, + 10**-2, -1] # TTT_comm indicator DFi050 # ttt_comm_t = 0.1*(i+1), 127 is not provided - # stdev indicator of bound on rate of change of PR error DFi120 - sig_rr_t = [1e-8, 1e-7, 1e-6, 1e-5, 1e-4, 1e-3, 3e-3, 6e-3, 1e-2, 3e-2, + # Bound on rate of change of PR error standard deviation DFi120 + sig_cd_t = [1e-8, 1e-7, 1e-6, 1e-5, 1e-4, 1e-3, 3e-3, 6e-3, 1e-2, 3e-2, 6e-2, 0.10, 0.50, 5.0, 0, -1] - # stdev indicator of bound on rate of change of phase error DFi121 - sig_rp_t = [1e-10, 1e-9, 1e-8, 1e-7, 1e-6, 1e-5, 1e-4, 4e-4, 1e-3, 4e-3, + # Bound on rate of change in phase error standard deviation DFi121 + sig_pd_t = [1e-10, 1e-9, 1e-8, 1e-7, 1e-6, 1e-5, 1e-4, 4e-4, 1e-3, 4e-3, 0.01, 0.04, 0.10, 0.50, 5.0, -1] - # stdev indicator of bound on rate of change of phase error DFi122 - sig_prre_t = [1e-3, 2e-3, 5e-3, 7.5e-3, 0.010, 0.014, 0.018, 0.025, 0.050, - 0.10, 0.15, 0.20, 0.50, 1.0, 20.0, -1] - # stdev indicator of bound on rate of change of phase error DFi123 - sig_dprre_t = [1e-11, 1e-10, 1e-9, 1e-8, 1e-7, 1e-6, 1e-5, 1e-4, 1e-3, - 0.01, 0.1, 1.0, 10.0, 0, 0, -1] + # Bounding sigma on phase-range-rate error DFi122 + sig_b_pd_t = [1e-3, 2e-3, 5e-3, 7.5e-3, 0.010, 0.014, 0.018, 0.025, 0.050, + 0.10, 0.15, 0.20, 0.50, 1.0, 20.0, -1] + # Phase-range-rate error rate integrity index DFi123 + b_pd_t = [1e-11, 1e-10, 1e-9, 1e-8, 1e-7, 1e-6, 1e-5, 1e-4, 1e-3, + 0.01, 0.1, 1.0, 10.0, 0, 0, -1] # bounding parameter in iono, tropo error indicator DFi129, DFi131 b_datm_t = [5e-4, 1e-3, 2e-3, 4e-3, 0.01, 0.02, 0.05, -1] # bounding pseudorange bias rate error indicator DFi125 b_rsdsa_t = [5e-4, 1e-3, 2e-3, 4e-3, 0.01, 0.02, 0.05, -1] # bounding rate of change in satellite orbit/clock error indicator DFi127 - b_rboc_t = [5e-4, 1e-3, 2e-3, 4e-3, 0.01, 0.02, 0.05, -1] + ocr_t = [5e-4, 1e-3, 2e-3, 4e-3, 0.01, 0.02, 0.05, -1] # residual iono error stdev for L1 GPS DFi128 - sig_di_t = [5e-3, 1e-2, 2e-2, 5e-2, 0.10, 0.25, 1.0, -1] + sig_ion_t = [5e-3, 1e-2, 2e-2, 5e-2, 0.10, 0.25, 1.0, -1] # residual trop error stdev for L1 GPS DFi130 - sig_dt_t = [0.01, 0.02, 0.03, 0.05, 0.075, 0.10, 0.125, -1] + sig_trp_t = [0.01, 0.02, 0.03, 0.05, 0.075, 0.10, 0.125, -1] # bounding inter-constellation bias DFi132 b_intc_t = [0.00, 0.01, 0.02, 0.05, 0.10, 0.25, 0.50, -1] # bounding inter-frequency bias @@ -148,13 +148,33 @@ class Integrity(): # GMM component stdev Table 8-41 sig_k_t = [0.0, 0.04, 0.07, 0.10, 0.20, 0.25, 0.30, 0.40, 0.50, 0.70, 1, 2, 5, 7, 10, -1] - + # Extended Area Time Parameter Degradation Factor DFi138 + ext_df_t = [1.0, 1.05, 1.1, 1.2, 1.4, 1.7, 2.0, -1] + # Extended Area Spatial Parameter Degradation Factor DFi139 + exs_df_t = [1.0, 1.1, 1.3, 1.6, 2.0, 3.0, 5.0, -1] + # Augmentation IR Degradation Factor DFi140 + f_ir_t = [1.0, 10, 1e2, 1e3] + # Augmentation TTD Degradation Factor DFi141 + f_ttd_t = [1.0,1.25,2.0,4.0] # GNSS Signal Modulation Mask Table 8-45 mod_t = ['BPSK(1)', 'BPSK(5)', 'BPSK(10)', 'BOC(1,1)', 'CBOC(6,1,1/11)', 'AltBOC(15,10)', '', ''] # mm_model_t = ['GMM', 'Mats Brenner', 'Jahn'] + + # DFi052 Satellite Integrity Fault Source + fault_src_t = ['satellite orbit','satellite clock','atmospheric','other'] + + # Table 8 16 GNSS Frequency Indexes + frq_idx = { + uGNSS.GPS: ['L1','L2', 'L5'], + uGNSS.GLO: ['L1','L2', 'L3'], + uGNSS.GAL: ['E1','E5a', 'E5b', 'E5 AltBOC', 'E6'], + uGNSS.BDS: ['B1I','B2I', 'B3I','B1C','B2a','B2b'], + uGNSS.QZS: ['L1','L2', 'L5','L6'], + uGNSS.IRN: ['L1','L5', 'S'], + } # Validity Period DFi065 vp_tbl = [1, 2, 5, 10, 15, 30, 60, 120, 240, @@ -181,31 +201,40 @@ def __init__(self): self.nid = {} self.flag = {} - self.sigmask = {} # GNSS signal mask DFi033 (b32) - self.iod_sig = {} # issue of GNSS signal mask DFi012 (b2) - self.sig_ists = {} # signal integrity status DFi031 (b32) - self.sig_msts = {} # signal monitoring status DFi032 (b32) + self.freq = {} # GNSS Frequency mask (b8) + self.iod_freq = {} # issue of GNSS signal mask DFi012 (b2) + self.ists = {} # signal integrity status DFi031 (b32) + self.msts = {} # signal monitoring status DFi032 (b32) self.Paug = 0 # Augmentation System Probability Falut DFi049 - self.Pa_sp = {} # Single Satellite PR Message Fault Probability DFi046 - self.sig_a = {} # Overbounding stdev of PR Augmentation Meesage DFi036 - self.Pa_sc = {} # Single Satellite CP Message Fault Probability DFi048 - self.ob_p = {} # Overbounding bias of long-ter PR bias DFi037 - self.sig_ob_c = {} # Ovebounding stdev of CP DFi038 - self.ob_c = {} # Overbounding bias of long-term CP bias DFi039 - - self.sig_pd = {} - self.sig_cd = {} - self.cbr = {} - self.sig_dp = {} - self.idx_dp = {} - - self.oc = {} - self.sig_ion = {} - self.idx_ion = {} - self.sig_trp = {} - self.idx_trp = {} + self.tau_c = {} + self.sig_c = {} + self.tau_p = {} + self.sig_p = {} + self.Pa_cc = {} + self.Pa_cp = {} + self.Pa_sc = {} # Single Satellite Code Message Fault Probability DFi048 + self.Pa_sp = {} # Single Satellite Phase Message Fault Probability DFi046 + self.sig_ob_c = {} # Ovebounding stdev of Code DFi038 + self.sig_ob_p = {} # Ovebounding stdev of Phase DFi038 + self.b_ob_c = {} # Overbounding bias of long-term CP bias DFi039 + self.b_ob_p = {} # Overbounding bias of long-ter PR bias DFi037 + + self.iod_m = {} # IOD GNSS Satellite mask DFi010 + + self.sig_cd = {} # Bound on rate of change of PR error stdev DFi120 + self.sig_pd = {} # Bound on rate of change in phase error stdev DFi121 + + self.cbr = {} # Nominal pseudorange bias rate of change DFi125 + self.sig_dp = {} # Bounding sigma on phase-range-rate error DFi122 + self.idx_dp = {} # Phase-range-rate error rate integrity index DFi123 + + self.ocr = {} # Orbit and clock error rate integrity parameter DFi127 + self.sig_ion = {} # Residual ionospheric error stdev DFi128 + self.idx_ion = {} # Index of bounding parameter on change in iono error DFi129 + self.sig_trp = {} # Residual tropospheric error stdev DFi130 + self.idx_trp = {} # Index of bounding parameter on change in tropo error DFi131 self.cnr = {} self.agc_t = {} @@ -218,7 +247,25 @@ def __init__(self): self.mask_sys = 0 self.mm_param = None + + def cnr_lvl(self, idx): + """ get index to C/N level [dB-Hz] """ + cnr = -1 + if idx>0 and idx<20: + cnr = idx-1 + elif idx<245: + cnr = 19+(idx-20)*0.2 + else: + cnr = 64+(idx-245) + + return cnr + def agc_lvl(self, idx): + """ get index to AGC level [dB] """ + agc = 'N/A' + if idx>0: + agc = idx-128 + return agc class rtcmUtil: """ class to define common parameters and utilities for RTCM """ @@ -1687,8 +1734,8 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.integ.pid)) if self.subtype == sRTCM.INTEG_SSR: - self.fh.write(" {:20s}{:6.1f}\n".format("Varidity Period:", - self.integ.vp)) + self.fh.write(" {:20s}{:6.1f}\n".format("Varidity Period [s]:", + self.integ.vp_tbl[self.integ.vp])) self.fh.write(" {:20s}{:6.1f}\n".format("Update Interval:", self.integ.uri)) @@ -1713,28 +1760,69 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write(" Message Type: {:4d}-{:-2d}\n". format(self.msgtype, self.subtype)) - if self.subtype == sRTCM.INTEG_MIN: + if self.subtype == sRTCM.INTEG_MIN: # MT2000 self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") self.fh.write(f" Provider Id: {self.integ.pid}\n") - self.fh.write(f" Validity Period: {self.integ.vp}\n") - self.fh.write(f" Update rate interval: {self.integ.uri}\n") + self.fh.write(f" Validity Period [s]: {self.integ.vp_tbl[self.integ.vp]}\n") + self.fh.write(f" Update rate interval [s]: {self.integ.uri*0.1}\n") - self.fh.write(f" Issue of Sat Mask: {self.integ.iod_sys}\n") + self.fh.write(" Issue of Sat Mask: ") + for sys in self.integ.iod_sys: + self.fh.write(f" {sys2char(sys)}:{self.integ.iod_sys[sys]}") + self.fh.write("\n") + self.fh.write(" Integrity Status:\n") for sys in self.integ.sts: - self.fh.write( - f" {sys2char(sys)} {format(self.integ.sts[sys], '064b')}\n") + self.fh.write(f" {sys2char(sys)}:" + f" {format(self.integ.sts[sys], '064b')}\n") self.fh.write(" Monitoring Status:\n") for sys in self.integ.mst: - self.fh.write( - f" {sys2char(sys)} {self.integ.mst[sys]}\n") - self.fh.write(f" Integrity Fault Source: {self.integ.src}\n") - self.fh.write(f" sigmask: {self.integ.sigmask}\n") - self.fh.write(f" IOD freq: {self.integ.iod_sig}\n") - self.fh.write(f" Integrity Status: {self.integ.sig_ists}\n") - self.fh.write(f" Monitoring Status: {self.integ.sig_msts}\n") - + self.fh.write(f" {sys2char(sys)}:") + for sat in self.integ.freq[sys]: + self.fh.write(f" {sat2id(sat)}") + self.fh.write("\n") + + self.fh.write(" Integrity Fault Source:\n") + for sys in self.integ.src: + msg = self.integ.fault_src_t[self.integ.src[sys]] + self.fh.write(f" {sys2char(sys)}: {msg}\n") + + self.fh.write(" Frequency mask:\n") + for sys in self.integ.freq: + self.fh.write(f" {sys2char(sys)}:") + s = self.integ.frq_idx[sys] + for sat in self.integ.freq[sys]: + f = self.integ.freq[sys][sat] + self.fh.write(f" {sat2id(sat)}") + for k in f: + self.fh.write(f" {s[k]}") + self.fh.write("\n") + + self.fh.write(" IOD freq mask:\n") + for sys in self.integ.iod_freq: + self.fh.write(f" {sys2char(sys)}:") + for sat in self.integ.iod_freq[sys]: + self.fh.write( + f" {sat2id(sat)} {self.integ.iod_freq[sys][sat]}") + self.fh.write("\n") + + self.fh.write(" Integrity Status:\n") + for sys in self.integ.ists: + self.fh.write(f" {sys2char(sys)}:") + for sat in self.integ.ists[sys]: + s = format(self.integ.ists[sys][sat], '08b') + self.fh.write(f" {sat2id(sat)} {s}") + self.fh.write("\n") + + self.fh.write(" Monitoring Status:\n") + for sys in self.integ.msts: + self.fh.write(f" {sys2char(sys)}:") + for sat in self.integ.msts[sys]: + s = format(self.integ.msts[sys][sat], '08b') + self.fh.write(f" {sat2id(sat)} {s}") + self.fh.write("\n") + elif self.subtype == sRTCM.INTEG_EXT: # MT2005 self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") self.fh.write(f" Provider Id: {self.integ.pid}\n") @@ -1743,28 +1831,82 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write(f" TTT: {self.integ.ttt}\n") self.fh.write(f" Solution Type: {self.integ.stype}\n") - self.fh.write(f" Validity Period: {self.integ.vp}\n") - self.fh.write(f" Update rate interval: {self.integ.uri}\n") + self.fh.write(f" Validity Period [s]: {self.integ.vp_tbl[self.integ.vp]}\n") + self.fh.write(f" Update rate interval [s]: {self.integ.uri*0.1}\n") self.fh.write(f" Paug: {self.integ.Paug}\n") self.fh.write(f" IOD param: {self.integ.iod_p}\n") self.fh.write(f" Integrity/Continuity Flag: {self.integ.fc}\n") - self.fh.write(f" mfd single: {self.integ.mfd_s}\n") - self.fh.write(f" mfd constellation: {self.integ.mfd_c}\n") - self.fh.write(f" tau_p: {self.integ.tau_p}\n") - self.fh.write(f" sig_p: {self.integ.sig_p}\n") - self.fh.write(f" tau_c: {self.integ.tau_c}\n") - self.fh.write(f" sig_c: {self.integ.sig_c}\n") - self.fh.write(f" Pa_cp: {self.integ.Pa_cp}\n") - self.fh.write(f" Pa_cc: {self.integ.Pa_cc}\n") - - self.fh.write(f" Pa_sp: {self.integ.Pa_sp}\n") - self.fh.write(f" sig_a: {self.integ.sig_a}\n") - self.fh.write(f" Pa_sc: {self.integ.Pa_sc}\n") - self.fh.write(f" ob_p: {self.integ.ob_p}\n") - self.fh.write(f" sig_ob_c: {self.integ.sig_ob_c}\n") - self.fh.write(f" ob_c: {self.integ.ob_c}\n") + self.fh.write(" mfd single: ") + for sys, s in self.integ.mfd_s.items(): + self.fh.write(f" {sys2char(sys)}: {self.integ.mfd_s_t[s]}") + self.fh.write("\n") + self.fh.write(" mfd constellation: ") + for sys, s in self.integ.mfd_c.items(): + self.fh.write(f" {sys2char(sys)}: {self.integ.mfd_c_t[s]}") + self.fh.write("\n") + self.fh.write(" tau_c: ") + for sys, s in self.integ.tau_c.items(): + self.fh.write(f" {sys2char(sys)}: {self.integ.tau_c_t[s]}") + self.fh.write("\n") + self.fh.write(" sig_c: ") + for sys, s in self.integ.sig_c.items(): + self.fh.write(f" {sys2char(sys)}: {self.integ.sig_ob_c_t[s]}") + self.fh.write("\n") + self.fh.write(" tau_p: ") + for sys, s in self.integ.tau_p.items(): + self.fh.write(f" {sys2char(sys)}: {self.integ.tau_p_t[s]}") + self.fh.write("\n") + self.fh.write(" sig_p: ") + for sys, s in self.integ.sig_p.items(): + self.fh.write(f" {sys2char(sys)}: {self.integ.sig_ob_p_t[s]}") + self.fh.write("\n") + self.fh.write(" Pa_cc: ") + for sys, s in self.integ.Pa_cc.items(): + self.fh.write(f" {sys2char(sys)}: {self.integ.P_t[s]}") + self.fh.write("\n") + self.fh.write(" Pa_cp: ") + for sys, s in self.integ.Pa_cp.items(): + self.fh.write(f" {sys2char(sys)}: {self.integ.P_t[s]}") + self.fh.write("\n") + + self.fh.write(" Pa_sc:\n") + for sys, s in self.integ.Pa_sc.items(): + self.fh.write(f" {sys2char(sys)}:") + for sat, v in s.items(): + self.fh.write(f" {sat2id(sat)} {self.integ.P_t[v]}") + self.fh.write("\n") + self.fh.write(" Pa_sp:\n") + for sys, s in self.integ.Pa_sp.items(): + self.fh.write(f" {sys2char(sys)}:") + for sat, v in s.items(): + self.fh.write(f" {sat2id(sat)} {self.integ.P_t[v]}") + self.fh.write("\n") + self.fh.write(" sig_ob_c:\n") + for sys, s in self.integ.sig_ob_c.items(): + self.fh.write(f" {sys2char(sys)}:") + for sat, v in s.items(): + self.fh.write(f" {sat2id(sat)} {self.integ.sig_ob_c_t[v]}") + self.fh.write("\n") + self.fh.write(" sig_ob_p:\n") + for sys, s in self.integ.sig_ob_p.items(): + self.fh.write(f" {sys2char(sys)}:") + for sat, v in s.items(): + self.fh.write(f" {sat2id(sat)} {self.integ.sig_ob_p_t[v]}") + self.fh.write("\n") + self.fh.write(" b_ob_c:\n") + for sys, s in self.integ.b_ob_c.items(): + self.fh.write(f" {sys2char(sys)}:") + for sat, v in s.items(): + self.fh.write(f" {sat2id(sat)} {self.integ.b_ob_c_t[v]}") + self.fh.write("\n") + self.fh.write(" b_ob_p:\n") + for sys, s in self.integ.b_ob_p.items(): + self.fh.write(f" {sys2char(sys)}:") + for sat, v in s.items(): + self.fh.write(f" {sat2id(sat)} {self.integ.b_ob_p_t[v]}") + self.fh.write("\n") elif self.subtype == sRTCM.INTEG_EXT_SIS: # MT2006 self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") @@ -1772,73 +1914,163 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write(f" Area-ID: {self.integ.aid}\n") self.fh.write(f" IOD param: {self.integ.iod_ip}\n") - self.fh.write(f" Validity Period: {self.integ.vp}\n") - self.fh.write(f" Update rate interval: {self.integ.uri}\n") + self.fh.write(f" Validity Period [s]: {self.integ.vp_tbl[self.integ.vp]}\n") + self.fh.write(f" Update rate interval [s]: {self.integ.uri*0.1}\n") self.fh.write(f" Integrity/Continuity Flag: {self.integ.fc}\n") - self.fh.write(f" bias (const/freq): {self.integ.bias}\n") - - self.fh.write(f" sig_pd: {self.integ.sig_pd}\n") - self.fh.write(f" sig_cd: {self.integ.sig_cd}\n") - self.fh.write(f" cbr: {self.integ.cbr}\n") - self.fh.write(f" sig_dp: {self.integ.sig_dp}\n") - self.fh.write(f" idx_dp: {self.integ.idx_dp}\n") - - self.fh.write(f" orbit/clock: {self.integ.oc}\n") - self.fh.write(f" stdev iono: {self.integ.sig_ion}\n") - self.fh.write(f" idx iono: {self.integ.idx_ion}\n") - self.fh.write(f" stdev trop: {self.integ.sig_trp}\n") - self.fh.write(f" idx trop: {self.integ.idx_trp}\n") - + + b = self.integ.bias + self.fh.write(f" bias (inter-const): {self.integ.b_intc_t[b[0]]}\n") + self.fh.write(f" bias (inter-freq): {self.integ.b_intf_t[b[1]]}\n") + + self.fh.write(" iod-mask:\n") + for sys, s in self.integ.iod_m.items(): + self.fh.write(f" {sys2char(sys)}: {s}") + self.fh.write("\n") + + self.fh.write(" sig_cd:\n") + for sys, s in self.integ.sig_cd.items(): + self.fh.write(f" {sys2char(sys)}:") + for sat, v in s.items(): + self.fh.write(f" {sat2id(sat)} {self.integ.sig_cd_t[v]}") + self.fh.write("\n") + + self.fh.write(" sig_pd:\n") + for sys, s in self.integ.sig_pd.items(): + self.fh.write(f" {sys2char(sys)}:") + for sat, v in s.items(): + self.fh.write(f" {sat2id(sat)} {self.integ.sig_pd_t[v]}") + self.fh.write("\n") + + self.fh.write(" cbr:\n") + for sys, s in self.integ.cbr.items(): + self.fh.write(f" {sys2char(sys)}:") + for sat, v in s.items(): + self.fh.write(f" {sat2id(sat)} {self.integ.b_rsdsa_t[v]}") + self.fh.write("\n") + + self.fh.write(" sig_dp:\n") + for sys, s in self.integ.sig_dp.items(): + self.fh.write(f" {sys2char(sys)}:") + for sat, v in s.items(): + self.fh.write(f" {sat2id(sat)} {self.integ.sig_b_pd_t[v]}") + self.fh.write("\n") + + self.fh.write(" idx_dp:\n") + for sys, s in self.integ.idx_dp.items(): + self.fh.write(f" {sys2char(sys)}:") + for sat, v in s.items(): + self.fh.write(f" {sat2id(sat)} {self.integ.b_pd_t[v]}") + self.fh.write("\n") + + self.fh.write(" orbit/clock:\n") + for sys, s in self.integ.ocr.items(): + self.fh.write(f" {sys2char(sys)}:") + for sat, v in s.items(): + self.fh.write(f" {sat2id(sat)} {self.integ.ocr_t[v]}") + self.fh.write("\n") + + self.fh.write(" stdev iono:\n") + for sys, s in self.integ.sig_ion.items(): + self.fh.write(f" {sys2char(sys)}:") + for sat, v in s.items(): + self.fh.write(f" {sat2id(sat)} {self.integ.sig_ion_t[v]}") + self.fh.write("\n") + + self.fh.write(" idx iono:\n") + for sys, s in self.integ.idx_ion.items(): + self.fh.write(f" {sys2char(sys)}:") + for sat, v in s.items(): + self.fh.write(f" {sat2id(sat)} {self.integ.b_datm_t[v]}") + self.fh.write("\n") + + self.fh.write(" stdev trop:\n") + for sys, s in self.integ.sig_trp.items(): + self.fh.write(f" {sys2char(sys)}:") + for sat, v in s.items(): + self.fh.write(f" {sat2id(sat)} {self.integ.sig_trp_t[v]}") + self.fh.write("\n") + + self.fh.write(" idx trop:\n") + for sys, s in self.integ.idx_trp.items(): + self.fh.write(f" {sys2char(sys)}:") + for sat, v in s.items(): + self.fh.write(f" {sat2id(sat)} {self.integ.b_datm_t[v]}") + self.fh.write("\n") + elif self.subtype == sRTCM.INTEG_PRI_AREA: # MT2007 self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") self.fh.write(f" Provider ID: {self.integ.pid}\n") self.fh.write(f" Area ID: {self.integ.aid}\n") self.fh.write(f" IOD area: {self.integ.iod_sa}\n") - self.fh.write(f" Validity Period: {self.integ.vp}\n") - self.fh.write(f" Update rate interval: {self.integ.uri}\n") + self.fh.write(f" Validity Period [s]: {self.integ.vp_tbl[self.integ.vp]}\n") + self.fh.write(f" Update rate interval [s]: {self.integ.uri*0.1}\n") self.fh.write(f" Continuation Flag: {self.integ.cf}\n") self.fh.write(f" Seq: {self.integ.seq}\n") - self.fh.write(f" Area Point (lat, lon): {self.integ.pos}\n") + self.fh.write(f" Area Point (lat, lon):\n {self.integ.pos}\n") elif self.subtype == sRTCM.INTEG_EXT_AREA: # MT2008 self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") self.fh.write(f" Provider ID: {self.integ.pid}\n") self.fh.write(f" Area ID: {self.integ.aid}\n") self.fh.write(f" IOD area: {self.integ.iod_sa}\n") - self.fh.write(f" Validity Period: {self.integ.vp}\n") - self.fh.write(f" Update rate interval: {self.integ.uri}\n") + self.fh.write(f" Validity Period [s]: {self.integ.vp_tbl[self.integ.vp]}\n") + self.fh.write(f" Update rate interval [s]: {self.integ.uri*0.1}\n") self.fh.write(f" Service Area Type: {self.integ.atype}\n") self.fh.write(f" Continuation Flag: {self.integ.cf}\n") self.fh.write(f" Seq: {self.integ.seq}\n") - self.fh.write(f" f_ir: {self.integ.f_ir}\n") - self.fh.write(f" f_ttd: {self.integ.f_ttd}\n") - self.fh.write(f" f_ext: {self.integ.f_ext}\n") - self.fh.write(f" f_exs: {self.integ.f_exs}\n") + self.fh.write(f" f_ir : {self.integ.f_ir_t[self.integ.f_ir]}\n") + self.fh.write(f" f_ttd: {self.integ.f_ttd_t[self.integ.f_ttd]}\n") + self.fh.write(f" f_ext: {self.integ.ext_df_t[self.integ.f_ext]}\n") + self.fh.write(f" f_exs: {self.integ.exs_df_t[self.integ.f_exs]}\n") self.fh.write(f" Area Point (lat/lon): {self.integ.pos}\n") elif self.subtype == sRTCM.INTEG_QUALITY: # MT2051 self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") self.fh.write(f" Provider Id: {self.integ.pid}\n") - self.fh.write(f" Validity Period: {self.integ.vp}\n") - self.fh.write(f" Update rate interval: {self.integ.uri}\n") + self.fh.write(f" Validity Period [s]: {self.integ.vp_tbl[self.integ.vp]}\n") + self.fh.write(f" Update rate interval [s]: {self.integ.uri*0.1}\n") self.fh.write(f" Network ID: {self.integ.nid}\n") - self.fh.write(f" Quality Indicator: {self.integ.qi}\n") + self.fh.write(f" Quality Indicator [m]: {self.integ.qi}\n") elif self.subtype == sRTCM.INTEG_CNR: # MT2091 self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") self.fh.write(f" Provider ID: {self.integ.pid}\n") self.fh.write(f" Area ID: {self.integ.aid}\n") - self.fh.write(f" Update rate interval: {self.integ.uri}\n") - - self.fh.write(f" CNR: {self.integ.cnr}\n") - self.fh.write(f" AGC type: {self.integ.agc_t}\n") - self.fh.write(f" AGC: {self.integ.agc}\n") - - elif self.subtype == sRTCM.INTEG_VMAP: # 2071 + self.fh.write(f" Update rate interval [s]: {self.integ.uri*0.1}\n") + + self.fh.write(" CNR [dB-Hz]:\n") + for sys, s in self.integ.cnr.items(): + self.fh.write(f" {sys2char(sys)}:") + for sat, v in s.items(): + self.fh.write(f" {sat2id(sat)}") + for sig, u in v.items(): + self.fh.write(f" {self.integ.cnr_lvl(u)}") + + self.fh.write("\n") + + self.fh.write(" AGC type:\n") + for sys, s in self.integ.agc_t.items(): + self.fh.write(f" {sys2char(sys)}:") + for sat, v in s.items(): + self.fh.write(f" {sat2id(sat)}") + for sig, u in v.items(): + self.fh.write(f" {u}") + self.fh.write("\n") + + self.fh.write(" AGC [dB]:\n") + for sys, s in self.integ.agc.items(): + self.fh.write(f" {sys2char(sys)}:") + for sat, v in s.items(): + self.fh.write(f" {sat2id(sat)}") + for sig, u in v.items(): + self.fh.write(f" {self.integ.agc_lvl(u)}") + self.fh.write("\n") + + elif self.subtype == sRTCM.INTEG_VMAP: # MT2071 self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") self.fh.write(f" Number of Area Points: {self.integ.narea:}\n") self.fh.write(" Boundary Points (lat, long, alt, naz):\n") @@ -1847,7 +2079,7 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): (k+1, self.integ.pos[k, 0], self.integ.pos[k, 1], self.integ.pos[k, 2], self.integ.naz[k])) - self.fh.write(" Visibility mask (az, el):\n") + self.fh.write(" Visibility mask (az, el) [deg]:\n") for j in range(self.integ.naz[k]): self.fh.write(" {:3.0f}\t{:3.0f}\n".format (self.integ.azel[k][j][0], self.integ.azel[k][j][1])) @@ -3010,10 +3242,10 @@ def decode_integrity_min(self, msg, i): mst_tbl = {} src_tbl = {} - iod_sig = {} - sigmask = {} - sig_ists = {} - sig_msts = {} + iod_freq = {} + freq = {} + ists = {} + msts = {} for sys_ in sys_t: sys = self.integ.sys_tbl[sys_] @@ -3038,26 +3270,27 @@ def decode_integrity_min(self, msg, i): mst_tbl[sys] = prn_m src_tbl[sys] = src - iod_sig[sys] = {} - sigmask[sys] = {} - sig_ists[sys] = {} - sig_msts[sys] = {} + iod_freq[sys] = {} + freq[sys] = {} + ists[sys] = {} + msts[sys] = {} for svid in svid_t: ofst = 192 if sys == uGNSS.QZS else 0 prn = svid+ofst sat = prn2sat(sys, prn) # GNSS frequency mask DFi022 - sigmask[sys][sat] = bs.unpack_from('u8', msg, i)[0] + f, _ = self.decode_mask(bs.unpack_from('u8', msg, i)[0], 8, 0) + freq[sys][sat] = f i += 8 # issue of GNSS frequency mask DFi023 - iod_sig[sys][sat] = bs.unpack_from('u2', msg, i)[0] + iod_freq[sys][sat] = bs.unpack_from('u2', msg, i)[0] i += 2 # frequency integrity status DFi024 - sig_ists[sys][sat] = bs.unpack_from('u8', msg, i)[0] + ists[sys][sat] = bs.unpack_from('u8', msg, i)[0] i += 8 # frequency monitoring status DFi025 - sig_msts[sys][sat] = bs.unpack_from('u8', msg, i)[0] + msts[sys][sat] = bs.unpack_from('u8', msg, i)[0] i += 8 self.integ.iod_sys = iod_sys @@ -3065,10 +3298,10 @@ def decode_integrity_min(self, msg, i): self.integ.mst = mst_tbl self.integ.src = src_tbl - self.integ.sigmask = sigmask - self.integ.iod_sig = iod_sig - self.integ.sig_ists = sig_ists - self.integ.sig_msts = sig_msts + self.integ.freq = freq + self.integ.iod_freq = iod_freq + self.integ.ists = ists + self.integ.msts = msts return i def decode_integrity_ext(self, msg, i): @@ -3089,7 +3322,7 @@ def decode_integrity_ext(self, msg, i): self.integ.pid = pid self.integ.aid = area self.integ.ilvl = ilvl - self.integ.ttt = ttt + self.integ.ttt = (ttt+1)*0.1 self.integ.stype = stype # GNSS Constellation Mask DFi013 @@ -3119,12 +3352,12 @@ def decode_integrity_ext(self, msg, i): # constellation specific part - self.integ.Pa_sp = {} - self.integ.sig_a = {} self.integ.Pa_sc = {} - self.integ.ob_p = {} - self.integ.sig_ob_c = {} - self.integ.ob_c = {} + self.integ.Pa_sp = {} + self.integ.sig_ob_c = {} + self.integ.sig_ob_p = {} + self.integ.b_ob_c = {} + self.integ.b_ob_p = {} self.integ.mfd_s = {} self.integ.mfd_c = {} @@ -3156,7 +3389,7 @@ def decode_integrity_ext(self, msg, i): # Probability DFi047 # Multiple Satellite Carrier Phase Augmentation Message Fault # Probability DFi035 - tau_p, sig_p, tau_c, sig_c, Pa_cp, Pa_cc = bs.unpack_from( + tau_c, sig_c, tau_p, sig_p, Pa_cc, Pa_cp = bs.unpack_from( 'u4u5u4u5u4u4', msg, i) i += 26 @@ -3176,12 +3409,12 @@ def decode_integrity_ext(self, msg, i): self.integ.Pa_cp[sys] = Pa_cp self.integ.Pa_cc[sys] = Pa_cc - self.integ.Pa_sp[sys] = {} - self.integ.sig_a[sys] = {} self.integ.Pa_sc[sys] = {} - self.integ.ob_p[sys] = {} - self.integ.sig_ob_c[sys] = {} - self.integ.ob_c[sys] = {} + self.integ.Pa_sp[sys] = {} + self.integ.sig_ob_c[sys] = {} + self.integ.sig_ob_p[sys] = {} + self.integ.b_ob_c[sys] = {} + self.integ.b_ob_p[sys] = {} ofst = 193 if sys == uGNSS.QZS else 1 prn_t, nsat = self.decode_mask(mask_sat, 64, ofst=ofst) @@ -3201,16 +3434,17 @@ def decode_integrity_ext(self, msg, i): # Augmentation Message Error under Fault-Free Scenario DFi038 # Overbounding Bias of the Long-Term Carrier Phase # Augmentation Message Bias Error under Fault-Free DFi039 - Pa_sp, sig_a, Pa_sc, ob_p, sig_ob_c, ob_c = bs.unpack_from( - 'u4u5u4u4u5u4', msg, i) + Pa_sc, sig_ob_c, Pa_sp, b_ob_c, sig_ob_p, b_ob_p = \ + bs.unpack_from('u4u5u4u4u5u4', msg, i) i += 26 - self.integ.Pa_sp[sys][sat] = Pa_sp - self.integ.sig_a[sys][sat] = sig_a self.integ.Pa_sc[sys][sat] = Pa_sc - self.integ.ob_p[sys][sat] = ob_p + self.integ.Pa_sp[sys][sat] = Pa_sp self.integ.sig_ob_c[sys][sat] = sig_ob_c - self.integ.ob_c[sys][sat] = ob_c + self.integ.sig_ob_p[sys][sat] = sig_ob_p + self.integ.b_ob_c[sys][sat] = b_ob_c + self.integ.b_ob_p[sys][sat] = b_ob_p + return i def decode_integrity_ext_sis_local(self, msg, i): @@ -3260,6 +3494,8 @@ def decode_integrity_ext_sis_local(self, msg, i): mask_sat, iod_mask = bs.unpack_from('u64u2', msg, i) i += 66 + self.integ.iod_m[sys] = iod_mask + ofst = 193 if sys == uGNSS.QZS else 1 prn_t, nsat = self.decode_mask(mask_sat, 64, ofst=ofst) @@ -3269,7 +3505,7 @@ def decode_integrity_ext_sis_local(self, msg, i): self.integ.sig_dp[sys] = {} self.integ.idx_dp[sys] = {} - self.integ.oc[sys] = {} + self.integ.ocr[sys] = {} self.integ.sig_ion[sys] = {} self.integ.idx_ion[sys] = {} self.integ.sig_trp[sys] = {} @@ -3292,7 +3528,7 @@ def decode_integrity_ext_sis_local(self, msg, i): # Index of bounding parameter on change in iono error DFi129 # Residual tropospheric error standard deviation DFi130 # Index of bounding parameter on change in tropo error DFi131 - oc, sig_ion, idx_ion, sig_trp, idx_trp = bs.unpack_from( + ocr, sig_ion, idx_ion, sig_trp, idx_trp = bs.unpack_from( 'u3u3u3u3u3', msg, i) i += 15 @@ -3302,7 +3538,7 @@ def decode_integrity_ext_sis_local(self, msg, i): self.integ.sig_dp[sys][sat] = sig_dp self.integ.idx_dp[sys][sat] = idx_dp - self.integ.oc[sys][sat] = oc + self.integ.ocr[sys][sat] = ocr self.integ.sig_ion[sys][sat] = sig_ion self.integ.idx_ion[sys][sat] = idx_ion self.integ.sig_trp[sys][sat] = sig_trp @@ -3458,7 +3694,7 @@ def decode_integrity_quality(self, msg, i): idx_q, np_ = self.decode_mask(mask_q, 8, ofst=0) self.integ.qi = np.zeros(np_) for k in range(np_): - self.integ.qi[k] = bs.unpack_from('u8', msg, i)[0] + self.integ.qi[k] = bs.unpack_from('u8', msg, i)[0]*0.1 i += 8 return i From 45f32ded0a70751f33fefa32ce615ed2be50cf9f Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Wed, 28 Jan 2026 22:26:09 +0900 Subject: [PATCH 33/48] - update output of sc134 messages. --- src/cssrlib/rtcm.py | 155 ++++++++++++++++++++++++++------------------ 1 file changed, 93 insertions(+), 62 deletions(-) diff --git a/src/cssrlib/rtcm.py b/src/cssrlib/rtcm.py index c601d68..ee1bd8c 100644 --- a/src/cssrlib/rtcm.py +++ b/src/cssrlib/rtcm.py @@ -160,6 +160,7 @@ class Integrity(): mod_t = ['BPSK(1)', 'BPSK(5)', 'BPSK(10)', 'BOC(1,1)', 'CBOC(6,1,1/11)', 'AltBOC(15,10)', '', ''] + atype_t = {0:"Circle", 1:"Polygon"} # mm_model_t = ['GMM', 'Mats Brenner', 'Jahn'] @@ -180,6 +181,17 @@ class Integrity(): vp_tbl = [1, 2, 5, 10, 15, 30, 60, 120, 240, 300, 600, 900, 1800, 3600, 7200, 10800] + mt_integ_t = {2000: "Minimum Integrity", + 2005: "Extended Integrity (Service Levels and Overbounding)", + 2006: "Extended Integrity (SIS and Local Error)", + 2007: "Primary Service Area", + 2008: "Extended Service Area", + 2011: "SIS SSR Integrity", + 2051: "Quality Indicator", + 2091: "AGC SIS Monitoring", + 2071: "Satellite Visibility Map", + 2072: "Multipath Map"} + def __init__(self): self.sys_r_tbl = {self.sys_tbl[s]: s for s in self.sys_tbl.keys()} self.vp_r_tbl = {s: k for k, s in enumerate(self.vp_tbl)} @@ -206,6 +218,8 @@ def __init__(self): self.ists = {} # signal integrity status DFi031 (b32) self.msts = {} # signal monitoring status DFi032 (b32) + self.pos_v = None # service area (polygon/circle) + self.Paug = 0 # Augmentation System Probability Falut DFi049 self.tau_c = {} @@ -266,6 +280,16 @@ def agc_lvl(self, idx): if idx>0: agc = idx-128 return agc + + def decode_sol_t(self, mask): + """ decode Service Provider Solution Type""" + sol_t = ['ARAIM','SBAS','GBAS','DGNSS','RTK','NRTK/VRS','PPP','PPP-AR', + 'PPP-RTK','','','','','','','other'] + s = "" + for k in range(15): + if (mask>>(15-k-1)) & 1: + s += sol_t[k] + return s class rtcmUtil: """ class to define common parameters and utilities for RTCM """ @@ -285,9 +309,6 @@ class rtcmUtil: uGNSS.QZS: 1044, uGNSS.GAL: 1046 } - mt_integ_t = [2000, 2005, 2006, 2007, - 2008, 2011, 2051, 2071, 2072, 2091] - sc_t = {sCSSR.ORBIT: sCType.ORBIT, sCSSR.CLOCK: sCType.CLOCK, sCSSR.MASK: sCType.MASK, sCSSR.CBIAS: sCType.CBIAS, sCSSR.PBIAS: sCType.PBIAS, sCSSR.URA: sCType.URA, @@ -1512,8 +1533,7 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): if sys > 0 and self.subtype not in [sRTCM.SSR_META, sRTCM.SSR_GRID]: j = self.sc_t[self.subtype] self.fh.write(f" Update Interval: {self.udint_t[self.udi[j]]}[s]") - - self.fh.write(f" MultiMsg: {self.mi}\n") + self.fh.write(f" MultiMsg: {self.mi}\n") if self.subtype == sCSSR.CLOCK: self.out_log_ssr_clk(sys) @@ -1755,10 +1775,11 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write("\n") self.fh.flush() - if self.msgtype == 54 or self.msgtype in self.mt_integ_t: # RTCM SC134 integrity messages - # self.fh.write(f" Multiple Message Sequence :{self.integ.seq:}\n") - self.fh.write(" Message Type: {:4d}-{:-2d}\n". - format(self.msgtype, self.subtype)) + if self.msgtype == 54 or self.msgtype in self.integ.mt_integ_t: # RTCM SC134 integrity messages + if self.msgtype == 54: + self.fh.write(f" Message Type: {self.msgtype}\n") + else: + self.fh.write(f" Message Type: {self.integ.mt_integ_t[self.msgtype]} ({self.msgtype})\n") if self.subtype == sRTCM.INTEG_MIN: # MT2000 self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") @@ -1829,7 +1850,7 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write(f" Area ID: {self.integ.aid}\n") self.fh.write(f" Integrity Level: {self.integ.ilvl}\n") self.fh.write(f" TTT: {self.integ.ttt}\n") - self.fh.write(f" Solution Type: {self.integ.stype}\n") + self.fh.write(f" Solution Type: {self.integ.decode_sol_t(self.integ.stype)}\n") self.fh.write(f" Validity Period [s]: {self.integ.vp_tbl[self.integ.vp]}\n") self.fh.write(f" Update rate interval [s]: {self.integ.uri*0.1}\n") @@ -1838,70 +1859,70 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write(f" IOD param: {self.integ.iod_p}\n") self.fh.write(f" Integrity/Continuity Flag: {self.integ.fc}\n") - self.fh.write(" mfd single: ") + self.fh.write(" Mean Failure Duration (MFD) of a Single Satellite Fault: ") for sys, s in self.integ.mfd_s.items(): self.fh.write(f" {sys2char(sys)}: {self.integ.mfd_s_t[s]}") self.fh.write("\n") - self.fh.write(" mfd constellation: ") + self.fh.write(" Mean Failure Duration (MFD) of a Constellation Fault: ") for sys, s in self.integ.mfd_c.items(): self.fh.write(f" {sys2char(sys)}: {self.integ.mfd_c_t[s]}") self.fh.write("\n") - self.fh.write(" tau_c: ") + self.fh.write(" Correlation Time of Pseudorange Augmentation Message Error: ") for sys, s in self.integ.tau_c.items(): self.fh.write(f" {sys2char(sys)}: {self.integ.tau_c_t[s]}") self.fh.write("\n") - self.fh.write(" sig_c: ") + self.fh.write(" Pseudorange stdev: ") for sys, s in self.integ.sig_c.items(): self.fh.write(f" {sys2char(sys)}: {self.integ.sig_ob_c_t[s]}") self.fh.write("\n") - self.fh.write(" tau_p: ") + self.fh.write(" Correlation Time of Carrier Phase Augmentation Message Error: ") for sys, s in self.integ.tau_p.items(): self.fh.write(f" {sys2char(sys)}: {self.integ.tau_p_t[s]}") self.fh.write("\n") - self.fh.write(" sig_p: ") + self.fh.write(" Carrier Phase stdev: ") for sys, s in self.integ.sig_p.items(): self.fh.write(f" {sys2char(sys)}: {self.integ.sig_ob_p_t[s]}") self.fh.write("\n") - self.fh.write(" Pa_cc: ") + self.fh.write(" Multiple PR Augmentation Message Fault Probability: ") for sys, s in self.integ.Pa_cc.items(): self.fh.write(f" {sys2char(sys)}: {self.integ.P_t[s]}") self.fh.write("\n") - self.fh.write(" Pa_cp: ") + self.fh.write(" Multiple CP Augmentation Message Fault Probability: ") for sys, s in self.integ.Pa_cp.items(): self.fh.write(f" {sys2char(sys)}: {self.integ.P_t[s]}") self.fh.write("\n") - self.fh.write(" Pa_sc:\n") + self.fh.write(" Single PR Augmentation Message Fault Probability:\n") for sys, s in self.integ.Pa_sc.items(): self.fh.write(f" {sys2char(sys)}:") for sat, v in s.items(): self.fh.write(f" {sat2id(sat)} {self.integ.P_t[v]}") self.fh.write("\n") - self.fh.write(" Pa_sp:\n") - for sys, s in self.integ.Pa_sp.items(): - self.fh.write(f" {sys2char(sys)}:") - for sat, v in s.items(): - self.fh.write(f" {sat2id(sat)} {self.integ.P_t[v]}") - self.fh.write("\n") - self.fh.write(" sig_ob_c:\n") + self.fh.write(" Overbounding stdev of PR Augmentation Message Error under Fault-Free Scenario:\n") for sys, s in self.integ.sig_ob_c.items(): self.fh.write(f" {sys2char(sys)}:") for sat, v in s.items(): self.fh.write(f" {sat2id(sat)} {self.integ.sig_ob_c_t[v]}") self.fh.write("\n") - self.fh.write(" sig_ob_p:\n") - for sys, s in self.integ.sig_ob_p.items(): - self.fh.write(f" {sys2char(sys)}:") + self.fh.write(" Single CP Augmentation Message Fault Probability:\n") + for sys, s in self.integ.Pa_sp.items(): + self.fh.write(f" {sys2char(sys)}:") for sat, v in s.items(): - self.fh.write(f" {sat2id(sat)} {self.integ.sig_ob_p_t[v]}") - self.fh.write("\n") - self.fh.write(" b_ob_c:\n") + self.fh.write(f" {sat2id(sat)} {self.integ.P_t[v]}") + self.fh.write("\n") + self.fh.write(" Overbounding Bias of the Long-Term PR Augmentation Message Bias Error under Fault-Free Scenario:\n") for sys, s in self.integ.b_ob_c.items(): self.fh.write(f" {sys2char(sys)}:") for sat, v in s.items(): self.fh.write(f" {sat2id(sat)} {self.integ.b_ob_c_t[v]}") self.fh.write("\n") - self.fh.write(" b_ob_p:\n") + self.fh.write(" Overbounding stdev of CP Augmentation Message Error under Fault-Free Scenario:\n") + for sys, s in self.integ.sig_ob_p.items(): + self.fh.write(f" {sys2char(sys)}:") + for sat, v in s.items(): + self.fh.write(f" {sat2id(sat)} {self.integ.sig_ob_p_t[v]}") + self.fh.write("\n") + self.fh.write(" Overbounding Bias of the Long-Term CP Augmentation Message Bias Error under Fault-Free Scenario:\n") for sys, s in self.integ.b_ob_p.items(): self.fh.write(f" {sys2char(sys)}:") for sat, v in s.items(): @@ -1910,87 +1931,86 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): elif self.subtype == sRTCM.INTEG_EXT_SIS: # MT2006 self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") - self.fh.write(f" Provider Id: {self.integ.pid}\n") - self.fh.write(f" Area-ID: {self.integ.aid}\n") self.fh.write(f" IOD param: {self.integ.iod_ip}\n") - + self.fh.write(f" Provider Id: {self.integ.pid}\n") + self.fh.write(f" Service Area ID: {self.integ.aid}\n") self.fh.write(f" Validity Period [s]: {self.integ.vp_tbl[self.integ.vp]}\n") self.fh.write(f" Update rate interval [s]: {self.integ.uri*0.1}\n") - self.fh.write(f" Integrity/Continuity Flag: {self.integ.fc}\n") + self.fh.write(f" Time Correlation Integrity/Continuity Flag: {self.integ.fc}\n") b = self.integ.bias - self.fh.write(f" bias (inter-const): {self.integ.b_intc_t[b[0]]}\n") - self.fh.write(f" bias (inter-freq): {self.integ.b_intf_t[b[1]]}\n") + self.fh.write(f" Bounding inter-constellation bia: {self.integ.b_intc_t[b[0]]}\n") + self.fh.write(f" Bounding inter-frequency bias: {self.integ.b_intf_t[b[1]]}\n") - self.fh.write(" iod-mask:\n") + self.fh.write(" Issue of GNSS Satellite Mask:\n") for sys, s in self.integ.iod_m.items(): self.fh.write(f" {sys2char(sys)}: {s}") self.fh.write("\n") - self.fh.write(" sig_cd:\n") + self.fh.write(" Bound on rate of change of PR error stdev:\n") for sys, s in self.integ.sig_cd.items(): self.fh.write(f" {sys2char(sys)}:") for sat, v in s.items(): self.fh.write(f" {sat2id(sat)} {self.integ.sig_cd_t[v]}") self.fh.write("\n") - self.fh.write(" sig_pd:\n") + self.fh.write(" Bound on rate of change in phase error stdev:\n") for sys, s in self.integ.sig_pd.items(): self.fh.write(f" {sys2char(sys)}:") for sat, v in s.items(): self.fh.write(f" {sat2id(sat)} {self.integ.sig_pd_t[v]}") self.fh.write("\n") - self.fh.write(" cbr:\n") + self.fh.write(" Nominal pseudorange bias rate of change:\n") for sys, s in self.integ.cbr.items(): self.fh.write(f" {sys2char(sys)}:") for sat, v in s.items(): self.fh.write(f" {sat2id(sat)} {self.integ.b_rsdsa_t[v]}") self.fh.write("\n") - self.fh.write(" sig_dp:\n") + self.fh.write(" Bounding sigma on phase-range-rate error:\n") for sys, s in self.integ.sig_dp.items(): self.fh.write(f" {sys2char(sys)}:") for sat, v in s.items(): self.fh.write(f" {sat2id(sat)} {self.integ.sig_b_pd_t[v]}") self.fh.write("\n") - self.fh.write(" idx_dp:\n") + self.fh.write(" Phase-range-rate error rate integrity index:\n") for sys, s in self.integ.idx_dp.items(): self.fh.write(f" {sys2char(sys)}:") for sat, v in s.items(): self.fh.write(f" {sat2id(sat)} {self.integ.b_pd_t[v]}") self.fh.write("\n") - self.fh.write(" orbit/clock:\n") + self.fh.write(" Orbit/clock error rate integrity:\n") for sys, s in self.integ.ocr.items(): self.fh.write(f" {sys2char(sys)}:") for sat, v in s.items(): self.fh.write(f" {sat2id(sat)} {self.integ.ocr_t[v]}") self.fh.write("\n") - self.fh.write(" stdev iono:\n") + self.fh.write(" Residual ionospheric error stdev:\n") for sys, s in self.integ.sig_ion.items(): self.fh.write(f" {sys2char(sys)}:") for sat, v in s.items(): self.fh.write(f" {sat2id(sat)} {self.integ.sig_ion_t[v]}") self.fh.write("\n") - self.fh.write(" idx iono:\n") + self.fh.write(" Index of bounding parameter on change in iono error:\n") for sys, s in self.integ.idx_ion.items(): self.fh.write(f" {sys2char(sys)}:") for sat, v in s.items(): self.fh.write(f" {sat2id(sat)} {self.integ.b_datm_t[v]}") self.fh.write("\n") - self.fh.write(" stdev trop:\n") + self.fh.write(" Residual tropospheric error stdev:\n") for sys, s in self.integ.sig_trp.items(): self.fh.write(f" {sys2char(sys)}:") for sat, v in s.items(): self.fh.write(f" {sat2id(sat)} {self.integ.sig_trp_t[v]}") self.fh.write("\n") - self.fh.write(" idx trop:\n") + self.fh.write(" Index of bounding parameter on change in tropo error:\n") for sys, s in self.integ.idx_trp.items(): self.fh.write(f" {sys2char(sys)}:") for sat, v in s.items(): @@ -2004,9 +2024,19 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write(f" IOD area: {self.integ.iod_sa}\n") self.fh.write(f" Validity Period [s]: {self.integ.vp_tbl[self.integ.vp]}\n") self.fh.write(f" Update rate interval [s]: {self.integ.uri*0.1}\n") + + self.fh.write(f" Service Area Type: {self.integ.atype_t[self.integ.atype]}\n") self.fh.write(f" Continuation Flag: {self.integ.cf}\n") self.fh.write(f" Seq: {self.integ.seq}\n") - self.fh.write(f" Area Point (lat, lon):\n {self.integ.pos}\n") + if self.integ.atype == 1: # lat/lon + self.fh.write(f" Number of Area Point: {self.integ.npnt}\n") + self.fh.write(" Area Point (lat, lon):\n") + for pos in self.integ.pos_v: + self.fh.write(f" {pos[0]:10.4f} {pos[1]:10.4f}\n") + else: # lat/lon/radius + pos = self.integ.pos_v + self.fh.write(" Area Point (lat, lon, radius): ") + self.fh.write(f" {pos[0]:10.4f} {pos[1]:10.4f} {pos[2]:10.0f}\n") elif self.subtype == sRTCM.INTEG_EXT_AREA: # MT2008 self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") @@ -2015,15 +2045,15 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write(f" IOD area: {self.integ.iod_sa}\n") self.fh.write(f" Validity Period [s]: {self.integ.vp_tbl[self.integ.vp]}\n") self.fh.write(f" Update rate interval [s]: {self.integ.uri*0.1}\n") - self.fh.write(f" Service Area Type: {self.integ.atype}\n") + self.fh.write(f" Service Area Type: {self.integ.atype_t[self.integ.atype]}\n") self.fh.write(f" Continuation Flag: {self.integ.cf}\n") self.fh.write(f" Seq: {self.integ.seq}\n") - self.fh.write(f" f_ir : {self.integ.f_ir_t[self.integ.f_ir]}\n") - self.fh.write(f" f_ttd: {self.integ.f_ttd_t[self.integ.f_ttd]}\n") - self.fh.write(f" f_ext: {self.integ.ext_df_t[self.integ.f_ext]}\n") - self.fh.write(f" f_exs: {self.integ.exs_df_t[self.integ.f_exs]}\n") + self.fh.write(f" Augmentation IR Degradation Factor: {self.integ.f_ir_t[self.integ.f_ir]}\n") + self.fh.write(f" Augmentation TTD Degradation Factor: {self.integ.f_ttd_t[self.integ.f_ttd]}\n") + self.fh.write(f" Extended Area Time Parameter Degradation Factor: {self.integ.ext_df_t[self.integ.f_ext]}\n") + self.fh.write(f" Extended Area Spatial Parameter Degradation Factor: {self.integ.exs_df_t[self.integ.f_exs]}\n") self.fh.write(f" Area Point (lat/lon): {self.integ.pos}\n") @@ -3580,27 +3610,28 @@ def decode_integrity_pri_service_area(self, msg, i): if atype == 1: # Validity Area Parameters defined in Table 10.3-5 # number of area points DFi201 - narea = bs.unpack_from('u8', msg, i)[0] + npnt = bs.unpack_from('u8', msg, i)[0] + self.integ.npnt = npnt i += 8 - self.integ.pos = np.zeros((narea, 3)) - for k in range(narea): + self.integ.pos_v = np.zeros((npnt, 2)) + for k in range(npnt): # Area Point - Lat DFi202 # Area Point - Lon DFi203 lat_i, lon_i = bs.unpack_from('s34s35', msg, i) i += 69 lat = self.sval(lat_i, 34, 0.000000011) lon = self.sval(lon_i, 35, 0.000000011) - self.integ.pos[k, :] = [lat, lon, 0] + self.integ.pos_v[k, :] = [lat, lon] elif atype == 0: # Validity Radius Data defined in Table 10.3-6 # Area Point - Lat DFi202 # Area Point - Lon DFi203 - # Validity Radius DF057 + # Validity Radius DFi057 lat_i, lon_i, r = bs.unpack_from('s34s35u20', msg, i) i += 89 lat = self.sval(lat_i, 34, 0.000000011) lon = self.sval(lon_i, 35, 0.000000011) - self.integ.pos = [lat, lon, r] + self.integ.pos_v = [lat, lon, r] return i def decode_integrity_ext_service_area(self, msg, i): From 8336ca8b312b3a09af72e648c1c0f7f02f4652e6 Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Wed, 28 Jan 2026 22:39:53 +0900 Subject: [PATCH 34/48] - added missing seq in sc134 messages. --- src/cssrlib/rtcm.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/cssrlib/rtcm.py b/src/cssrlib/rtcm.py index ee1bd8c..babb5eb 100644 --- a/src/cssrlib/rtcm.py +++ b/src/cssrlib/rtcm.py @@ -2103,6 +2103,8 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): elif self.subtype == sRTCM.INTEG_VMAP: # MT2071 self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") self.fh.write(f" Number of Area Points: {self.integ.narea:}\n") + self.fh.write(f" Continuation Flag: {self.integ.cf}\n") + self.fh.write(f" Seq: {self.integ.seq}\n") self.fh.write(" Boundary Points (lat, long, alt, naz):\n") for k in range(self.integ.narea): self.fh.write(" {:2d}\t{:12.9f}\t{:12.9f}\t{:4.0f}\t{:2d}\n".format @@ -2119,6 +2121,8 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") self.fh.write(f" Number of Area Points: {self.integ.narea:}\n") self.fh.write(f" Multipath Model ID: {model}\n") + self.fh.write(f" Continuation Flag: {self.integ.cf}\n") + self.fh.write(f" Seq: {self.integ.seq}\n") self.fh.write(" Boundary Points (lat, long, alt), np:\n") for k in range(self.integ.narea): self.fh.write(" {:2d}\t{:12.9f}\t{:12.9f}\t{:4.0f}\t{:2d}\n".format @@ -3789,13 +3793,13 @@ def decode_integrity_vmap(self, msg, i): # number of area points DFi201 # Message Continuation Flag DFi021 # Multiple Message Sequence Number DFi079 - tow, narea, mi, seq = bs.unpack_from('u30u8u1u5', msg, i) + tow, narea, cf, seq = bs.unpack_from('u30u8u1u5', msg, i) i += 44 - self.integ.seq = seq self.integ.tow = tow*1e-3 self.integ.narea = narea - self.mi = mi + self.integ.cf = cf + self.integ.seq = seq self.integ.pos = np.zeros((narea, 3)) self.integ.naz = np.zeros(narea, dtype=int) self.integ.azel = {} @@ -3832,7 +3836,7 @@ def decode_integrity_mmap(self, msg, i): # GNSS Epoch Time (TOW) DFi008 # number of area points DFi201 # multipath model ID DFi209: 0:GMM,1:MBM,2:JM - tow, narea, mm_id, self.mi, self.integ.seq = bs.unpack_from( + tow, narea, mm_id, self.integ.cf, self.integ.seq = bs.unpack_from( 'u30u8u3u1u5', msg, i) i += 47 self.integ.tow = tow*1e-3 @@ -4136,8 +4140,10 @@ def decode(self, msg, subtype=None, scanmode=False): elif self.msgtype == 54: # SSR integrity test msg i = self.decode_sc134_test_header(msg, i) if self.subtype == 9: + self.subtype = sRTCM.INTEG_VMAP self.decode_integrity_vmap(msg, i) elif self.subtype == 10: + self.subtype = sRTCM.INTEG_MMAP self.decode_integrity_mmap(msg, i) else: self.subtype = -1 From e25ac8988a9511e21edb6939d1dca906efbd0557 Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sat, 31 Jan 2026 14:00:30 +0900 Subject: [PATCH 35/48] - fixed missing item. --- src/cssrlib/rtcm.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cssrlib/rtcm.py b/src/cssrlib/rtcm.py index babb5eb..87b7ba6 100644 --- a/src/cssrlib/rtcm.py +++ b/src/cssrlib/rtcm.py @@ -313,6 +313,7 @@ class rtcmUtil: sCSSR.MASK: sCType.MASK, sCSSR.CBIAS: sCType.CBIAS, sCSSR.PBIAS: sCType.PBIAS, sCSSR.URA: sCType.URA, sCSSR.GRID: sCType.TROP, sCSSR.STEC: sCType.STEC, + sCSSR.COMBINED: sCType.OC, sRTCM.SSR_PBIAS: sCType.PBIAS, sRTCM.SSR_TROP: sCType.TROP, sRTCM.SSR_STEC: sCType.STEC, From 3d157bea0ea09f48b41e58065c980eda730bf938 Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sat, 31 Jan 2026 22:38:14 +0900 Subject: [PATCH 36/48] - optimized by eliminating frequent update of sun position, antTx position. (suggested by mfinneman) --- src/cssrlib/gnss.py | 7 +++++-- src/cssrlib/peph.py | 8 ++++---- src/cssrlib/ppp.py | 6 +++--- src/cssrlib/pppssr.py | 22 +++++++++++++++------- 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/cssrlib/gnss.py b/src/cssrlib/gnss.py index c618c8f..a693fea 100644 --- a/src/cssrlib/gnss.py +++ b/src/cssrlib/gnss.py @@ -9,7 +9,7 @@ from datetime import datetime, timezone import bitstruct as bs import yaml - +from numba import njit gpst0 = [1980, 1, 6, 0, 0, 0] # GPS system time reference gst0 = [1999, 8, 22, 0, 0, 0] # Galileo system time reference @@ -951,6 +951,9 @@ def __init__(self, nf=2): # number of satellite (observed, calculated, corrected) self.nsat = [0, 0, 0] + + self.t_rsun = None # reference time of rsun + self.rsun = None # placeholder of sun position def epoch2time(ep): @@ -1208,7 +1211,7 @@ def prn2sat(sys, prn): sat = 0 return sat - +@njit def sat2prn(sat): """ convert sat to sys+prn """ if sat > uGNSS.MAXSAT: diff --git a/src/cssrlib/peph.py b/src/cssrlib/peph.py index 0593c47..b9ee22d 100644 --- a/src/cssrlib/peph.py +++ b/src/cssrlib/peph.py @@ -795,7 +795,7 @@ def antModelTx(nav, e, sigs, sat, time, rs, sig0=None): # Rotation matrix from satellite antenna frame to ECEF frame [ex, ey, ez] # - A = orb2ecef(time, rs) + A = orb2ecef(time, rs, nav.rsun) ez = A[2, :] # Zenith angle and zenith angle grid @@ -1124,14 +1124,14 @@ def utc2gmst(t: gtime_t, ut1_utc): return np.fmod(gmst, 86400.0)*np.pi/43200.0 # 0 <= gmst <= 2*PI -def orb2ecef(time, rs): +def orb2ecef(time, rs, rsun=None): """ Rotation matrix from satellite antenna frame to ECEF frame assuming standard yaw attitude law """ - erpv = np.zeros(5) - rsun, _, _ = sunmoonpos(gpst2utc(time), erpv, True) + if rsun is None: + rsun, _, _ = sunmoonpos(gpst2utc(time), np.zeros(5), True) r = -rs ez = r/np.linalg.norm(r) r = rsun-rs diff --git a/src/cssrlib/ppp.py b/src/cssrlib/ppp.py index 90e993b..35ee99e 100644 --- a/src/cssrlib/ppp.py +++ b/src/cssrlib/ppp.py @@ -274,13 +274,12 @@ def shapiro(rsat, rrcv): return corr -def windupcorr(time, rs, vs, rr, phw, full=False): +def windupcorr(time, rs, vs, rr, rsun, phw, full=False): """ calculate windup correction """ ek = vnorm(rr-rs) if full: # Satellite antenna frame unit vectors assuming standard yaw attitude law # - rsun, _, _ = sunmoonpos(gpst2utc(time)) r = -rs ezs = r/np.linalg.norm(r) r = rsun-rs @@ -443,7 +442,8 @@ def tidedispIERS2010(tutc, pos, erpv=None): tn = timeadd(tgps_, k*300) eph = findeph(nav.eph, tn, sat) rs_, vs_, dts = eph2pos(tn, eph, True) - phw_ = windupcorr(tn, rs_, vs_, rr_, phw_) + rsun_, _, _ = sunmoonpos(gpst2utc(tn)) + phw_ = windupcorr(tn, rs_, vs_, rr_, rsun_, phw_) t[k] = timediff(tn, tgps_) ph_[k] = phw_ d[k] = shapiro(rs_, rr_) diff --git a/src/cssrlib/pppssr.py b/src/cssrlib/pppssr.py index 36bc26c..e434464 100644 --- a/src/cssrlib/pppssr.py +++ b/src/cssrlib/pppssr.py @@ -9,7 +9,7 @@ from cssrlib.gnss import ecef2pos, tropmodel, geodist, satazel, uTideModel from cssrlib.gnss import time2str, timediff, gpst2utc, tropmapf from cssrlib.gnss import trop_model_tbl, iono_model_tbl, default_config -from cssrlib.ppp import tidedisp, tidedispIERS2010, shapiro, windupcorr +from cssrlib.ppp import tidedisp, tidedispIERS2010, shapiro, windupcorr, sunmoonpos from cssrlib.peph import antModelRx, antModelTx from cssrlib.cssrlib import sCType from cssrlib.cssrlib import sCSSRTYPE as sc @@ -656,7 +656,8 @@ def zdres(self, obs, cs, bsx, rs, vs, dts, rr, rtype=1): if self.nav.phw_opt > 0: phw_mode = (False if self.nav.phw_opt == 2 else True) self.nav.phw[sat-1] = windupcorr(obs.t, rs[i, :], vs[i, :], - rr_, self.nav.phw[sat-1], + rr_, self.nav.rsun, + self.nav.phw[sat-1], full=phw_mode) # cycle -> m @@ -738,11 +739,11 @@ def zdres(self, obs, cs, bsx, rs, vs, dts, rr, rtype=1): sc.RTCM3_SSR, sc.BDS_PPP, sc.PVS_PPP): - - antsPR = antModelTx(self.nav, e[i, :], sigsPR, - sat, obs.t, rs[i, :], sig0) - antsCP = antModelTx(self.nav, e[i, :], sigsCP, - sat, obs.t, rs[i, :], sig0) + + ants = antModelTx(self.nav, e[i, :], (sigsPR + sigsCP), + sat, obs.t, rs[i, :], sig0) + antsPR = ants[0:len(sigsPR)] + antsCP = ants[len(sigsPR):] else: @@ -1390,6 +1391,13 @@ def process(self, obs, cs=None, orb=None, bsx=None, obsb=None): self.nav.nsat[0] = len(obs.sat) + # sun position is updated every 1sec + if (self.nav.rsun is None) or \ + (np.abs(timediff(self.nav.t_rsun,obs.t))>=1.0): + self.nav.rsun, _, _ = sunmoonpos(gpst2utc(obs.t)) + self.nav.t_rsun = obs.t + + # GNSS satellite positions, velocities and clock offsets # for all satellite in RINEX observations # From 594924df3a0a099f324799e31b98942e10da9161 Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sun, 1 Feb 2026 22:20:01 +0900 Subject: [PATCH 37/48] - fixed ppprtk config loading. - fixed week align in ccsr_pvs - fixed trph of ssr --- src/cssrlib/cssr_pvs.py | 8 ++++---- src/cssrlib/cssrlib.py | 2 +- src/cssrlib/ppprtk.py | 14 ++++++++------ 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/cssrlib/cssr_pvs.py b/src/cssrlib/cssr_pvs.py index 7f1cd31..2416174 100644 --- a/src/cssrlib/cssr_pvs.py +++ b/src/cssrlib/cssr_pvs.py @@ -87,10 +87,10 @@ def slot2sat(self, slot): def adjust_time_week(self, time, time0): dt = timediff(time, time0) - if dt > rCST.DAY_SEC/2: - time = timeadd(time, -rCST.DAY_SEC) - elif dt < -rCST.DAY_SEC/2: - time = timeadd(time, rCST.DAY_SEC) + if dt > rCST.HALFWEEK_SEC: + time = timeadd(time, -rCST.WEEK_SEC) + elif dt < -rCST.HALFWEEK_SEC: + time = timeadd(time, rCST.WEEK_SEC) return time def check_validity(self, time): diff --git a/src/cssrlib/cssrlib.py b/src/cssrlib/cssrlib.py index 8c27f9b..2d8fa33 100644 --- a/src/cssrlib/cssrlib.py +++ b/src/cssrlib/cssrlib.py @@ -1337,7 +1337,7 @@ def get_trop(self, dlat=0.0, dlon=0.0): trpw = 0 if self.lc[inet].flg_trop & 2: p = [1, dlat, dlon, dlat*dlon] - trph = 2.3+self.lc[inet].ct[0, :]@p + trph = self.lc[inet].ct[0, :]@p trpw = self.lc[inet].ct[1, :]@p if self.lc[inet].flg_trop & 1: if self.lc[inet].dth is not None: diff --git a/src/cssrlib/ppprtk.py b/src/cssrlib/ppprtk.py index e1c90d2..013db03 100644 --- a/src/cssrlib/ppprtk.py +++ b/src/cssrlib/ppprtk.py @@ -15,9 +15,11 @@ def __init__(self, nav, pos0=np.zeros(3), logfile=None, config=None): # trop, iono from cssr # phase windup model is local/regional super().__init__(nav=nav, pos0=pos0, logfile=logfile, config=config) - - # self.nav.eratio = np.ones(self.nav.nf)*50 # [-] factor - # self.nav.err = [0, 0.01, 0.005]/np.sqrt(2) # [m] sigma - # self.nav.sig_p0 = 30.0 # [m] - # self.nav.thresar = 2.0 # AR acceptance threshold - # self.nav.armode = 3 # AR is enabled + + self.nav.eratio = config['nav']['eratio'] + self.nav.err = config['nav']['err'] + self.nav.sig_p0 = config['nav']['sig_p0'] + self.nav.thresar = config['nav']['thresar'] # AR acceptance threshold + self.nav.armode = config['nav']['armode'] # AR is enabled + self.nav.elmaskar = np.deg2rad(config['nav']['elmaskar']) # elevation mask for AR + self.nav.elmin = np.deg2rad(config['nav']['elmin']) \ No newline at end of file From f37c3206dad64c360467636b1a563f14291f5e89 Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sun, 8 Feb 2026 14:46:01 +0900 Subject: [PATCH 38/48] - increased max PRN for GLO to support R28 - log file name changed. --- src/cssrlib/gnss.py | 4 +-- src/cssrlib/rawnav.py | 58 +++++++++++++++++-------------------------- 2 files changed, 25 insertions(+), 37 deletions(-) diff --git a/src/cssrlib/gnss.py b/src/cssrlib/gnss.py index a693fea..6ed9428 100644 --- a/src/cssrlib/gnss.py +++ b/src/cssrlib/gnss.py @@ -153,7 +153,7 @@ class uGNSS(IntEnum): GALMAX = 36 QZSMAX = 17 BDSMAX = 63 - GLOMAX = 27 + GLOMAX = 32 SBSMAX = 39 IRNMAX = 14 @@ -170,7 +170,7 @@ class uGNSS(IntEnum): MINPRNGPS = 1 MAXPRNGPS = 36 MINPRNGLO = 1 - MAXPRNGLO = 27 + MAXPRNGLO = 32 MINPRNGAL = 1 MAXPRNGAL = 36 MINPRNQZS = 193 diff --git a/src/cssrlib/rawnav.py b/src/cssrlib/rawnav.py index b6ea70d..c4c66e3 100644 --- a/src/cssrlib/rawnav.py +++ b/src/cssrlib/rawnav.py @@ -28,7 +28,7 @@ from cssrlib.gnss import gpst2time, gst2time, bdt2time, rCST from cssrlib.gnss import uGNSS, sat2prn, bdt2gpst, utc2gpst, time2gpst from cssrlib.gnss import Eph, Geph, uTYP, copy_buff, gtime_t, timeadd, Seph -from cssrlib.gnss import tod2tow +from cssrlib.gnss import tod2tow, char2sys from cssrlib.rinex import rnxenc, rSigRnx @@ -1987,8 +1987,10 @@ class rcvDec(): re = None # placeholder for RINEX encoder sig_tab = {} + sys_t = [] # list of enabled GNSS def __init__(self, opt=None, prefix='', gnss_t='GECJ'): + self.sys_t = char2sys(gnss_t) self.sig_tab = self.init_sig_tab(gnss_t) if opt is not None: self.init_param(opt, prefix) @@ -2086,64 +2088,53 @@ def init_param(self, opt: rcvOpt, prefix=''): if opt.flg_qzslnav: self.flg_qzslnav = True - self.file_qzslnav = "qzslnav.txt" - self.fh_qzslnav = open(prefix+self.file_qzslnav, mode='w') + if self.fh_gpslnav is None: + self.fh_gpslnav = open(prefix+'.lnv', mode='w') if opt.flg_gpslnav: self.flg_gpslnav = True - self.file_gpslnav = "gpslnav.txt" - self.fh_gpslnav = open(prefix+self.file_gpslnav, mode='w') + self.fh_gpslnav = open(prefix+'.lnv', mode='w') if opt.flg_qzscnav: self.flg_qzscnav = True - self.file_qzscnav = "qzscnav.txt" - self.fh_qzscnav = open(prefix+self.file_qzscnav, mode='w') + if self.fh_gpscnav is None: + self.fh_gpscnav = open(prefix+'.cnv', mode='w') if opt.flg_gpscnav: self.flg_gpscnav = True - self.file_gpscnav = "gpscnav.txt" - self.fh_gpscnav = open(prefix+self.file_gpscnav, mode='w') + self.fh_gpscnav = open(prefix+'.cnv', mode='w') if opt.flg_qzscnav2: self.flg_qzscnav2 = True - self.file_qzscnav2 = "qzscnav2.txt" - self.fh_qzscnav2 = open(prefix+self.file_qzscnav2, mode='w') + if self.fh_gpscnav2 is None: + self.fh_gpscnav2 = open(prefix+'.cn2', mode='w') if opt.flg_gpscnav2: self.flg_gpscnav2 = True - self.file_gpscnav2 = "gpscnav2.txt" - self.fh_gpscnav2 = open(prefix+self.file_gpscnav2, mode='w') + self.fh_gpscnav2 = open(prefix+'.cn2', mode='w') if opt.flg_qzsl6: self.flg_qzsl6 = True - self.file_qzsl6 = "qzsl6.txt" - self.fh_qzsl6 = open(prefix+self.file_qzsl6, mode='w') + self.fh_qzsl6 = open(prefix+'.l6', mode='w') if opt.flg_qzsl1s: self.flg_qzsl1s = True if self.fh_sbas is None: - self.file_sbas = "sbas.txt" - self.fh_sbas = open(prefix+self.file_sbas, mode='w') + self.fh_sbas = open(prefix+'.sbs', mode='w') if opt.flg_qzsl5s: self.flg_qzsl5s = True if self.fh_sbas is None: - self.file_sbas = "sbas.txt" - self.fh_sbas = open(prefix+self.file_sbas, mode='w') + self.fh_sbas = open(prefix+'.sbs', mode='w') if opt.flg_gale6: self.flg_gale6 = True - self.file_gale6 = "gale6.txt" - self.fh_gale6 = open(prefix+self.file_gale6, mode='w') + self.fh_gale6 = open(prefix+'.e6', mode='w') if opt.flg_galinav: self.flg_galinav = True - self.file_galinav = "galinav.txt" - self.fh_galinav = open(prefix+self.file_galinav, mode='w') + self.fh_galinav = open(prefix+'.inv', mode='w') if opt.flg_galfnav: self.flg_galfnav = True - self.file_galfnav = "galfnav.txt" - self.fh_galfnav = open(prefix+self.file_galfnav, mode='w') + self.fh_galfnav = open(prefix+'.fnv', mode='w') if opt.flg_bdsb1c: self.flg_bdsb1c = True - self.file_bdsb1c = "bdsb1c.txt" - self.fh_bdsb1c = open(prefix+self.file_bdsb1c, mode='w') + #self.fh_bdsb1c = open(prefix+'.b1c', mode='w') if opt.flg_bdsb2a: self.flg_bdsb2a = True if opt.flg_bdsb2b: self.flg_bdsb2b = True - self.file_bdsb2b = "bdsb2b.txt" - self.fh_bdsb2b = open(prefix+self.file_bdsb2b, mode='w') + self.fh_bdsb2b = open(prefix+'.b2b', mode='w') if opt.flg_bdsd12: self.flg_bdsd12 = True if opt.flg_gloca: @@ -2153,17 +2144,14 @@ def init_param(self, opt: rcvOpt, prefix=''): if opt.flg_sbas: self.flg_sbas = True if self.fh_sbas is None: - self.file_sbas = "sbas.txt" - self.fh_sbas = open(prefix+self.file_sbas, mode='w') + self.fh_sbas = open(prefix+'.sbs', mode='w') if opt.flg_rnxnav: self.flg_rnxnav = True - self.file_rnxnav = "rnx.nav" - self.fh_rnxnav = open(prefix+self.file_rnxnav, mode='w') + self.fh_rnxnav = open(prefix+'.nav', mode='w') self.re.rnx_nav_header(self.fh_rnxnav) if opt.flg_rnxobs: self.flg_rnxobs = True - self.file_rnxobs = "rnx.obs" - self.fh_rnxobs = open(prefix+self.file_rnxobs, mode='w') + self.fh_rnxobs = open(prefix+'.obs', mode='w') # self.re.rnx_obs_header(self.fh_rnxobs) if opt.useL1CB: # use QZSS L1CB in place of L1CA self.useL1CB = True From 5d6cd79dd5e55ef5b7792cf1130f4765175d2599 Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Wed, 11 Feb 2026 21:32:19 +0900 Subject: [PATCH 39/48] - added maximum nsig check in RTCM SSR decoder. --- src/cssrlib/rtcm.py | 50 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/src/cssrlib/rtcm.py b/src/cssrlib/rtcm.py index 87b7ba6..caa1605 100644 --- a/src/cssrlib/rtcm.py +++ b/src/cssrlib/rtcm.py @@ -204,6 +204,7 @@ def __init__(self): self.pidssr = 0 # SSR provider ID self.sidssr = 0 # SSR solution type self.iodssr = 0 # SSR iod + self.inet = 0 self.tow = 0 self.iod_sys = {} # issue of GNSS satellite mask DFi010 (b2) @@ -403,6 +404,8 @@ def get_ssr_sys(self, msgtype): return uGNSS.SBS elif msgtype >= 1258 and msgtype < 1264: return uGNSS.BDS + elif msgtype == 1264: + return uGNSS.NONE elif msgtype >= 1265 and msgtype < 1271: # proposed phase bias tbl_t = {1265: uGNSS.GPS, 1266: uGNSS.GLO, 1267: uGNSS.GAL, 1268: uGNSS.QZS, 1269: uGNSS.SBS, 1270: uGNSS.BDS} @@ -676,6 +679,8 @@ def is_ssrtype(self, msgtype, tstmsg=False): for sys_ in self.ssr_t.keys(): if msgtype >= self.ssr_t[sys_] and msgtype <= self.ssr_t[sys_]+6: return True + if msgtype >= 1264 and msgtype <= 1270: # VTEC, PBIAS (obsoleted) + return True if tstmsg and msgtype >= 60 and msgtype <= 100: # SSR test message return True return False @@ -770,6 +775,7 @@ def __init__(self, foutname=None): self.pid = 0 # SSR Provider ID self.sid = 0 # SSR Solution Type + self.inet = 0 self.mi = False self.nrtk_r = {} @@ -786,8 +792,10 @@ def __init__(self, foutname=None): self.antc = {} self.integ = Integrity() self.test_mode = False # for interop testing in SC134 + + self.mt_skip = [] # skip decode for the specified message types - def ssig2rsig(self, sys: uGNSS, utyp: uTYP, ssig): + def ssig2rsig(self, sys: uGNSS, utyp=None, ssig=None): """ convert ssig to rSigRnx """ gps_tbl = { 0: uSIG.L1C, @@ -807,6 +815,7 @@ def ssig2rsig(self, sys: uGNSS, utyp: uTYP, ssig): 18: uSIG.L1L, 19: uSIG.L1X, } + glo_tbl = { 0: uSIG.L1C, 1: uSIG.L1P, @@ -896,6 +905,13 @@ def ssig2rsig(self, sys: uGNSS, utyp: uTYP, ssig): } usig_tbl = usig_tbl_[sys] + + if ssig is None: + return len(usig_tbl) + + if ssig not in usig_tbl: + print(f"sys={sys} sig={ssig} undefined.") + return rSigRnx() return rSigRnx(sys, utyp, usig_tbl[ssig]) def sync(self, buff, k): @@ -945,6 +961,7 @@ def decode_head(self, msg, i, sys=uGNSS.NONE): if self.subtype == sCSSR.PBIAS: ci, mw = bs.unpack_from('u1u1', msg, i) + self.iexpb = 0 i += 2 elif self.subtype == sRTCM.SSR_PBIAS: # Satellite Yaw Information Indicator DF486 @@ -1126,6 +1143,9 @@ def decode_cssr_cbias(self, msg, i, inet=0): if sat_ not in self.lc[inet].cbias: self.lc[inet].cbias[sat_] = {} + if nsig > self.ssig2rsig(sys): + break + for j in range(nsig): sig, cb = bs.unpack_from('u5s14', msg, i) i += 19 @@ -1158,7 +1178,7 @@ def decode_cssr_pbias(self, msg, i, inet=0): self.iodssr = v['iodssr'] self.udi[sCType.PBIAS] = v['udi'] self.mi = v['mi'] - self.iyaw = v['iyaw'] + self.iyaw = v['iyaw'] if 'iyaw' in v.keys() else True inet = self.inet @@ -1186,6 +1206,9 @@ def decode_cssr_pbias(self, msg, i, inet=0): self.lc[inet].di[sat_] = {} self.lc[inet].wl[sat_] = {} + if nsig > self.ssig2rsig(sys): + break + for j in range(nsig): # tracking mode: DF461 # signal integer indicator: DF483 @@ -1654,7 +1677,7 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write(" {:s}\t".format(sat2id(sat_))) if self.iyaw: - self.fh.write("\t{:7.4f}\t{:7.4f}\t" + self.fh.write("{:7.4f}\t{:7.4f}\t" .format(self.lc[inet].yaw[sat_], self.lc[inet].dyaw[sat_])) @@ -3354,6 +3377,8 @@ def decode_integrity_ext(self, msg, i): i += 82 self.integ.tow = tow*1e-3 + self.tow = self.integ.tow + self.time = gpst2time(self.week, self.tow) self.integ.pid = pid self.integ.aid = area self.integ.ilvl = ilvl @@ -3494,6 +3519,8 @@ def decode_integrity_ext_sis_local(self, msg, i): i += 58 self.integ.tow = tow*1e-3 + self.tow = self.integ.tow + self.time = gpst2time(self.week, self.tow) self.integ.iod_ip = iod_ip self.integ.pid = pid self.integ.aid = area @@ -3594,6 +3621,8 @@ def decode_integrity_pri_service_area(self, msg, i): self.integ.iod_sa = iod_sa self.integ.tow = tow*1e-3 + self.tow = self.integ.tow + self.time = gpst2time(self.week, self.tow) self.integ.pid = pid self.integ.aid = area self.integ.vp = vp @@ -3666,6 +3695,8 @@ def decode_integrity_ext_service_area(self, msg, i): i += 10 self.integ.tow = tow*1e-3 + self.tow = self.integ.tow + self.time = gpst2time(self.week, self.tow) self.integ.iod_sa = iod_sa self.integ.pid = pid self.integ.aid = area @@ -3722,6 +3753,8 @@ def decode_integrity_quality(self, msg, i): i += 32 self.integ.tow = tow*1e-3 + self.tow = self.integ.tow + self.time = gpst2time(self.week, self.tow) self.integ.pid = pid self.integ.vp = vp self.integ.uri = uri @@ -3746,6 +3779,8 @@ def decode_integrity_cnr_acg(self, msg, i): i += 84 self.integ.tow = tow*1e-3 + self.tow = self.integ.tow + self.time = gpst2time(self.week, self.tow) self.integ.pid = pid self.integ.uri = uri self.integ.aid = area @@ -3798,6 +3833,8 @@ def decode_integrity_vmap(self, msg, i): i += 44 self.integ.tow = tow*1e-3 + self.tow = self.integ.tow + self.time = gpst2time(self.week, self.tow) self.integ.narea = narea self.integ.cf = cf self.integ.seq = seq @@ -3841,6 +3878,8 @@ def decode_integrity_mmap(self, msg, i): 'u30u8u3u1u5', msg, i) i += 47 self.integ.tow = tow*1e-3 + self.tow = self.integ.tow + self.time = gpst2time(self.week, self.tow) self.integ.narea = narea self.integ.pos = np.zeros((narea, 3)) self.integ.mm_id = mm_id @@ -3924,6 +3963,8 @@ def decode_integrity_ssr(self, msg, i): i += 46 self.integ.tow = tow*1e-3 + self.tow = self.integ.tow + self.time = gpst2time(self.week, self.tow) self.integ.mask_sys = mask_sys vp, uri = bs.unpack_from('u4u16', msg, i) @@ -3995,6 +4036,9 @@ def decode(self, msg, subtype=None, scanmode=False): self.subtype = subtype + if self.msgtype in self.mt_skip: + return i + # SSR messages if self.msgtype in (1057, 1063, 1240, 1246, 1252, 1258): self.subtype = sCSSR.ORBIT From f6b36906e18a45cf494ffce66bb949f7aed1a427 Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Wed, 11 Feb 2026 21:35:59 +0900 Subject: [PATCH 40/48] added option for clock bias reset, default is 1e2 [m]. --- src/cssrlib/pntpos.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cssrlib/pntpos.py b/src/cssrlib/pntpos.py index 0d8b1dc..6793ae8 100644 --- a/src/cssrlib/pntpos.py +++ b/src/cssrlib/pntpos.py @@ -134,6 +134,8 @@ def __init__(self, nav, pos0=np.array([0, 0, rCST.RE_WGS84]), logfile=None, trop self.nav.elmin = np.deg2rad(10.0) + self.nav.th_clk_rst = 1e2 # threshold value for clock bias initialize/reset + self.nav.obad = None self.nsat = 0 @@ -560,7 +562,7 @@ def process(self, obs, cs=None, orb=None, bsx=None, obsb=None): # v, H, R = self.sdres(obs, xp, y, e, sat, el) - if abs(np.mean(v)) > 1e3: # clock bias initialize/reset + if abs(np.mean(v)) > self.nav.th_clk_rst: # clock bias initialize/reset ic = self.ICB() idx_ = np.where(v != 0.0)[0] xp[ic] = np.mean(v[idx_]) From 01bb005b7b9ae63608af07a5e9db503ab5920c7f Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sat, 14 Feb 2026 20:08:01 +0900 Subject: [PATCH 41/48] - fixed glonass time in rtcm-ssr - code cleanup on sc134. --- src/cssrlib/rtcm.py | 175 +++++++++++++++++++++----------------------- 1 file changed, 82 insertions(+), 93 deletions(-) diff --git a/src/cssrlib/rtcm.py b/src/cssrlib/rtcm.py index caa1605..351619f 100644 --- a/src/cssrlib/rtcm.py +++ b/src/cssrlib/rtcm.py @@ -198,7 +198,7 @@ def __init__(self): self.pid = 0 # augmentation provider id DFi027 (0-4095) self.vp = 0 # validity period DFi065 (0-15) - self.uri = 0 # update rate interval DFi067 (b16) + self.udi = 0 # update rate interval DFi067 (b16) # placeholder for RTCM SSR self.pidssr = 0 # SSR provider ID @@ -935,13 +935,15 @@ def checksum(self, msg, k, maxlen=0): def decode_head(self, msg, i, sys=uGNSS.NONE): """ decode the header of ssr message """ - if self.msgtype == 4076 or sys != uGNSS.GLO: - blen = 20 + + if sys==uGNSS.GLO and self.msgtype != 4076: + self.tod = bs.unpack_from('u17', msg, i)[0] + i += 17 + self.time = utc2gpst(glo2time(self.time, self.tod)) else: - blen = 17 - self.tow = bs.unpack_from('u'+str(blen), msg, i)[0] - self.time = gpst2time(self.week, self.tow) - i += blen + self.tow = bs.unpack_from('u20', msg, i)[0] + i += 20 + self.time = gpst2time(self.week, self.tow) if self.subtype not in [sRTCM.SSR_PBIAS_EX]: udi, mi = bs.unpack_from('u4b1', msg, i) @@ -1545,6 +1547,13 @@ def out_log_ssr_ura(self, sys): self.fh.write(" {:s}\t{:8.4f}\n".format(sat2id(sat_), self.lc[0].ura[sat_])) + def out_log_integ_head(self): + vp = self.integ.vp_tbl[self.integ.vp] + self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") + self.fh.write(f" Provider Id: {self.integ.pid}\n") + self.fh.write(f" Validity Period [s]: {vp:6d}\n") + self.fh.write(f" Update rate interval [s]: {self.integ.udi*0.1}\n") + def out_log(self, obs=None, eph=None, geph=None, seph=None): """ output ssr message to log file """ sys = -1 @@ -1769,36 +1778,6 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write(" {:20s}{:6d}\n".format("Number of Signals:", self.nsig)) - if self.subtype in (sRTCM.INTEG_SSR, sRTCM.INTEG_SSR_IONO, - sRTCM.INTEG_SSR_TROP): - self.fh.write(" {:20s}{:6d} TOW={:6d}\n". - format("INTEG-SSR:", self.msgtype, - int(self.integ.tow))) - self.fh.write(" {:20s}{:6d}\n".format("ProviderID:", - self.integ.pid)) - - if self.subtype == sRTCM.INTEG_SSR: - self.fh.write(" {:20s}{:6.1f}\n".format("Varidity Period [s]:", - self.integ.vp_tbl[self.integ.vp])) - self.fh.write(" {:20s}{:6.1f}\n".format("Update Interval:", - self.integ.uri)) - - self.fh.write(" {:20s}{:04x}\n".format("Constellation Mask:", - self.integ.mask_sys)) - self.fh.write(" IOD GNSS Mask: ") - for sys in self.integ.iod_sys.keys(): - self.fh.write(" {:8s}: {:1d}".format( - sys2str(sys), self.integ.iod_sys[sys])) - self.fh.write("\n") - - for sys in self.integ.flag.keys(): - self.fh.write(" NIntegrity Flag: ") - for sat in self.integ.flag[sys]: - self.fh.write(" {:3s}:{:2d}".format( - sat2id(sat), self.integ.flag[sys][sat])) - self.fh.write("\n") - self.fh.flush() - if self.msgtype == 54 or self.msgtype in self.integ.mt_integ_t: # RTCM SC134 integrity messages if self.msgtype == 54: self.fh.write(f" Message Type: {self.msgtype}\n") @@ -1806,11 +1785,7 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write(f" Message Type: {self.integ.mt_integ_t[self.msgtype]} ({self.msgtype})\n") if self.subtype == sRTCM.INTEG_MIN: # MT2000 - self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") - self.fh.write(f" Provider Id: {self.integ.pid}\n") - - self.fh.write(f" Validity Period [s]: {self.integ.vp_tbl[self.integ.vp]}\n") - self.fh.write(f" Update rate interval [s]: {self.integ.uri*0.1}\n") + self.out_log_integ_head() self.fh.write(" Issue of Sat Mask: ") for sys in self.integ.iod_sys: @@ -1869,16 +1844,13 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write("\n") elif self.subtype == sRTCM.INTEG_EXT: # MT2005 - self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") - self.fh.write(f" Provider Id: {self.integ.pid}\n") + self.out_log_integ_head() + self.fh.write(f" Area ID: {self.integ.aid}\n") self.fh.write(f" Integrity Level: {self.integ.ilvl}\n") self.fh.write(f" TTT: {self.integ.ttt}\n") self.fh.write(f" Solution Type: {self.integ.decode_sol_t(self.integ.stype)}\n") - self.fh.write(f" Validity Period [s]: {self.integ.vp_tbl[self.integ.vp]}\n") - self.fh.write(f" Update rate interval [s]: {self.integ.uri*0.1}\n") - self.fh.write(f" Paug: {self.integ.Paug}\n") self.fh.write(f" IOD param: {self.integ.iod_p}\n") self.fh.write(f" Integrity/Continuity Flag: {self.integ.fc}\n") @@ -1954,12 +1926,10 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write("\n") elif self.subtype == sRTCM.INTEG_EXT_SIS: # MT2006 - self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") + self.out_log_integ_head() + self.fh.write(f" IOD param: {self.integ.iod_ip}\n") - self.fh.write(f" Provider Id: {self.integ.pid}\n") self.fh.write(f" Service Area ID: {self.integ.aid}\n") - self.fh.write(f" Validity Period [s]: {self.integ.vp_tbl[self.integ.vp]}\n") - self.fh.write(f" Update rate interval [s]: {self.integ.uri*0.1}\n") self.fh.write(f" Time Correlation Integrity/Continuity Flag: {self.integ.fc}\n") b = self.integ.bias @@ -2042,16 +2012,15 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write("\n") elif self.subtype == sRTCM.INTEG_PRI_AREA: # MT2007 - self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") - self.fh.write(f" Provider ID: {self.integ.pid}\n") - self.fh.write(f" Area ID: {self.integ.aid}\n") - self.fh.write(f" IOD area: {self.integ.iod_sa}\n") - self.fh.write(f" Validity Period [s]: {self.integ.vp_tbl[self.integ.vp]}\n") - self.fh.write(f" Update rate interval [s]: {self.integ.uri*0.1}\n") + self.out_log_integ_head() + self.fh.write(f" Area ID: {self.integ.aid}\n") + self.fh.write(f" IOD area: {self.integ.iod_sa}\n") self.fh.write(f" Service Area Type: {self.integ.atype_t[self.integ.atype]}\n") + self.fh.write(f" Continuation Flag: {self.integ.cf}\n") self.fh.write(f" Seq: {self.integ.seq}\n") + if self.integ.atype == 1: # lat/lon self.fh.write(f" Number of Area Point: {self.integ.npnt}\n") self.fh.write(" Area Point (lat, lon):\n") @@ -2063,12 +2032,10 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write(f" {pos[0]:10.4f} {pos[1]:10.4f} {pos[2]:10.0f}\n") elif self.subtype == sRTCM.INTEG_EXT_AREA: # MT2008 - self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") - self.fh.write(f" Provider ID: {self.integ.pid}\n") + self.out_log_integ_head() + self.fh.write(f" Area ID: {self.integ.aid}\n") self.fh.write(f" IOD area: {self.integ.iod_sa}\n") - self.fh.write(f" Validity Period [s]: {self.integ.vp_tbl[self.integ.vp]}\n") - self.fh.write(f" Update rate interval [s]: {self.integ.uri*0.1}\n") self.fh.write(f" Service Area Type: {self.integ.atype_t[self.integ.atype]}\n") self.fh.write(f" Continuation Flag: {self.integ.cf}\n") @@ -2082,20 +2049,19 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write(f" Area Point (lat/lon): {self.integ.pos}\n") elif self.subtype == sRTCM.INTEG_QUALITY: # MT2051 - self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") - self.fh.write(f" Provider Id: {self.integ.pid}\n") - self.fh.write(f" Validity Period [s]: {self.integ.vp_tbl[self.integ.vp]}\n") - self.fh.write(f" Update rate interval [s]: {self.integ.uri*0.1}\n") + self.out_log_integ_head() + self.fh.write(f" Network ID: {self.integ.nid}\n") self.fh.write(f" Quality Indicator [m]: {self.integ.qi}\n") elif self.subtype == sRTCM.INTEG_CNR: # MT2091 - self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") + self.fh.write(f" TOW (s): {self.integ.tow:9.3f}\n") self.fh.write(f" Provider ID: {self.integ.pid}\n") - self.fh.write(f" Area ID: {self.integ.aid}\n") - self.fh.write(f" Update rate interval [s]: {self.integ.uri*0.1}\n") + self.fh.write(f" Update rate interval [s]: {self.integ.udi*0.1}\n") + self.fh.write(f" Area ID: {self.integ.aid}\n") + self.fh.write(" CNR [dB-Hz]:\n") for sys, s in self.integ.cnr.items(): self.fh.write(f" {sys2char(sys)}:") @@ -2125,7 +2091,8 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write("\n") elif self.subtype == sRTCM.INTEG_VMAP: # MT2071 - self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") + self.fh.write(f" TOW (s): {self.integ.tow:9.3f}\n") + self.fh.write(f" Number of Area Points: {self.integ.narea:}\n") self.fh.write(f" Continuation Flag: {self.integ.cf}\n") self.fh.write(f" Seq: {self.integ.seq}\n") @@ -2142,11 +2109,13 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): elif self.subtype == sRTCM.INTEG_MMAP: # 2072 model = self.integ.mm_model_t[self.integ.mm_id] - self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") + self.fh.write(f" TOW (s): {self.integ.tow:9.3f}\n") + self.fh.write(f" Number of Area Points: {self.integ.narea:}\n") self.fh.write(f" Multipath Model ID: {model}\n") self.fh.write(f" Continuation Flag: {self.integ.cf}\n") self.fh.write(f" Seq: {self.integ.seq}\n") + self.fh.write(" Boundary Points (lat, long, alt), np:\n") for k in range(self.integ.narea): self.fh.write(" {:2d}\t{:12.9f}\t{:12.9f}\t{:4.0f}\t{:2d}\n".format @@ -2178,6 +2147,26 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.flush() + if self.subtype in (sRTCM.INTEG_SSR, sRTCM.INTEG_SSR_IONO, + sRTCM.INTEG_SSR_TROP): + self.out_log_integ_head() + + self.fh.write(" {:20s}{:04x}\n".format("Constellation Mask:", + self.integ.mask_sys)) + self.fh.write(" IOD GNSS Mask: ") + for sys in self.integ.iod_sys.keys(): + self.fh.write(" {:8s}: {:1d}".format( + sys2str(sys), self.integ.iod_sys[sys])) + self.fh.write("\n") + + for sys in self.integ.flag.keys(): + self.fh.write(" NIntegrity Flag: ") + for sat in self.integ.flag[sys]: + self.fh.write(" {:3s}:{:2d}".format( + sat2id(sat), self.integ.flag[sys][sat])) + self.fh.write("\n") + self.fh.flush() + if eph is not None: sys, prn = sat2prn(eph.sat) self.fh.write(" {:20s}{:6d} ({:s})\n".format("PRN:", prn, @@ -3287,11 +3276,11 @@ def decode_integrity_min(self, msg, i): # constellation integirity status DFi029 # constellation monitoring status DFi030 - c_integ, c_status, vp, uri = bs.unpack_from('u16u16u4u16', msg, i) + c_integ, c_status, vp, udi = bs.unpack_from('u16u16u4u16', msg, i) i += 52 self.integ.vp = vp # Validity Period DFi065 (0-15) - self.integ.uri = uri # update rate interval DFi067 (0.1) + self.integ.udi = udi # update rate interval DFi067 (0.1) sys_t, nsys = self.decode_mask(mask_sys, 16, ofst=0) @@ -3388,12 +3377,12 @@ def decode_integrity_ext(self, msg, i): # GNSS Constellation Mask DFi013 # Validity Period DFi065 (0-15) # update rate interval DFi067 (0.1) - mask_sys, vp, uri = bs.unpack_from('u16u4u16', msg, i) + mask_sys, vp, udi = bs.unpack_from('u16u4u16', msg, i) i += 36 sys_t, nsys = self.decode_mask(mask_sys, 16, ofst=0) self.integ.vp = vp # Validity Period DFi065 (0-15) - self.integ.uri = uri # update rate interval DFi067 (0.1) + self.integ.udi = udi # update rate interval DFi067 (0.1) # Paug Bundling flag DFi066 f_paug = bs.unpack_from('b1', msg, i)[0] @@ -3529,11 +3518,11 @@ def decode_integrity_ext_sis_local(self, msg, i): # Validity Period DFi065 (0-15) # update rate interval DFi067 (0.1) # Time correlation integrity/continuity flag DFi137 - mask_sys, vp, uri, fc = bs.unpack_from('u16u4u16b1', msg, i) + mask_sys, vp, udi, fc = bs.unpack_from('u16u4u16b1', msg, i) i += 37 self.integ.vp = vp - self.integ.uri = uri + self.integ.udi = udi self.integ.fc = fc # Bounding inter-constellation bias error parameter DFi132 @@ -3545,7 +3534,7 @@ def decode_integrity_ext_sis_local(self, msg, i): sys_t, nsys = self.decode_mask(mask_sys, 16, ofst=0) self.integ.vp = vp # Validity Period DFi065 (0-15) - self.integ.uri = uri # update rate interval DFi067 (0.1) + self.integ.udi = udi # update rate interval DFi067 (0.1) # constellation-specific part for sys_ in sys_t: @@ -3634,10 +3623,10 @@ def decode_integrity_pri_service_area(self, msg, i): # service area rtpe DFi056 # service area parameter continuation flag DFi041 # multiple message sequence number - uri, atype, cf, seq = bs.unpack_from('u16u2u1u5', msg, i) + udi, atype, cf, seq = bs.unpack_from('u16u2u1u5', msg, i) i += 24 - self.integ.uri = uri + self.integ.udi = udi self.integ.atype = atype self.integ.cf = cf self.integ.seq = seq @@ -3684,7 +3673,7 @@ def decode_integrity_ext_service_area(self, msg, i): # service area rtpe DFi056 # service area parameter continuation flag DFi041 # multiple message sequence number DFi079 - uri, atype, cf, seq = bs.unpack_from('u16u2u1u5', msg, i) + udi, atype, cf, seq = bs.unpack_from('u16u2u1u5', msg, i) i += 24 # Augmentation IR Degradation Factor DFi140 @@ -3701,7 +3690,7 @@ def decode_integrity_ext_service_area(self, msg, i): self.integ.pid = pid self.integ.aid = area self.integ.vp = vp - self.integ.uri = uri + self.integ.udi = udi self.integ.atype = atype self.integ.cf = cf self.integ.seq = seq @@ -3742,22 +3731,22 @@ def decode_integrity_quality(self, msg, i): # GPS Epoch Time (TOW) DFi008 # augmentation service provider id DFi027 # Validity Period DFi065 (0-15) - - tow, pid, vp = bs.unpack_from('u30u12u4', msg, i) - i += 46 - # update rate interval DFi067 (0.1) + + tow, pid, vp, udi = bs.unpack_from('u30u12u4u16', msg, i) + i += 62 + # network id DFi071 # quality indicator mask DFi061 - uri, nid, mask_q = bs.unpack_from('u16u8u8', msg, i) - i += 32 + nid, mask_q = bs.unpack_from('u8u8', msg, i) + i += 16 self.integ.tow = tow*1e-3 self.tow = self.integ.tow self.time = gpst2time(self.week, self.tow) self.integ.pid = pid self.integ.vp = vp - self.integ.uri = uri + self.integ.udi = udi self.integ.nid = nid idx_q, np_ = self.decode_mask(mask_q, 8, ofst=0) @@ -3775,14 +3764,14 @@ def decode_integrity_cnr_acg(self, msg, i): # update rate interval DFi067 (0.1) # service area id DFi075 # constellation mask DFi013 - pid, tow, uri, area, mask_c = bs.unpack_from('u12u30u16u10u16', msg, i) + pid, tow, udi, area, mask_c = bs.unpack_from('u12u30u16u10u16', msg, i) i += 84 self.integ.tow = tow*1e-3 self.tow = self.integ.tow self.time = gpst2time(self.week, self.tow) self.integ.pid = pid - self.integ.uri = uri + self.integ.udi = udi self.integ.aid = area # constellation specific part @@ -3967,12 +3956,12 @@ def decode_integrity_ssr(self, msg, i): self.time = gpst2time(self.week, self.tow) self.integ.mask_sys = mask_sys - vp, uri = bs.unpack_from('u4u16', msg, i) + vp, udi = bs.unpack_from('u4u16', msg, i) i += 20 # Validity Period DFi065 (0-15) self.integ.vp = self.integ.vp_tbl[vp] # update rate interval DFi067 (0.1) - self.integ.uri = uri*0.1 + self.integ.udi = udi*0.1 sys_t, nsys = self.decode_mask(mask_sys, 16, ofst=0) iod_sys = {} @@ -4443,7 +4432,7 @@ def encode_integrity_ssr(self, msg, i): # validity period DFi065 # update rate interval DFi067 vp = self.integ.vp_r_tbl[self.integ.vp] - bs.pack_into('u4u16', msg, i, vp, self.integ.uri*10) + bs.pack_into('u4u16', msg, i, vp, self.integ.udi*10) i += 20 for sys in sys_t: From 263f76938824900eca132d8a1f099fb4bdde839e Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sat, 14 Feb 2026 22:51:00 +0900 Subject: [PATCH 42/48] - rtcm ssr log output clean-up. --- src/cssrlib/cssrlib.py | 2 +- src/cssrlib/gnss.py | 2 +- src/cssrlib/rtcm.py | 60 +++++++++++++++++++++++++++++++++++++++--- 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/src/cssrlib/cssrlib.py b/src/cssrlib/cssrlib.py index 2d8fa33..8a9f9d1 100644 --- a/src/cssrlib/cssrlib.py +++ b/src/cssrlib/cssrlib.py @@ -60,7 +60,7 @@ class sCSSR(IntEnum): ATMOS = 12 AUTH = 13 VTEC = 16 - + HCLOCK = 19 class sCType(IntEnum): """ class to define correction message types """ diff --git a/src/cssrlib/gnss.py b/src/cssrlib/gnss.py index 6ed9428..f5bd40f 100644 --- a/src/cssrlib/gnss.py +++ b/src/cssrlib/gnss.py @@ -1308,7 +1308,7 @@ def sys2str(sys): uGNSS.QZS: 'QZSS', uGNSS.SBS: 'SBAS', uGNSS.IRN: 'IRNSS'} if sys not in gnss_tbl: - return "???" + return "" else: return gnss_tbl[sys] diff --git a/src/cssrlib/rtcm.py b/src/cssrlib/rtcm.py index 351619f..76c5f68 100644 --- a/src/cssrlib/rtcm.py +++ b/src/cssrlib/rtcm.py @@ -327,6 +327,25 @@ class rtcmUtil: 1060: (uGNSS.GPS, sCType.OC), 1061: (uGNSS.GPS, sCType.URA), 1062: (uGNSS.GPS, sCType.HCLOCK), + 1063: (uGNSS.GLO, sCType.ORBIT), + 1064: (uGNSS.GLO, sCType.CLOCK), + 1065: (uGNSS.GLO, sCType.CBIAS), + 1066: (uGNSS.GLO, sCType.OC), + 1067: (uGNSS.GLO, sCType.URA), + 1068: (uGNSS.GLO, sCType.HCLOCK), + 1240: (uGNSS.GAL, sCType.ORBIT), + 1241: (uGNSS.GAL, sCType.CLOCK), + 1242: (uGNSS.GAL, sCType.CBIAS), + 1243: (uGNSS.GAL, sCType.OC), + 1244: (uGNSS.GAL, sCType.URA), + 1245: (uGNSS.GAL, sCType.HCLOCK), + 1264: (uGNSS.NONE, sCType.VTEC), + 1265: (uGNSS.GPS, sCType.PBIAS), + 1266: (uGNSS.GLO, sCType.PBIAS), + 1267: (uGNSS.GAL, sCType.PBIAS), + 1268: (uGNSS.QZS, sCType.PBIAS), + 1269: (uGNSS.SBS, sCType.PBIAS), + 1270: (uGNSS.BDS, sCType.PBIAS), 41: (uGNSS.GLO, sCType.ORBIT), 42: (uGNSS.GLO, sCType.CLOCK), 43: (uGNSS.GLO, sCType.CBIAS), @@ -376,6 +395,33 @@ class rtcmUtil: 100: (uGNSS.QZS, sCType.STEC), } + ssrt_t = { + sCType.ORBIT: "ORBIT", + sCType.CLOCK: "CLOCK", + sCType.OC: "ORBIT/CLOCK", + sCType.URA: "URA", + sCType.CBIAS: "CODE-BIAS", + sCType.PBIAS: "PHASE-BIAS", + sCType.META: "META", + sCType.HCLOCK: "HIGH-RATE CLOCK", + sCType.PBIAS_EX: "PHASE-BIAS EXTENDED", + sCType.SATANT: "SATELLITE ANTENNA", + sCType.TROP: "TROP", + sCType.STEC: "STEC", + sCType.VTEC: "VTEC", + } + + igsssrt_t = { + sCSSR.VTEC: "VTEC", + sCSSR.ORBIT: "ORBIT", + sCSSR.CLOCK: "CLOCK", + sCSSR.COMBINED: "ORBIT/CLOCK", + sCSSR.HCLOCK: "HIGH-RATE CLOCK", + sCSSR.CBIAS: "CODE-BIAS", + sCSSR.PBIAS: "PHASE-BIAS", + sCSSR.URA: "URA", + } + def mt2sct(self, s): """ SSR Message type to SCType """ return self.ssrtype_t[s] @@ -1409,7 +1455,7 @@ def decode_igsssr(self, msg, i=0): self.subtype = sCSSR.COMBINED i = self.decode_cssr_comb(msg, i) elif st == 4: - self.subtype = sCSSR.CLOCK + self.subtype = sCSSR.HCLOCK i = self.decode_cssr_hclk(msg, i) elif st == 5: self.subtype = sCSSR.CBIAS @@ -1549,7 +1595,7 @@ def out_log_ssr_ura(self, sys): def out_log_integ_head(self): vp = self.integ.vp_tbl[self.integ.vp] - self.fh.write(f" TOF (s): {self.integ.tow:9.3f}\n") + self.fh.write(f" TOW (s): {self.integ.tow:9.3f}\n") self.fh.write(f" Provider Id: {self.integ.pid}\n") self.fh.write(f" Validity Period [s]: {vp:6d}\n") self.fh.write(f" Update rate interval [s]: {self.integ.udi*0.1}\n") @@ -1557,12 +1603,18 @@ def out_log_integ_head(self): def out_log(self, obs=None, eph=None, geph=None, seph=None): """ output ssr message to log file """ sys = -1 - if self.is_ssrtype(self.msgtype, True): - sys = self.get_ssr_sys(self.msgtype) inet = self.inet self.fh.write("{:4d}\t{:s}\n".format(self.msgtype, time2str(self.time))) + if self.is_ssrtype(self.msgtype, True): + sys = self.get_ssr_sys(self.msgtype) + sys, type_ = self.ssrtype_t[self.msgtype] + self.fh.write(f" Message Type: RTCM-SSR {self.ssrt_t[type_]} {sys2str(sys)}\n") + + if self.msgtype == 4076: # IGS-SSR + self.fh.write(f" Message Type: IGS-SSR {self.igsssrt_t[self.subtype]} {sys2str(self.sysref)}\n") + if sys > 0 and self.subtype not in [sRTCM.SSR_META, sRTCM.SSR_GRID]: j = self.sc_t[self.subtype] self.fh.write(f" Update Interval: {self.udint_t[self.udi[j]]}[s]") From dd799827689af622742f0d4c5a7f42b73df7e062 Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sun, 15 Feb 2026 19:12:18 +0900 Subject: [PATCH 43/48] - fixed satant offset reference error for QZS. --- src/cssrlib/peph.py | 6 +++++- src/cssrlib/pppssr.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/cssrlib/peph.py b/src/cssrlib/peph.py index b9ee22d..b588583 100644 --- a/src/cssrlib/peph.py +++ b/src/cssrlib/peph.py @@ -825,7 +825,11 @@ def antModelTx(nav, e, sigs, sat, time, rs, sig0=None): # Satellite PCO in local antenna frame # - off0 += fac0_*ant.off[sig] + if sig in ant.off: + off0 += fac0_*ant.off[sig] + else: + print(f"sig={sig} not in antenna database.") + off0 = np.ones((1,3))*np.nan # Interpolate PCV and map PCO on line-of-sight vector # diff --git a/src/cssrlib/pppssr.py b/src/cssrlib/pppssr.py index e434464..64aed08 100644 --- a/src/cssrlib/pppssr.py +++ b/src/cssrlib/pppssr.py @@ -679,7 +679,7 @@ def zdres(self, obs, cs, bsx, rs, vs, dts, rr, rtype=1): elif sys == uGNSS.GAL: sig0 = (rSigRnx("EC1C"), rSigRnx("EC5Q")) elif sys == uGNSS.QZS: - sig0 = (rSigRnx("JC1C"), rSigRnx("JC2S")) + sig0 = (rSigRnx("JC1C"), rSigRnx("JC5Q")) elif cs.cssrmode == sc.GAL_HAS_SIS: From 55ea16c2ab77e4f70e94ddeafb2997fc362454f3 Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Mon, 23 Feb 2026 10:31:14 +0900 Subject: [PATCH 44/48] - SC134 decoder updated. --- src/cssrlib/rtcm.py | 405 +++++++++++++++----------------------------- 1 file changed, 137 insertions(+), 268 deletions(-) diff --git a/src/cssrlib/rtcm.py b/src/cssrlib/rtcm.py index 76c5f68..03d61df 100644 --- a/src/cssrlib/rtcm.py +++ b/src/cssrlib/rtcm.py @@ -148,8 +148,8 @@ class Integrity(): # GMM component stdev Table 8-41 sig_k_t = [0.0, 0.04, 0.07, 0.10, 0.20, 0.25, 0.30, 0.40, 0.50, 0.70, 1, 2, 5, 7, 10, -1] - # Extended Area Time Parameter Degradation Factor DFi138 - ext_df_t = [1.0, 1.05, 1.1, 1.2, 1.4, 1.7, 2.0, -1] + # Time Parameter Degradation Factor DFi138 + df_t = [0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 1, -1] # Extended Area Spatial Parameter Degradation Factor DFi139 exs_df_t = [1.0, 1.1, 1.3, 1.6, 2.0, 3.0, 5.0, -1] # Augmentation IR Degradation Factor DFi140 @@ -1597,8 +1597,20 @@ def out_log_integ_head(self): vp = self.integ.vp_tbl[self.integ.vp] self.fh.write(f" TOW (s): {self.integ.tow:9.3f}\n") self.fh.write(f" Provider Id: {self.integ.pid}\n") - self.fh.write(f" Validity Period [s]: {vp:6d}\n") - self.fh.write(f" Update rate interval [s]: {self.integ.udi*0.1}\n") + self.fh.write(f" Validity Period [s]: {vp:4.1f}\n") + self.fh.write(f" Update rate interval [s]: {self.integ.udi:4.1f}\n") + self.fh.write(f" Service Area ID: {self.integ.aid:d}\n") + + def out_log_area_point(self): + if self.integ.atype == 1: # lat/lon + self.fh.write(f" Number of Area Point: {self.integ.npnt}\n") + self.fh.write(" Area Point (lat, lon):\n") + for pos in self.integ.pos_v: + self.fh.write(f" {pos[0]:15.9f} {pos[1]:15.9f}\n") + else: # lat/lon/radius + pos = self.integ.pos_v + self.fh.write(" Area Point (lat, lon, radius): ") + self.fh.write(f" {pos[0]:15.9f} {pos[1]:15.9f} {pos[2]:10.0f}\n") def out_log(self, obs=None, eph=None, geph=None, seph=None): """ output ssr message to log file """ @@ -1898,7 +1910,6 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): elif self.subtype == sRTCM.INTEG_EXT: # MT2005 self.out_log_integ_head() - self.fh.write(f" Area ID: {self.integ.aid}\n") self.fh.write(f" Integrity Level: {self.integ.ilvl}\n") self.fh.write(f" TTT: {self.integ.ttt}\n") self.fh.write(f" Solution Type: {self.integ.decode_sol_t(self.integ.stype)}\n") @@ -1981,7 +1992,6 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.out_log_integ_head() self.fh.write(f" IOD param: {self.integ.iod_ip}\n") - self.fh.write(f" Service Area ID: {self.integ.aid}\n") self.fh.write(f" Time Correlation Integrity/Continuity Flag: {self.integ.fc}\n") b = self.integ.bias @@ -2065,41 +2075,33 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): elif self.subtype == sRTCM.INTEG_PRI_AREA: # MT2007 self.out_log_integ_head() - - self.fh.write(f" Area ID: {self.integ.aid}\n") + self.fh.write(f" IOD area: {self.integ.iod_sa}\n") self.fh.write(f" Service Area Type: {self.integ.atype_t[self.integ.atype]}\n") - + self.fh.write(f" Continuation Flag: {self.integ.cf}\n") self.fh.write(f" Seq: {self.integ.seq}\n") + + self.fh.write(f" Time Parameter Degradation Factor: {self.integ.df_t[self.integ.f_t]}\n") - if self.integ.atype == 1: # lat/lon - self.fh.write(f" Number of Area Point: {self.integ.npnt}\n") - self.fh.write(" Area Point (lat, lon):\n") - for pos in self.integ.pos_v: - self.fh.write(f" {pos[0]:10.4f} {pos[1]:10.4f}\n") - else: # lat/lon/radius - pos = self.integ.pos_v - self.fh.write(" Area Point (lat, lon, radius): ") - self.fh.write(f" {pos[0]:10.4f} {pos[1]:10.4f} {pos[2]:10.0f}\n") + self.out_log_area_point() elif self.subtype == sRTCM.INTEG_EXT_AREA: # MT2008 self.out_log_integ_head() - self.fh.write(f" Area ID: {self.integ.aid}\n") self.fh.write(f" IOD area: {self.integ.iod_sa}\n") self.fh.write(f" Service Area Type: {self.integ.atype_t[self.integ.atype]}\n") - + self.fh.write(f" Continuation Flag: {self.integ.cf}\n") self.fh.write(f" Seq: {self.integ.seq}\n") self.fh.write(f" Augmentation IR Degradation Factor: {self.integ.f_ir_t[self.integ.f_ir]}\n") self.fh.write(f" Augmentation TTD Degradation Factor: {self.integ.f_ttd_t[self.integ.f_ttd]}\n") - self.fh.write(f" Extended Area Time Parameter Degradation Factor: {self.integ.ext_df_t[self.integ.f_ext]}\n") + self.fh.write(f" Time Parameter Degradation Factor: {self.integ.df_t[self.integ.f_t]}\n") self.fh.write(f" Extended Area Spatial Parameter Degradation Factor: {self.integ.exs_df_t[self.integ.f_exs]}\n") - self.fh.write(f" Area Point (lat/lon): {self.integ.pos}\n") - + self.out_log_area_point() + elif self.subtype == sRTCM.INTEG_QUALITY: # MT2051 self.out_log_integ_head() @@ -2107,12 +2109,7 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write(f" Quality Indicator [m]: {self.integ.qi}\n") elif self.subtype == sRTCM.INTEG_CNR: # MT2091 - - self.fh.write(f" TOW (s): {self.integ.tow:9.3f}\n") - self.fh.write(f" Provider ID: {self.integ.pid}\n") - self.fh.write(f" Update rate interval [s]: {self.integ.udi*0.1}\n") - - self.fh.write(f" Area ID: {self.integ.aid}\n") + self.out_log_integ_head() self.fh.write(" CNR [dB-Hz]:\n") for sys, s in self.integ.cnr.items(): @@ -2143,11 +2140,12 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write("\n") elif self.subtype == sRTCM.INTEG_VMAP: # MT2071 - self.fh.write(f" TOW (s): {self.integ.tow:9.3f}\n") + self.out_log_integ_head() self.fh.write(f" Number of Area Points: {self.integ.narea:}\n") self.fh.write(f" Continuation Flag: {self.integ.cf}\n") self.fh.write(f" Seq: {self.integ.seq}\n") + self.fh.write(" Boundary Points (lat, long, alt, naz):\n") for k in range(self.integ.narea): self.fh.write(" {:2d}\t{:12.9f}\t{:12.9f}\t{:4.0f}\t{:2d}\n".format @@ -2159,7 +2157,9 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): self.fh.write(" {:3.0f}\t{:3.0f}\n".format (self.integ.azel[k][j][0], self.integ.azel[k][j][1])) - elif self.subtype == sRTCM.INTEG_MMAP: # 2072 + elif self.subtype == sRTCM.INTEG_MMAP: # MT2072 + self.out_log_integ_head() + model = self.integ.mm_model_t[self.integ.mm_id] self.fh.write(f" TOW (s): {self.integ.tow:9.3f}\n") @@ -3314,25 +3314,36 @@ def decode_ssr_iono(self, msg, i, inet=0): return i + def decode_integ_head(self, msg, i): + """ decode common headder of SC-134 messages """ + tow, pid, vp, udi = bs.unpack_from('u30u12u4u16', msg, i) + i += 62 + + self.integ.tow = tow*1e-3 + self.integ.pid = pid # Augmentation Service Provider Id + self.integ.vp = self.integ.vp_tbl[vp] # Validity Period DFi065 (0-15) + self.integ.udi = udi*0.1 # update rate interval DFi067 (0.1) + + self.tow = self.integ.tow + self.time = gpst2time(self.week, self.tow) + + if self.msgtype != 2000: + aid = bs.unpack_from('u10', msg, i)[0] + i += 10 + self.integ.aid = aid # service area id DFi075 + + return i + def decode_integrity_min(self, msg, i): """ RTCM SC-134 minimum integrity message (MT3,2000) """ - # augmentation service provider id DFi027 - # GNSS Epoch Time (TOW) DFi008 + i = self.decode_integ_head(msg, i) + # Constellation Mask DFi013 - pid, tow, mask_sys = bs.unpack_from('u12u30u16', msg, i) - i += 58 - - self.integ.pid = pid - self.integ.tow = tow*1e-3 - # constellation integirity status DFi029 # constellation monitoring status DFi030 - c_integ, c_status, vp, udi = bs.unpack_from('u16u16u4u16', msg, i) - i += 52 - - self.integ.vp = vp # Validity Period DFi065 (0-15) - self.integ.udi = udi # update rate interval DFi067 (0.1) + mask_sys, c_integ, c_status = bs.unpack_from('u16u16u16', msg, i) + i += 48 sys_t, nsys = self.decode_mask(mask_sys, 16, ofst=0) @@ -3374,9 +3385,7 @@ def decode_integrity_min(self, msg, i): ists[sys] = {} msts[sys] = {} for svid in svid_t: - ofst = 192 if sys == uGNSS.QZS else 0 - prn = svid+ofst - sat = prn2sat(sys, prn) + sat = self.svid2sat(sys, svid) # GNSS frequency mask DFi022 f, _ = self.decode_mask(bs.unpack_from('u8', msg, i)[0], 8, 0) @@ -3407,34 +3416,23 @@ def decode_integrity_ext(self, msg, i): """ RTCM SC-134 extended integrity message service levels and overbounding parameters (MT4,2005) """ - # GPS Epoch Time (TOW) DFi008 - # augmentation service provider id DFi027 - # service area id DFi075 + i = self.decode_integ_head(msg, i) + # Augmentation Integrity Level DFi026 # TTTcomm DFi050 # Service Provider Solution Type DFi004 - tow, pid, area, ilvl, ttt, stype = bs.unpack_from('u30u12u10u8u7u15', - msg, i) + ilvl, ttt, stype = bs.unpack_from('u8u7u15', msg, i) i += 82 - self.integ.tow = tow*1e-3 - self.tow = self.integ.tow - self.time = gpst2time(self.week, self.tow) - self.integ.pid = pid - self.integ.aid = area self.integ.ilvl = ilvl self.integ.ttt = (ttt+1)*0.1 self.integ.stype = stype # GNSS Constellation Mask DFi013 - # Validity Period DFi065 (0-15) - # update rate interval DFi067 (0.1) - mask_sys, vp, udi = bs.unpack_from('u16u4u16', msg, i) - i += 36 + mask_sys = bs.unpack_from('u16', msg, i)[0] + i += 16 sys_t, nsys = self.decode_mask(mask_sys, 16, ofst=0) - self.integ.vp = vp # Validity Period DFi065 (0-15) - self.integ.udi = udi # update rate interval DFi067 (0.1) # Paug Bundling flag DFi066 f_paug = bs.unpack_from('b1', msg, i)[0] @@ -3517,12 +3515,12 @@ def decode_integrity_ext(self, msg, i): self.integ.b_ob_c[sys] = {} self.integ.b_ob_p[sys] = {} - ofst = 193 if sys == uGNSS.QZS else 1 - prn_t, nsat = self.decode_mask(mask_sat, 64, ofst=ofst) + svid_t, _ = self.decode_mask(mask_sat, 64) # satellite-specific part - for prn in prn_t: - sat = prn2sat(sys, prn) + for svid in svid_t: + sat = self.svid2sat(sys, svid) + # Single Satellite Pseudorange Augmentation Message Fault # Probability DFi046 # Overbounding Standard Deviation of the Pseudorange @@ -3552,29 +3550,15 @@ def decode_integrity_ext_sis_local(self, msg, i): """ RTCM SC-134 extended integrity message signal in space integrity and local error parameters (MT5, 2006) """ - # GNSS Epoch Time (TOW) DFi008 - # integrity parameter IOD DFi006 - # augmentation service provider id DFi027 - # service area id DFi075 - tow, iod_ip, pid, area = bs.unpack_from('u30u6u12u10', msg, i) - i += 58 - - self.integ.tow = tow*1e-3 - self.tow = self.integ.tow - self.time = gpst2time(self.week, self.tow) - self.integ.iod_ip = iod_ip - self.integ.pid = pid - self.integ.aid = area + i = self.decode_integ_head(msg, i) + # integrity parameter IOD DFi006 # GNSS Constellation Mask DFi013 - # Validity Period DFi065 (0-15) - # update rate interval DFi067 (0.1) # Time correlation integrity/continuity flag DFi137 - mask_sys, vp, udi, fc = bs.unpack_from('u16u4u16b1', msg, i) - i += 37 + iod_ip, mask_sys, fc = bs.unpack_from('u6u16u1', msg, i)[0] + i += 23 - self.integ.vp = vp - self.integ.udi = udi + self.integ.iod_ip = iod_ip self.integ.fc = fc # Bounding inter-constellation bias error parameter DFi132 @@ -3585,8 +3569,6 @@ def decode_integrity_ext_sis_local(self, msg, i): self.integ.bias = [bias_ic, bias_if] sys_t, nsys = self.decode_mask(mask_sys, 16, ofst=0) - self.integ.vp = vp # Validity Period DFi065 (0-15) - self.integ.udi = udi # update rate interval DFi067 (0.1) # constellation-specific part for sys_ in sys_t: @@ -3598,9 +3580,7 @@ def decode_integrity_ext_sis_local(self, msg, i): i += 66 self.integ.iod_m[sys] = iod_mask - - ofst = 193 if sys == uGNSS.QZS else 1 - prn_t, nsat = self.decode_mask(mask_sat, 64, ofst=ofst) + svid_t, _ = self.decode_mask(mask_sat, 64) self.integ.sig_pd[sys] = {} self.integ.sig_cd[sys] = {} @@ -3614,8 +3594,8 @@ def decode_integrity_ext_sis_local(self, msg, i): self.integ.sig_trp[sys] = {} self.integ.idx_trp[sys] = {} - for prn in prn_t: - sat = prn2sat(sys, prn) + for svid in svid_t: + sat = self.svid2sat(sys, svid) # Bound on rate of change of PR error stdev DFi120 # Bound on rate of change in phase error stdev DFi121 @@ -3648,123 +3628,50 @@ def decode_integrity_ext_sis_local(self, msg, i): self.integ.idx_trp[sys][sat] = idx_trp return i - def decode_integrity_pri_service_area(self, msg, i): - """ RTCM SC-134 Primary Service Area Parameters (MT6,MT2007) """ - - # GNSS Epoch Time (TOW) DFi008 - # service area iod DFi005 - # augmentation service provider id DFi027 - # service area id DFi075 - # Validity Period DFi065 (0-15) - tow, iod_sa, pid, area, vp = bs.unpack_from( - 'u30u6u12u10u4', msg, i) - i += 62 - - self.integ.iod_sa = iod_sa - self.integ.tow = tow*1e-3 - self.tow = self.integ.tow - self.time = gpst2time(self.week, self.tow) - self.integ.pid = pid - self.integ.aid = area - self.integ.vp = vp - - # augmentation integrity level DFi026 - # GNSS Constellation Mask DFi013 - - # update rate interval DFi067 (0.1) - # service area rtpe DFi056 - # service area parameter continuation flag DFi041 - # multiple message sequence number - udi, atype, cf, seq = bs.unpack_from('u16u2u1u5', msg, i) - i += 24 - - self.integ.udi = udi - self.integ.atype = atype - self.integ.cf = cf - self.integ.seq = seq - - if atype == 1: # Validity Area Parameters defined in Table 10.3-5 - # number of area points DFi201 - npnt = bs.unpack_from('u8', msg, i)[0] - self.integ.npnt = npnt - i += 8 - self.integ.pos_v = np.zeros((npnt, 2)) - for k in range(npnt): - # Area Point - Lat DFi202 - # Area Point - Lon DFi203 - lat_i, lon_i = bs.unpack_from('s34s35', msg, i) - i += 69 - lat = self.sval(lat_i, 34, 0.000000011) - lon = self.sval(lon_i, 35, 0.000000011) - self.integ.pos_v[k, :] = [lat, lon] - - elif atype == 0: # Validity Radius Data defined in Table 10.3-6 - # Area Point - Lat DFi202 - # Area Point - Lon DFi203 - # Validity Radius DFi057 - lat_i, lon_i, r = bs.unpack_from('s34s35u20', msg, i) - i += 89 - lat = self.sval(lat_i, 34, 0.000000011) - lon = self.sval(lon_i, 35, 0.000000011) - self.integ.pos_v = [lat, lon, r] - return i + def decode_integrity_service_area(self, msg, i, ext=False): + """ decode SC-134 Primary/Extended Service Area Parameters (MT2007/2008) """ + + i = self.decode_integ_head(msg, i) + + atype, iod_sa, cf = bs.unpack_from('u2u6u1', msg, i) + i += 9 - def decode_integrity_ext_service_area(self, msg, i): - """ RTCM SC-134 Extended Service Area Parameters (MT7,MT2008) """ + self.integ.atype = atype # service area type DFi056 + self.integ.iod_sa = iod_sa + self.integ.cf = cf # service area parameter continuation flag DFi041 - # GNSS Epoch Time (TOW) DFi008 - # service area iod DFi005 - # augmentation service provider id DFi027 - # service area id DFi075 - # Validity Period DFi065 (0-15) - tow, iod_sa, pid, area, vp = bs.unpack_from( - 'u30u6u12u10u4', msg, i) - i += 62 + if not ext: + # Time Parameter Degradation Factor (DFi138) + self.integ.f_t = bs.unpack_from('u3', msg, i)[0] + i += 3 - # update rate interval DFi067 (0.1) - # service area rtpe DFi056 - # service area parameter continuation flag DFi041 # multiple message sequence number DFi079 - udi, atype, cf, seq = bs.unpack_from('u16u2u1u5', msg, i) - i += 24 - - # Augmentation IR Degradation Factor DFi140 - # Augmentation TTD Degradation Factor DFi141 - # Extended Area Time Parameter Degradation Factor DFi138 - # Extended Area Spatial Parameter Degradation Factor DFi139 - f_ir, f_ttd, f_ext, f_exs = bs.unpack_from('u2u2u3u3', msg, i) - i += 10 - - self.integ.tow = tow*1e-3 - self.tow = self.integ.tow - self.time = gpst2time(self.week, self.tow) - self.integ.iod_sa = iod_sa - self.integ.pid = pid - self.integ.aid = area - self.integ.vp = vp - self.integ.udi = udi - self.integ.atype = atype - self.integ.cf = cf - self.integ.seq = seq - - self.integ.f_ir = f_ir - self.integ.f_ttd = f_ttd - self.integ.f_ext = f_ext - self.integ.f_exs = f_exs + self.integ.seq = bs.unpack_from('u5', msg, i)[0] + i += 5 + + if ext: # Extended Service Area + f_ir, f_ttd, f_t, f_exs = bs.unpack_from('u2u2u3u3', msg, i) + i += 10 + + self.integ.f_ir = f_ir # Augmentation IR Degradation Factor DFi140 + self.integ.f_ttd = f_ttd # Augmentation TTD Degradation Factor DFi141 + self.integ.f_t = f_t # Time Parameter Degradation Factor DFi138 + self.integ.f_exs = f_exs # Extended Area Spatial Parameter Degradation Factor DFi139 if atype == 1: # Validity Area Parameters # number of area points DFi201 - narea = bs.unpack_from('u8', msg, i)[0] + npnt = bs.unpack_from('u8', msg, i)[0] i += 8 - self.integ.pos = np.zeros((narea, 3)) - for k in range(narea): + self.integ.npnt = npnt + self.integ.pos_v = np.zeros((npnt, 3)) + for k in range(npnt): # Area Point - Lat DFi202 # Area Point - Lon DFi203 lat_i, lon_i = bs.unpack_from('s34s35', msg, i) i += 69 - lat = self.sval(lat_i, 34, 0.000000011) - lon = self.sval(lon_i, 35, 0.000000011) - self.integ.pos[k, :] = [lat, lon, 0] + lat = self.sval(lat_i, 34, 1.1e-8) + lon = self.sval(lon_i, 35, 1.1e-8) + self.integ.pos_v[k, :] = [lat, lon, 0] elif atype == 0: # Validity Radius Data # Area Point - Lat DFi202 @@ -3772,33 +3679,21 @@ def decode_integrity_ext_service_area(self, msg, i): # Validity Radius DF057 lat_i, lon_i, r = bs.unpack_from('s34s35u20', msg, i) i += 89 - lat = self.sval(lat_i, 34, 0.000000011) - lon = self.sval(lon_i, 35, 0.000000011) - self.integ.pos = [lat, lon, r] + lat = self.sval(lat_i, 34, 1.1e-8) + lon = self.sval(lon_i, 35, 1.1e-8) + self.integ.pos_v = [lat, lon, r] return i def decode_integrity_quality(self, msg, i): """ RTCM SC-134 Message Quality Indicator (MT7, 2051) """ - # GPS Epoch Time (TOW) DFi008 - # augmentation service provider id DFi027 - # Validity Period DFi065 (0-15) - # update rate interval DFi067 (0.1) - - tow, pid, vp, udi = bs.unpack_from('u30u12u4u16', msg, i) - i += 62 + i = self.decode_integ_head(msg, i) # network id DFi071 # quality indicator mask DFi061 nid, mask_q = bs.unpack_from('u8u8', msg, i) i += 16 - self.integ.tow = tow*1e-3 - self.tow = self.integ.tow - self.time = gpst2time(self.week, self.tow) - self.integ.pid = pid - self.integ.vp = vp - self.integ.udi = udi self.integ.nid = nid idx_q, np_ = self.decode_mask(mask_q, 8, ofst=0) @@ -3811,20 +3706,11 @@ def decode_integrity_quality(self, msg, i): def decode_integrity_cnr_acg(self, msg, i): """ RTCM SC-134 CNR/ACG SIS Monitoring Message (MT8, 2091) """ - # augmentation service provider id DFi027 - # GPS Epoch Time (TOW) DFi008 - # update rate interval DFi067 (0.1) - # service area id DFi075 - # constellation mask DFi013 - pid, tow, udi, area, mask_c = bs.unpack_from('u12u30u16u10u16', msg, i) - i += 84 + i = self.decode_integ_head(msg, i) - self.integ.tow = tow*1e-3 - self.tow = self.integ.tow - self.time = gpst2time(self.week, self.tow) - self.integ.pid = pid - self.integ.udi = udi - self.integ.aid = area + # constellation mask DFi013 + mask_c = bs.unpack_from('u16', msg, i)[0] + i += 16 # constellation specific part sys_t, nsys = self.decode_mask(mask_c, 16, ofst=0) @@ -3839,11 +3725,10 @@ def decode_integrity_cnr_acg(self, msg, i): mask_s, iod_sm = bs.unpack_from('u64u2', msg, i) i += 66 - ofst = 193 if sys == uGNSS.QZS else 1 - prn_t, nsat = self.decode_mask(mask_s, 64, ofst=ofst) + svid_t, _ = self.decode_mask(mask_s, 64) - for prn in prn_t: - sat = prn2sat(sys, prn) + for svid in svid_t: + sat = self.svid2sat(sys, svid) self.integ.cnr[sys][sat] = {} self.integ.agc_t[sys][sat] = {} @@ -3866,19 +3751,18 @@ def decode_integrity_cnr_acg(self, msg, i): def decode_integrity_vmap(self, msg, i): """ RTCM SC-134 Satellite Visibility Map Message (MT9, 2071) """ - # GNSS Epoch Time (TOW) DFi008 + i = self.decode_integ_head(msg, i) + # number of area points DFi201 # Message Continuation Flag DFi021 # Multiple Message Sequence Number DFi079 - tow, narea, cf, seq = bs.unpack_from('u30u8u1u5', msg, i) - i += 44 + narea, cf, seq = bs.unpack_from('u8u1u5', msg, i) + i += 14 - self.integ.tow = tow*1e-3 - self.tow = self.integ.tow - self.time = gpst2time(self.week, self.tow) self.integ.narea = narea self.integ.cf = cf self.integ.seq = seq + self.integ.pos = np.zeros((narea, 3)) self.integ.naz = np.zeros(narea, dtype=int) self.integ.azel = {} @@ -3912,15 +3796,14 @@ def decode_integrity_vmap(self, msg, i): def decode_integrity_mmap(self, msg, i): """ RTCM SC-134 Multipath Map Message (MT10, 2072) """ - # GNSS Epoch Time (TOW) DFi008 + i = self.decode_integ_head(msg, i) + # number of area points DFi201 # multipath model ID DFi209: 0:GMM,1:MBM,2:JM - tow, narea, mm_id, self.integ.cf, self.integ.seq = bs.unpack_from( - 'u30u8u3u1u5', msg, i) - i += 47 - self.integ.tow = tow*1e-3 - self.tow = self.integ.tow - self.time = gpst2time(self.week, self.tow) + narea, mm_id, self.integ.cf, self.integ.seq = bs.unpack_from( + 'u8u3u1u5', msg, i) + i += 17 + self.integ.narea = narea self.integ.pos = np.zeros((narea, 3)) self.integ.mm_id = mm_id @@ -3991,29 +3874,17 @@ def decode_integrity_mmap(self, msg, i): def decode_integrity_ssr(self, msg, i): """ RTCM SC-134 SSR integrity message (MT11,12,13) """ - self.integ.pid = bs.unpack_from('u12', msg, i)[0] - i += 12 - + i = self.decode_integ_head(msg, i) + # SSR provider ID, solution type, iod self.pid, self.sid, self.iodssr = bs.unpack_from( 'u16u4u4', msg, i) i += 24 - # mask_sys:: DFi013 0:GPS,1:GLO,2:GAL,3:BDS,4:QZS,5:IRN - tow, mask_sys = bs.unpack_from('u30u16', msg, i) - i += 46 - - self.integ.tow = tow*1e-3 - self.tow = self.integ.tow - self.time = gpst2time(self.week, self.tow) - self.integ.mask_sys = mask_sys - - vp, udi = bs.unpack_from('u4u16', msg, i) - i += 20 - # Validity Period DFi065 (0-15) - self.integ.vp = self.integ.vp_tbl[vp] - # update rate interval DFi067 (0.1) - self.integ.udi = udi*0.1 + # GNSS Constellation Mask (DFi013) + # 0:GPS,1:GLO,2:GAL,3:BDS,4:QZS,5:IRN + mask_sys = bs.unpack_from('u16', msg, i)[0] + i += 16 sys_t, nsys = self.decode_mask(mask_sys, 16, ofst=0) iod_sys = {} @@ -4023,13 +3894,11 @@ def decode_integrity_ssr(self, msg, i): sys = self.integ.sys_tbl[sys_] mask_sat, iod_sys[sys] = bs.unpack_from('u64u2', msg, i) i += 66 - svid_t, nsat = self.decode_mask(mask_sat, 64) + svid_t, _ = self.decode_mask(mask_sat, 64) sys = self.integ.sys_tbl[sys_] flag_t[sys] = {} for svid in svid_t: - ofst = 192 if sys == uGNSS.QZS else 0 - prn = svid+ofst - sat = prn2sat(sys, prn) + sat = self.svid2sat(sys, svid) flag_t[sys][sat] = bs.unpack_from('u2', msg, i)[0] i += 2 @@ -4198,10 +4067,10 @@ def decode(self, msg, subtype=None, scanmode=False): self.decode_integrity_ext_sis_local(msg, i) elif self.msgtype in [6, 2007]: # primary sercice area self.subtype = sRTCM.INTEG_PRI_AREA - self.decode_integrity_pri_service_area(msg, i) + self.decode_integrity_service_area(msg, i) elif self.msgtype == 2008: # extended sercice area self.subtype = sRTCM.INTEG_EXT_AREA - self.decode_integrity_ext_service_area(msg, i) + self.decode_integrity_service_area(msg, i, True) elif self.msgtype in [7, 2051]: # quality indicator self.subtype = sRTCM.INTEG_QUALITY self.decode_integrity_quality(msg, i) From 0a4e6c02c458c9ed1e884a118d830ea9648b29be Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sun, 1 Mar 2026 15:49:02 +0900 Subject: [PATCH 45/48] - excluded satellite from config.yml - CSSR ST8/9 supports RTCM SSR compatible format. --- src/cssrlib/cssrlib.py | 71 +++++++++++++++++++++++++++--------------- src/cssrlib/pppssr.py | 5 ++- 2 files changed, 50 insertions(+), 26 deletions(-) diff --git a/src/cssrlib/cssrlib.py b/src/cssrlib/cssrlib.py index 8a9f9d1..00800c3 100644 --- a/src/cssrlib/cssrlib.py +++ b/src/cssrlib/cssrlib.py @@ -554,7 +554,7 @@ def decode_head(self, msg, i, st=-1): else: dtow = bs.unpack_from('u12', msg, i)[0] i += 12 - if self.tow >= 0: + if self.tow0 >= 0: self.tow = self.tow0+dtow if self.week >= 0: self.time = gpst2time(self.week, self.tow) @@ -882,25 +882,25 @@ def decode_cssr_stec(self, msg, i): if self.iodssr != head['iodssr']: return -1 self.flg_net = True - self.udi[sCType.STEC] = head['udi'] dfm = bs.unpack_from_dict('u2u5u'+str(self.nsat_n), ['stype', 'inet', 'svmaskn'], msg, i) + i += 7+self.nsat_n inet = dfm['inet'] - self.netmask[inet] = netmask = dfm['svmaskn'] + self.lc[inet].netmask = netmask = dfm['svmaskn'] self.lc[inet].sat_n = self.decode_local_sat(netmask) - self.lc[inet].nsat_n = nsat = len(self.lc[inet].sat_n) - i += 7+self.nsat_n + self.lc[inet].nsat_n = len(self.lc[inet].sat_n) self.lc[inet].stec_quality = {} + self.udi[sCType.STEC] = head['udi'] + self.lc[inet].flg_stec = 2 # stec functional term self.lc[inet].ci = {} - for k in range(nsat): - sat = self.lc[inet].sat_n[k] - v = bs.unpack_from_dict('u3u3', ['class', 'val'], msg, i) - self.lc[inet].stec_quality[sat] = self.quality_idx(v['class'], - v['val']) + self.lc[inet].stype = {} + for sat in self.lc[inet].sat_n: + self.set_t0(inet, sat, sCType.STEC, self.time) + cls_, val = bs.unpack_from('u3u3', msg, i) i += 6 + self.lc[inet].stec_quality[sat] = self.quality_idx(cls_, val) ci, i = self.decode_cssr_stec_coeff(msg, dfm['stype'], i) self.lc[inet].ci[sat] = ci - self.set_t0(inet, sat, sCType.STEC, self.time) self.lc[inet].cstat |= (1 << sCType.STEC) @@ -914,36 +914,57 @@ def decode_cssr_grid(self, msg, i): dfm = bs.unpack_from_dict('u2u1u5u'+str(self.nsat_n)+'u3u3u6', ['ttype', 'range', 'inet', 'svmaskn', 'class', 'value', 'ng'], msg, i) + i += 20+self.nsat_n + self.flg_net = True self.udi[sCType.TROP] = head['udi'] inet = dfm['inet'] - self.netmask[inet] = netmask = dfm['svmaskn'] + self.lc[inet].netmask = netmask = dfm['svmaskn'] self.lc[inet].sat_n = self.decode_local_sat(netmask) self.lc[inet].nsat_n = nsat = len(self.lc[inet].sat_n) ng = dfm['ng'] self.lc[inet].ng = ng self.lc[inet].trop_quality = self.quality_idx(dfm['class'], dfm['value']) - i += 20+self.nsat_n - sz = 7 if dfm['range'] == 0 else 16 - fmt = 's'+str(sz) - self.lc[inet].stec = np.zeros(ng, dtype=list) + + self.udi[sCType.TROP] = head['udi'] + + grid = self.grid[self.grid['nid'] == inet] + if len(grid)>0: + ah, aw = mapfParam(self.time, grid['lat'][0]) + self.lc[inet].maph = ah + self.lc[inet].mapw = aw + self.lc[inet].dth = np.zeros(ng) self.lc[inet].dtw = np.zeros(ng) + self.lc[inet].ct = np.zeros((2, 4)) + self.lc[inet].ct[0, 0] = 2.3 + self.lc[inet].ct[1, 0] = 0.252 + + sz = 7 if dfm['range'] == 0 else 16 + fmt = 's'+str(sz) + self.lc[inet].dstec = {} + self.lc[inet].s_sz = {} + + for sat in self.lc[inet].sat_n: + self.lc[inet].dstec[sat] = {} + self.lc[inet].s_sz[sat] = sz + + self.lc[inet].flg_trop = 3 if dfm['ttype'] > 0 else 0 + for j in range(ng): - self.lc[inet].stec[j] = {} if dfm['ttype'] > 0: dth, dtw = bs.unpack_from('s9s8', msg, i) i += 17 - self.lc[inet].dth[j] = self.sval(dth, 9, 0.004)+2.3 + self.lc[inet].dth[j] = self.sval(dth, 9, 0.004) self.lc[inet].dtw[j] = self.sval(dtw, 8, 0.004) - - for k in range(nsat): - sat = self.lc[inet].sat_n[k] + + for sat in self.lc[inet].sat_n: dstec = bs.unpack_from(fmt, msg, i)[0] i += sz - self.lc[inet].stec[j][sat] = self.sval(dstec, sz, 0.04) + self.lc[inet].dstec[sat][j] = self.sval(dstec, sz, 0.04) + self.lc[inet].cstat |= (1 << sCType.TROP) for k in range(nsat): self.set_t0(inet, self.lc[inet].sat_n[k], sCType.TROP, self.time) @@ -1108,8 +1129,8 @@ def decode_cssr_atmos(self, msg, i): if dfm['stec'] & 1 > 0: # residual term sz_idx = bs.unpack_from('u2', msg, i)[0] i += 2 - self.lc[inet].s_sz[sat] = sz_idx sz = self.stec_sz_t[sz_idx] + self.lc[inet].s_sz[sat] = sz scl = self.stec_scl_t[sz_idx] v = bs.unpack_from(('s'+str(sz))*ng, msg, i) i += sz*ng @@ -1163,7 +1184,7 @@ def decode_cssr(self, msg, i=0): return 0 if self.monlevel >= 2: if self.flg_net: - print("tow={:6d} subtype={:2d} inet={:2d}". + print("tow={:6.1f} subtype={:2d} inet={:2d}". format(self.tow, self.subtype, self.inet)) else: print("tow={:6.0f} subtype={:2d}".format(self.tow, @@ -1200,7 +1221,7 @@ def out_log(self): if self.time == -1: return - self.fh.write("{:4d}\t{:s}\n".format(self.msgtype, + self.fh.write("{:4d}/{:2d}\t{:s}\n".format(self.msgtype,self.subtype, time2str(self.time))) if (self.lc[0].cstat & (1 << sCType.MASK)) != (1 << sCType.MASK): return diff --git a/src/cssrlib/pppssr.py b/src/cssrlib/pppssr.py index 64aed08..2a7ff12 100644 --- a/src/cssrlib/pppssr.py +++ b/src/cssrlib/pppssr.py @@ -7,7 +7,7 @@ from cssrlib.ephemeris import satposs from cssrlib.gnss import sat2id, sat2prn, rSigRnx, uTYP, uGNSS, rCST from cssrlib.gnss import ecef2pos, tropmodel, geodist, satazel, uTideModel -from cssrlib.gnss import time2str, timediff, gpst2utc, tropmapf +from cssrlib.gnss import time2str, timediff, gpst2utc, tropmapf, id2sat from cssrlib.gnss import trop_model_tbl, iono_model_tbl, default_config from cssrlib.ppp import tidedisp, tidedispIERS2010, shapiro, windupcorr, sunmoonpos from cssrlib.peph import antModelRx, antModelTx @@ -154,6 +154,9 @@ def __init__(self, nav, pos0=np.zeros(3), if 'elmin' in config['nav']: self.nav.elmin = config['nav']['elmin']*rCST.D2R + if 'excl_sat' in config['nav']: + self.nav.excl_sat = [id2sat(id) for id in config['nav']['excl_sat']] + if 'parmode' in config['nav']: self.nav.parmode = config['nav']['parmode'] # 1: normal, 2: PAR # probability of sussefull AR From ba5e0905f698f558019576fffa46ccc053427868 Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sun, 8 Mar 2026 20:53:42 +0900 Subject: [PATCH 46/48] - cnr monitor works when cnr is available. - rtcm sc134 message output improved. --- src/cssrlib/pppssr.py | 2 +- src/cssrlib/rtcm.py | 40 +++++++++++++++++++++++++++------------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/cssrlib/pppssr.py b/src/cssrlib/pppssr.py index 2a7ff12..4c7bc59 100644 --- a/src/cssrlib/pppssr.py +++ b/src/cssrlib/pppssr.py @@ -1325,7 +1325,7 @@ def qcedit(self, obs, rs, dts, svh, rr=None): # cnr_min = self.nav.cnr_min_gpy \ if sigsCN[f].isGPS_PY() else self.nav.cnr_min - if obs.S[j, f] < cnr_min: + if obs.S[j, f]>0 and obs.S[j, f] < cnr_min: self.nav.edt[i, f] = 1 if self.nav.monlevel > 0: self.nav.fout.write( diff --git a/src/cssrlib/rtcm.py b/src/cssrlib/rtcm.py index 03d61df..c29aead 100644 --- a/src/cssrlib/rtcm.py +++ b/src/cssrlib/rtcm.py @@ -160,7 +160,7 @@ class Integrity(): mod_t = ['BPSK(1)', 'BPSK(5)', 'BPSK(10)', 'BOC(1,1)', 'CBOC(6,1,1/11)', 'AltBOC(15,10)', '', ''] - atype_t = {0:"Circle", 1:"Polygon"} + atype_t = {0:"Validity Radius", 1:"Area Points"} # mm_model_t = ['GMM', 'Mats Brenner', 'Jahn'] @@ -767,7 +767,7 @@ def ssrtype(self, msgtype): sys = uGNSS.NONE ssr = 0 for sys_ in self.ssr_t.keys(): - if msgtype >= self.ssr_t[sys_] and msgtype <= self.ssr_t[sys_]+6: + if msgtype >= self.ssr_t[sys_] and msgtype < self.ssr_t[sys_]+6: sys = sys_ ssr = msgtype-self.ssr_t[sys_]+1 break @@ -840,6 +840,8 @@ def __init__(self, foutname=None): self.test_mode = False # for interop testing in SC134 self.mt_skip = [] # skip decode for the specified message types + + self.workaround_sc134 = False def ssig2rsig(self, sys: uGNSS, utyp=None, ssig=None): """ convert ssig to rSigRnx """ @@ -986,6 +988,8 @@ def decode_head(self, msg, i, sys=uGNSS.NONE): self.tod = bs.unpack_from('u17', msg, i)[0] i += 17 self.time = utc2gpst(glo2time(self.time, self.tod)) + if self.workaround_sc134: + self.time = timeadd(self.time, -36) # work-around else: self.tow = bs.unpack_from('u20', msg, i)[0] i += 20 @@ -1481,7 +1485,7 @@ def decode_nrtk_time(self, msg, i): week, _ = time2bdt(gpst2bdt(gpst2time(self.week, tow))) time = bdt2gpst(bdt2time(week, tow)) elif sys == uGNSS.GLO: - time = glo2time(self.time, tow) + time = utc2gpst(glo2time(self.time, tow)) else: time = gpst2time(self.week, tow) return i, sys, time @@ -1520,8 +1524,15 @@ def decode_time(self, msg): sys, ssr = self.ssrtype(self.msgtype) if ssr > 0 or self.msgtype == 4076: sz = 20 if self.msgtype == 4076 or sys != uGNSS.GLO else 17 - self.tow = bs.unpack_from('u'+str(sz), msg, i)[0] - return gpst2time(self.week, self.tow) + tow = bs.unpack_from('u'+str(sz), msg, i)[0] + if sys == uGNSS.GLO: + t = utc2gpst(glo2time(self.time, tow)) + if self.workaround_sc134: + t = timeadd(t, -36) # work-around on sc134 + return t + else: + self.tow = tow + return gpst2time(self.week, self.tow) sys, nrtk = self.nrtktype(self.msgtype) if nrtk > 0: @@ -1595,15 +1606,14 @@ def out_log_ssr_ura(self, sys): def out_log_integ_head(self): vp = self.integ.vp_tbl[self.integ.vp] - self.fh.write(f" TOW (s): {self.integ.tow:9.3f}\n") - self.fh.write(f" Provider Id: {self.integ.pid}\n") + self.fh.write(f" ASP Id: {self.integ.pid}\n") self.fh.write(f" Validity Period [s]: {vp:4.1f}\n") - self.fh.write(f" Update rate interval [s]: {self.integ.udi:4.1f}\n") + self.fh.write(f" Update Interval [s]: {self.integ.udi:4.1f}\n") self.fh.write(f" Service Area ID: {self.integ.aid:d}\n") def out_log_area_point(self): if self.integ.atype == 1: # lat/lon - self.fh.write(f" Number of Area Point: {self.integ.npnt}\n") + self.fh.write(f" Number of Area Points: {self.integ.npnt}\n") self.fh.write(" Area Point (lat, lon):\n") for pos in self.integ.pos_v: self.fh.write(f" {pos[0]:15.9f} {pos[1]:15.9f}\n") @@ -2201,8 +2211,12 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): if self.subtype in (sRTCM.INTEG_SSR, sRTCM.INTEG_SSR_IONO, sRTCM.INTEG_SSR_TROP): - self.out_log_integ_head() - + self.out_log_integ_head() + + self.fh.write(f" SSR Provider ID: {self.integ.pidssr}\n") + self.fh.write(f" SSR Solution ID: {self.integ.sidssr}\n") + self.fh.write(f" IOD SSR: {self.integ.iodssr}\n") + self.fh.write(" {:20s}{:04x}\n".format("Constellation Mask:", self.integ.mask_sys)) self.fh.write(" IOD GNSS Mask: ") @@ -3877,8 +3891,8 @@ def decode_integrity_ssr(self, msg, i): i = self.decode_integ_head(msg, i) # SSR provider ID, solution type, iod - self.pid, self.sid, self.iodssr = bs.unpack_from( - 'u16u4u4', msg, i) + self.integ.pidssr, self.integ.sidssr, self.integ.iodssr = \ + bs.unpack_from('u16u4u4', msg, i) i += 24 # GNSS Constellation Mask (DFi013) From 4a03f29c49e10e29b95e9628c89221159cabe68c Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sat, 21 Mar 2026 20:59:53 +0900 Subject: [PATCH 47/48] - added list of faulty satellites in RTCM SC134 MT2011 message. --- src/cssrlib/rtcm.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/cssrlib/rtcm.py b/src/cssrlib/rtcm.py index c29aead..4b185ed 100644 --- a/src/cssrlib/rtcm.py +++ b/src/cssrlib/rtcm.py @@ -262,7 +262,9 @@ def __init__(self): self.mask_sys = 0 self.mm_param = None - + + self.sat_e = [] # excluded satellites by SSR integrity flag + def cnr_lvl(self, idx): """ get index to C/N level [dB-Hz] """ cnr = -1 @@ -1605,9 +1607,8 @@ def out_log_ssr_ura(self, sys): self.lc[0].ura[sat_])) def out_log_integ_head(self): - vp = self.integ.vp_tbl[self.integ.vp] self.fh.write(f" ASP Id: {self.integ.pid}\n") - self.fh.write(f" Validity Period [s]: {vp:4.1f}\n") + self.fh.write(f" Validity Period [s]: {self.integ.vp:4.1f}\n") self.fh.write(f" Update Interval [s]: {self.integ.udi:4.1f}\n") self.fh.write(f" Service Area ID: {self.integ.aid:d}\n") @@ -1636,8 +1637,8 @@ def out_log(self, obs=None, eph=None, geph=None, seph=None): if self.msgtype == 4076: # IGS-SSR self.fh.write(f" Message Type: IGS-SSR {self.igsssrt_t[self.subtype]} {sys2str(self.sysref)}\n") - - if sys > 0 and self.subtype not in [sRTCM.SSR_META, sRTCM.SSR_GRID]: + + if sys > 0 and self.subtype not in [None, sRTCM.SSR_META, sRTCM.SSR_GRID]: j = self.sc_t[self.subtype] self.fh.write(f" Update Interval: {self.udint_t[self.udi[j]]}[s]") self.fh.write(f" MultiMsg: {self.mi}\n") @@ -3887,7 +3888,7 @@ def decode_integrity_mmap(self, msg, i): return i def decode_integrity_ssr(self, msg, i): - """ RTCM SC-134 SSR integrity message (MT11,12,13) """ + """ RTCM SC-134 SSR integrity message (MT2011) """ i = self.decode_integ_head(msg, i) # SSR provider ID, solution type, iod @@ -3904,6 +3905,7 @@ def decode_integrity_ssr(self, msg, i): iod_sys = {} flag_t = {} + sat_e = [] for sys_ in sys_t: sys = self.integ.sys_tbl[sys_] mask_sat, iod_sys[sys] = bs.unpack_from('u64u2', msg, i) @@ -3915,7 +3917,10 @@ def decode_integrity_ssr(self, msg, i): sat = self.svid2sat(sys, svid) flag_t[sys][sat] = bs.unpack_from('u2', msg, i)[0] i += 2 - + if flag_t[sys][sat] == 1: # Do Not USe: + sat_e.append(sat) + + self.integ.sat_e = sat_e self.integ.iod_sys = iod_sys self.integ.flag = flag_t @@ -4366,7 +4371,7 @@ def encode_integrity_ssr(self, msg, i): # validity period DFi065 # update rate interval DFi067 - vp = self.integ.vp_r_tbl[self.integ.vp] + vp = self.integ.vp_r_tbl[int(self.integ.vp)] bs.pack_into('u4u16', msg, i, vp, self.integ.udi*10) i += 20 From a666a7396ebbc8faa63bd08c4affdfe1af18d802 Mon Sep 17 00:00:00 2001 From: Rui Hirokawa Date: Sat, 25 Apr 2026 22:37:19 +0900 Subject: [PATCH 48/48] - fixed grid message decode. --- src/cssrlib/rtcm.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/cssrlib/rtcm.py b/src/cssrlib/rtcm.py index 4b185ed..65d8739 100644 --- a/src/cssrlib/rtcm.py +++ b/src/cssrlib/rtcm.py @@ -411,6 +411,7 @@ class rtcmUtil: sCType.TROP: "TROP", sCType.STEC: "STEC", sCType.VTEC: "VTEC", + sCType.GRID: "GRID", } igsssrt_t = { @@ -3078,12 +3079,15 @@ def decode_ssr_grid(self, msg, i): for k in range(ofst, ofst+npnt): dlat, dlon, dalt = bs.unpack_from('s13s14s9', msg, i) i += 36 - lat += self.sval(dlat, 13, 1e-3) - lon += self.sval(dlon, 14, 1e-3) - alt += self.sval(dalt, 9, 12.5) + dlat_s = self.sval(dlat, 13, 1e-3) + dlon_s = self.sval(dlon, 14, 1e-3) + dalt_s = self.sval(dalt, 9, 12.5) + lat += dlat_s + lon += dlon_s + alt += dalt_s d = np.array([(gid, k+1, lat, lon, alt)], dtype=dtype0) self.grid = np.append(self.grid, d) - self.dpos[k-ofst, :] = (dlat*1e-3, dlon*1e-3, dalt*12.5) + self.dpos[k-ofst, :] = (dlat_s, dlon_s, dalt_s) elif gtype == 1: # grid type 1 lat0, lon0, ofst, npnt = bs.unpack_from('s18s19u12u8', msg, i) @@ -3100,11 +3104,13 @@ def decode_ssr_grid(self, msg, i): for k in range(npnt): dlat, dlon = bs.unpack_from('s13s14', msg, i) i += 27 - lat += self.sval(dlat, 13, 1e-3) - lon += self.sval(dlon, 14, 1e-3) + dlat_s = self.sval(dlat, 13, 1e-3) + dlon_s = self.sval(dlon, 14, 1e-3) + lat += dlat_s + lon += dlon_s d = np.array([(gid, k, lat, lon, 0)], dtype=dtype0) self.grid = np.append(self.grid, d) - self.dpos[k-ofst, :] = (dlat*1e-3, dlon*1e-3, 0) + self.dpos[k-ofst, :] = (dlat_s, dlon_s, 0) elif gtype == 2: # grid type 2 lat0_, lon0_, nrow, ncol, dlat_, dlon_, gma = bs.unpack_from( @@ -4437,7 +4443,7 @@ def encode_ssr_grid(self, msg, i): self.ofst, self.ng-1) i += 67 - for k in range(self.ng-1): + for k in range(1, self.ng): gid, ofst, lat_, lon_, alt_ = grid[k] dlat = lat_-lat dlon = lon_-lon @@ -4460,10 +4466,10 @@ def encode_ssr_grid(self, msg, i): lon_i = int(lon/1e-3) bs.pack_into('s18s19u12u8', msg, i, lat_i, lon_i, - self.ofst, self.ng) + self.ofst, self.ng-1) i += 57 - for k in range(self.ng-1): + for k in range(1, self.ng): gid, ofst, lat_, lon_, _ = grid[k] dlat = lat_-lat dlon = lon_-lon