gps_pvt 0.8.5 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,364 @@
1
+ # RTCM3 parser
2
+
3
+ require_relative 'util'
4
+
5
+ module GPS_PVT
6
+ class RTCM3
7
+ def initialize(io)
8
+ @io = io
9
+ @buf = []
10
+ end
11
+ def RTCM3.checksum(packet, range = 0..-4)
12
+ GPS_PVT::Util::CRC24Q::checksum(packet[range])
13
+ end
14
+ module Packet
15
+ def decode(bits_list, offset = nil)
16
+ Util::BitOp::extract(self, bits_list, offset || 24)
17
+ end
18
+ def message_number
19
+ decode([12]).first
20
+ end
21
+ DataFrame = proc{
22
+ unum_gen = proc{|n, sf|
23
+ next [n, proc{|v| v}] unless sf
24
+ [n, sf.kind_of?(Rational) ? proc{|v| (sf * v).to_f} : proc{|v| sf * v}]
25
+ }
26
+ num_gen = proc{|n, sf|
27
+ lim = 1 << (n - 1)
28
+ lim2 = lim << 1
29
+ next [n, proc{|v| v >= lim ? v - lim2 : v}] unless sf
30
+ [n, sf.kind_of?(Rational) ?
31
+ proc{|v| v -= lim2 if v >= lim; (sf * v).to_f} :
32
+ proc{|v| v -= lim2 if v >= lim; sf * v}]
33
+ }
34
+ num_sign_gen = proc{|n, sf|
35
+ lim = 1 << (n - 1)
36
+ next [n, proc{|v| v >= lim ? lim - v : v}] unless sf
37
+ [n, sf.kind_of?(Rational) ?
38
+ proc{|v| v = lim - v if v >= lim; (sf * v).to_f} :
39
+ proc{|v| v = lim - v if v >= lim; sf * v}]
40
+ }
41
+ invalidate = proc{|orig, err|
42
+ [orig[0], proc{|v| v == err ? nil : orig[1].call(v)}]
43
+ }
44
+ idx_list_gen = proc{|n, start|
45
+ start ||= 0
46
+ idx_list = (start...(start+n)).to_a.reverse
47
+ [n, proc{|v| idx_list.inject([]){|res, idx|
48
+ res.unshift(idx) if (v & 0x1) > 0
49
+ break res unless (v >>= 1) > 0
50
+ res
51
+ } }]
52
+ }
53
+ sc2rad = 3.1415926535898
54
+ df = { # {df_num => [bits, post_process] or generator_proc, ...}
55
+ 1 => proc{|n| n},
56
+ 2 => 12,
57
+ 3 => 12,
58
+ 4 => unum_gen.call(30, Rational(1, 1000)), # [sec]
59
+ 9 => 6,
60
+ 21 => 6,
61
+ 22 => 1,
62
+ 23 => 1,
63
+ 24 => 1,
64
+ 25 => num_gen.call(38, Rational(1, 10000)), # [m]
65
+ 34 => unum_gen.call(27, Rational(1, 1000)), # [sec]
66
+ 38 => 6,
67
+ 40 => 5,
68
+ 71 => 8,
69
+ 76 => 10,
70
+ 77 => proc{
71
+ idx2meter = [
72
+ 2.40, 3.40, 4.85, 6.85, 9.65, 13.65, 24.00, 48.00,
73
+ 96.00, 192.00, 384.00, 768.00, 1536.00, 3072.00, 6144.00]
74
+ [4, proc{|v| (v >= idx2meter.size) ? (idx2meter[-1] * 2) : idx2meter[v]}]
75
+ }.call, # [m]
76
+ 78 => 2,
77
+ 79 => num_gen.call(14, Rational(sc2rad, 1 << 43)), # [rad/s]
78
+ 81 => unum_gen.call(16, 1 << 4), # [sec]
79
+ 82 => num_gen.call(8, Rational(1, 1 << 55)), # [s/s^2]
80
+ 83 => num_gen.call(16, Rational(1, 1 << 43)), # [s/s]
81
+ 84 => num_gen.call(22, Rational(1, 1 << 31)), # [sec]
82
+ 85 => 10,
83
+ 86 => num_gen.call(16, Rational(1, 1 << 5)), # [m]
84
+ 87 => num_gen.call(16, Rational(sc2rad, 1 << 43)), # [rad/s]
85
+ 88 => num_gen.call(32, Rational(sc2rad, 1 << 31)), # [rad/s]
86
+ 89 => num_gen.call(16, Rational(1, 1 << 29)), # [rad]
87
+ 90 => unum_gen.call(32, Rational(1, 1 << 33)),
88
+ 91 => num_gen.call(16, Rational(1, 1 << 29)), # [rad]
89
+ 92 => unum_gen.call(32, Rational(1, 1 << 19)), # [m^1/2]
90
+ 93 => unum_gen.call(16, 1 << 4), # [sec]
91
+ 94 => num_gen.call(16, Rational(1, 1 << 29)), # [rad]
92
+ 95 => num_gen.call(32, Rational(sc2rad, 1 << 31)), # [rad/s]
93
+ 96 => num_gen.call(16, Rational(1, 1 << 29)), # [rad]
94
+ 97 => num_gen.call(32, Rational(sc2rad, 1 << 31)), # [rad/s]
95
+ 98 => num_gen.call(16, Rational(1, 1 << 5)), # [m]
96
+ 99 => num_gen.call(32, Rational(sc2rad, 1 << 31)), # [rad]
97
+ 100 => num_gen.call(24, Rational(sc2rad, 1 << 43)), # [rad/s]
98
+ 101 => num_gen.call(8, Rational(1, 1 << 31)), # [sec]
99
+ 102 => 6,
100
+ 103 => 1,
101
+ 104 => 1,
102
+ 105 => 1,
103
+ 106 => [2, proc{|v| [0, 30, 45, 60][v] * 60}], # [s]
104
+ 107 => [12, proc{|v|
105
+ hh, mm, ss = [v >> 7, (v & 0x7E) >> 1, (v & 0x1) > 0 ? 30 : 0]
106
+ hh * 3600 + mm * 60 + ss # [sec]
107
+ }],
108
+ 108 => 1,
109
+ 109 => 1,
110
+ 110 => unum_gen.call(7, 15 * 60), # [sec]
111
+ 111 => num_sign_gen.call(24, Rational(1000, 1 << 20)), # [m/s]
112
+ 112 => num_sign_gen.call(27, Rational(1000, 1 << 11)), # [m]
113
+ 113 => num_sign_gen.call(5, Rational(1000, 1 << 30)), # [m/s^2]
114
+ 120 => 1,
115
+ 121 => num_sign_gen.call(11, Rational(1, 1 << 40)),
116
+ 122 => 2, # (M)
117
+ 123 => 1, # (M)
118
+ 124 => num_sign_gen.call(22, Rational(1, 1 << 30)), # [sec]
119
+ 125 => num_sign_gen.call(5, Rational(1, 1 << 30)), # [sec], (M)
120
+ 126 => 5, # [day]
121
+ 127 => 1, # (M)
122
+ 128 => [4, proc{|v|
123
+ [1, 2, 2.5, 4, 5, 7, 10, 12, 14, 16, 32, 64, 128, 256, 512, 1024][v]
124
+ }], # [m] (M)
125
+ 129 => 11, # [day]
126
+ 130 => 2, # 1 => GLONASS-M, (M) fields are active
127
+ 131 => 1,
128
+ 132 => 11, # [day]
129
+ 133 => num_sign_gen.call(32, Rational(1, 1 << 31)), # [sec]
130
+ 134 => 5, # [4year], (M)
131
+ 135 => num_sign_gen.call(22, Rational(1, 1 << 30)), # [sec], (M)
132
+ 136 => 1, # (M)
133
+ 137 => 1,
134
+ 141 => 1,
135
+ 142 => 1,
136
+ 248 => 30,
137
+ 364 => 2,
138
+ 393 => 1,
139
+ 394 => idx_list_gen.call(64, 1),
140
+ 395 => idx_list_gen.call(32, 1),
141
+ 396 => proc{|df394, df395|
142
+ x_list = df394.product(df395)
143
+ idx_list = idx_list_gen.call(x_list.size)[1]
144
+ [x_list.size, proc{|v| x_list.values_at(*idx_list.call(v))}]
145
+ },
146
+ 397 => invalidate.call(unum_gen.call(8, Rational(1, 1000)), 0xFF), # [sec]
147
+ 398 => unum_gen.call(10, Rational(1, 1000 << 10)), # [sec]
148
+ 399 => invalidate.call(num_gen.call(14), 0x2000), # [m/s]
149
+ 404 => invalidate.call(num_gen.call(15, Rational(1, 10000)), 0x4000), # [m/s]
150
+ 405 => invalidate.call(num_gen.call(20, Rational(1, 1000 << 29)), 0x80000), # [sec]
151
+ 406 => invalidate.call(num_gen.call(24, Rational(1, 1000 << 31)), 0x800000), # [sec]
152
+ 407 => 10,
153
+ 408 => unum_gen.call(10, Rational(1, 1 << 4)), # [dB-Hz]
154
+ 409 => 3,
155
+ 411 => 2,
156
+ 412 => 2,
157
+ 416 => 3,
158
+ 417 => 1,
159
+ 418 => 3,
160
+ 420 => 1,
161
+ 429 => 4,
162
+ :uint => proc{|n| n},
163
+ }
164
+ df[27] = df[26] = df[25]
165
+ df[117] = df[114] = df[111]
166
+ df[118] = df[115] = df[112]
167
+ df[119] = df[116] = df[113]
168
+ {430..433 => 81..84, 434 => 71, 435..449 => 86..100, 450 => 79, 451 => 78,
169
+ 452 => 76, 453 => 77, 454 => 102, 455 => 101, 456 => 85, 457 => 137}.each{|dst, src|
170
+ # QZSS ephemeris => GPS
171
+ src = (src.to_a rescue [src]).flatten
172
+ (dst.to_a rescue ([dst] * src.size)).flatten.zip(src).each{|i, j| df[i] = df[j]}
173
+ }
174
+ df.define_singleton_method(:generate_prop){|idx_list|
175
+ hash = Hash[*([:bits, :op].collect.with_index{|k, i|
176
+ [k, idx_list.collect{|idx, *args|
177
+ case prop = self[idx]
178
+ when Proc; prop = prop.call(*args)
179
+ end
180
+ [prop].flatten(1)[i]
181
+ }]
182
+ }.flatten(1))].merge({:df => idx_list})
183
+ hash[:bits_total] = hash[:bits].inject{|a, b| a + b}
184
+ hash
185
+ }
186
+ df
187
+ }.call
188
+ MessageType = Hash[*({
189
+ 1005 => [2, 3, 21, 22, 23, 24, 141, 25, 142, [1, 1], 26, 364, 27],
190
+ 1019 => [2, 9, (76..79).to_a, 71, (81..103).to_a, 137].flatten, # 488 bits @see Table 3.5-21
191
+ 1020 => [2, 38, 40, (104..136).to_a].flatten, # 360 bits @see Table 3.5-21
192
+ 1044 => [2, (429..457).to_a].flatten, # 485 bits
193
+ 1077 => [2, 3, 4, 393, 409, [1, 7], 411, 412, 417, 418, 394, 395], # 169 bits @see Table 3.5-78
194
+ 1087 => [2, 3, 416, 34, 393, 409, [1, 7], 411, 412, 417, 418, 394, 395], # 169 bits @see Table 3.5-93
195
+ 1097 => [2, 3, 248, 393, 409, [1, 7], 411, 412, 417, 418, 394, 395], # 169 bits @see Table 3.5-98
196
+ 1117 => [2, 3, 4, 393, 409, [1, 7], 411, 412, 417, 418, 394, 395], # 169 bits
197
+ }.collect{|mt, df_list| [mt, DataFrame.generate_prop(df_list)]}.flatten(1))]
198
+ module GPS_Ephemeris
199
+ KEY2IDX = {:svid => 1, :WN => 2, :URA => 3, :dot_i0 => 5, :iode => 6, :t_oc => 7,
200
+ :a_f2 => 8, :a_f1 => 9, :a_f0 => 10, :iodc => 11, :c_rs => 12, :delta_n => 13,
201
+ :M0 => 14, :c_uc => 15, :e => 16, :c_us => 17, :sqrt_A => 18, :t_oe => 19, :c_ic => 20,
202
+ :Omega0 => 21, :c_is => 22, :i0 => 23, :c_rc => 24, :omega => 25, :dot_Omega0 => 26,
203
+ :t_GD => 27, :SV_health => 28}
204
+ def params
205
+ # TODO WN is truncated to 0-1023
206
+ res = Hash[*(KEY2IDX.collect{|k, i| [k, self[i][0]]}.flatten(1))]
207
+ res[:fit_interval] = ((self[29] == 0) ? 4 : case res[:iodc]
208
+ when 240..247; 8
209
+ when 248..255, 496; 14
210
+ when 497..503; 26
211
+ when 504..510; 50
212
+ when 511, 752..756; 74
213
+ when 757..763; 98
214
+ when 764..767, 1088..1010; 122
215
+ when 1011..1020; 146
216
+ else; 6
217
+ end) * 60 * 60
218
+ res
219
+ end
220
+ end
221
+ module GLONASS_Ephemeris
222
+ def params
223
+ # TODO insufficient: :n => ?(String4); extra: :P3
224
+ # TODO generate time with t_b, N_T, NA, N_4
225
+ # TODO GPS.i is required to modify to generate EPhemeris_with_GPS_Time
226
+ k_i = {:svid => 1, :freq_ch => 2, :P1 => 5, :t_k => 6, :B_n => 7, :P2 => 8, :t_b => 9,
227
+ :xn_dot => 10, :xn => 11, :xn_ddot => 12,
228
+ :yn_dot => 13, :yn => 14, :yn_ddot => 15,
229
+ :zn_dot => 16, :zn => 17, :zn_ddot => 18,
230
+ :P3 => 19, :gamma_n => 20, :p => 21, :tau_n => 23, :delta_tau_n => 24, :E_n => 25,
231
+ :P4 => 26, :F_T => 27, :N_T => 28, :M => 29}
232
+ k_i.merge!({:NA => 31, :tau_c => 32, :N_4 => 33, :tau_GPS => 34}) if self[30][0] == 1 # check DF131
233
+ res = Hash[*(k_i.collect{|k, i| [k, self[i][0]]}.flatten(1))]
234
+ res.reject!{|k, v|
235
+ case k
236
+ when :N_T; v == 0
237
+ when :p, :delta_tau_n, :P4, :F_T, :N_4, :tau_GPS; true # TODO sometimes delta_tau_n is valid?
238
+ else; false
239
+ end
240
+ } if (res[:M] != 1) # check DF130
241
+ res
242
+ end
243
+ end
244
+ module QZSS_Ephemeris
245
+ KEY2IDX = {:svid => 1, :t_oc => 2, :a_f2 => 3, :a_f1 => 4, :a_f0 => 5,
246
+ :iode => 6, :c_rs => 7, :delta_n => 8, :M0 => 9, :c_uc => 10, :e => 11,
247
+ :c_us => 12, :sqrt_A => 13, :t_oe => 14, :c_ic => 15, :Omega0 => 16,
248
+ :c_is => 17, :i0 => 18, :c_rc => 19, :omega => 20, :dot_Omega0 => 21,
249
+ :dot_i0 => 22, :WN => 24, :URA => 25, :SV_health => 26,
250
+ :t_GD => 27, :iodc => 28}
251
+ def params
252
+ # TODO PRN = svid + 192, WN is truncated to 0-1023
253
+ res = Hash[*(KEY2IDX.collect{|k, i| [k, self[i][0]]}.flatten(1))]
254
+ res[:fit_interval] = (self[29] == 0) ? 2 * 60 * 60 : nil # TODO how to treat fit_interval > 2 hrs
255
+ res
256
+ end
257
+ end
258
+ module MSM_Header
259
+ def more_data?
260
+ self.find{|v| v[1] == 393}[0] == 1
261
+ end
262
+ end
263
+ module MSM7
264
+ SPEED_OF_LIGHT = 299_792_458
265
+ def ranges
266
+ idx_sat = self.find_index{|v| v[1] == 394}
267
+ sats = self[idx_sat][0]
268
+ nsat = sats.size
269
+ cells = self[idx_sat + 2][0] # DF396
270
+ ncell = cells.size
271
+ offset = idx_sat + 3
272
+ range_rough = self[offset, nsat] # DF397
273
+ range_rough2 = self[offset + (nsat * 2), nsat] # DF398
274
+ delta_rough = self[offset + (nsat * 3), nsat] # DF399
275
+ range_fine = self[offset + (nsat * 4), ncell] # DF405
276
+ phase_fine = self[offset + (nsat * 4) + (ncell * 1), ncell] # DF406
277
+ delta_fine = self[offset + (nsat * 4) + (ncell * 5), ncell] # DF404
278
+ Hash[*([:pseudo_range, :phase_range, :phase_range_rate, :sat_sig].zip(
279
+ cells.collect.with_index{|(sat, sig), i|
280
+ i2 = sats.find_index(sat)
281
+ rough_ms = (range_rough2[i2][0] + range_rough[i2][0]) rescue nil
282
+ [(((range_fine[i][0] + rough_ms) * SPEED_OF_LIGHT) rescue nil),
283
+ (((phase_fine[i][0] + rough_ms) * SPEED_OF_LIGHT) rescue nil),
284
+ ((delta_fine[i][0] + delta_rough[i2][0]) rescue nil)]
285
+ }.transpose + [cells]).flatten(1))]
286
+ end
287
+ end
288
+ def parse
289
+ msg_num = message_number
290
+ return nil unless (mt = MessageType[msg_num])
291
+ # return [[value, df], ...]
292
+ values, df_list, attributes = [[], [], []]
293
+ add_proc = proc{|target, offset|
294
+ values += decode(target[:bits], offset).zip(target[:op]).collect{|v, op|
295
+ op ? op.call(v) : v
296
+ }
297
+ df_list += target[:df]
298
+ }
299
+ add_proc.call(mt)
300
+ case msg_num
301
+ when 1019
302
+ attributes << GPS_Ephemeris
303
+ when 1020
304
+ attributes << GLONASS_Ephemeris
305
+ when 1044
306
+ attributes << QZSS_Ephemeris
307
+ when 1077, 1087, 1097, 1117
308
+ # 1077(GPS), 1087(GLONASS), 1097(GALILEO), 1117(QZSS)
309
+ attributes << MSM7
310
+ nsat, nsig = [-2, -1].collect{|i| values[i].size}
311
+ offset = 24 + mt[:bits_total]
312
+ df396 = DataFrame.generate_prop([[396, values[-2], values[-1]]])
313
+ add_proc.call(df396, offset)
314
+ ncell = values[-1].size
315
+ offset += df396[:bits_total]
316
+ msm7_sat = DataFrame.generate_prop(
317
+ ([[397, [:uint, 4], 398, 399]] * nsat).transpose.flatten(1))
318
+ add_proc.call(msm7_sat, offset)
319
+ offset += msm7_sat[:bits_total]
320
+ msm7_sig = DataFrame.generate_prop(
321
+ ([[405, 406, 407, 420, 408, 404]] * ncell).transpose.flatten(1))
322
+ add_proc.call(msm7_sig, offset)
323
+ end
324
+ attributes << MSM_Header if (1070..1229).include?(msg_num)
325
+ res = values.zip(df_list)
326
+ attributes.empty? ? res : res.extend(*attributes)
327
+ end
328
+ end
329
+ def read_packet
330
+ while !@io.eof?
331
+ if @buf.size < 6 then
332
+ @buf += @io.read(6 - @buf.size).unpack('C*')
333
+ return nil if @buf.size < 6
334
+ end
335
+
336
+ if @buf[0] != 0xD3 then
337
+ @buf.shift
338
+ next
339
+ elsif (@buf[1] & 0xFC) != 0x0 then
340
+ @buf = @buf[2..-1]
341
+ next
342
+ end
343
+
344
+ len = ((@buf[1] & 0x3) << 8) + @buf[2]
345
+ if @buf.size < len + 6 then
346
+ @buf += @io.read(len + 6 - @buf.size).unpack('C*')
347
+ return nil if @buf.size < len + 6
348
+ end
349
+
350
+ #p (((["%02X"] * 3) + ["%06X"]).join(', '))%[*(@buf[(len + 3)..(len + 5)]) + [RTCM3::checksum(@buf)]]
351
+ if "\0#{@buf[(len + 3)..(len + 5)].pack('C3')}".unpack('N')[0] != RTCM3::checksum(@buf) then
352
+ @buf = @buf[2..-1]
353
+ next
354
+ end
355
+
356
+ packet = @buf[0..(len + 5)]
357
+ @buf = @buf[(len + 6)..-1]
358
+
359
+ return packet.extend(Packet)
360
+ end
361
+ return nil
362
+ end
363
+ end
364
+ end
data/lib/gps_pvt/util.rb CHANGED
@@ -36,6 +36,17 @@ proc{
36
36
  require 'open-uri'
37
37
  require_relative 'ntrip'
38
38
 
39
+ class URI::Ntrip
40
+ def read_format(options = {})
41
+ pnt_list = self.read_source_table(options).mount_points
42
+ case pnt_list[self.mount_point][:format]
43
+ when /u-?b(?:lo)?x/i; :ubx
44
+ when /RTCM 3/i; :rtcm3
45
+ else; nil
46
+ end
47
+ end
48
+ end
49
+
39
50
  module GPS_PVT
40
51
  module Util
41
52
  class << self
@@ -79,5 +90,38 @@ module Util
79
90
  }
80
91
  end
81
92
  end
93
+ module CRC24Q
94
+ POLY = 0x1864CFB
95
+ TABLE = 0x100.times.collect{|i|
96
+ res = i << 16
97
+ 8.times{
98
+ res <<= 1
99
+ res ^= POLY if (res & 0x1000000) > 0
100
+ }
101
+ res
102
+ }
103
+ def CRC24Q.checksum(bytes)
104
+ bytes.inject(0){|crc, byte|
105
+ ((crc << 8) & 0xFFFF00) ^ TABLE[byte ^ ((crc >> 16) & 0xFF)]
106
+ }
107
+ end
108
+ end
109
+ module BitOp
110
+ MASK = (1..8).collect{|i| (1 << i) - 1}.reverse
111
+ def BitOp.extract(src_bytes, bits_list, offset = 0)
112
+ res = []
113
+ bits_list.inject(offset.divmod(8) + [offset]){|(qM, rM, skip), bits|
114
+ qL, rL = (skip += bits).divmod(8)
115
+ v = src_bytes[qM] & MASK[rM]
116
+ res << if rL > 0 then
117
+ src_bytes[(qM+1)..qL].inject(v){|v2, b| (v2 << 8) | b} >> (8 - rL)
118
+ else
119
+ src_bytes[(qM+1)..(qL-1)].inject(v){|v2, b| (v2 << 8) | b}
120
+ end
121
+ [qL, rL, skip]
122
+ }
123
+ res
124
+ end
125
+ end
82
126
  end
83
127
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GPS_PVT
4
- VERSION = "0.8.5"
4
+ VERSION = "0.9.0"
5
5
 
6
6
  def GPS_PVT.version_compare(a, b)
7
7
  Gem::Version::new(a) <=> Gem::Version::new(b)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gps_pvt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.5
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - fenrir(M.Naruoka)
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-12-22 00:00:00.000000000 Z
11
+ date: 2023-01-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubyserial
@@ -137,6 +137,8 @@ files:
137
137
  - lib/gps_pvt/ntrip.rb
138
138
  - lib/gps_pvt/receiver.rb
139
139
  - lib/gps_pvt/receiver/extension.rb
140
+ - lib/gps_pvt/receiver/rtcm3.rb
141
+ - lib/gps_pvt/rtcm3.rb
140
142
  - lib/gps_pvt/ubx.rb
141
143
  - lib/gps_pvt/util.rb
142
144
  - lib/gps_pvt/version.rb