gps_pvt 0.2.3 → 0.4.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.
@@ -7,15 +7,22 @@ require_relative 'GPS'
7
7
 
8
8
  module GPS_PVT
9
9
  class Receiver
10
+
11
+ GPS::Time.send(:define_method, :utc){ # send as work around of old Ruby
12
+ res = c_tm(GPS::Time::guess_leap_seconds(self))
13
+ res[-1] += (seconds % 1)
14
+ res
15
+ }
16
+
10
17
  def self.pvt_items(opt = {})
11
18
  opt = {
12
19
  :system => [[:GPS, 1..32]],
13
20
  :satellites => (1..32).to_a,
14
21
  }.merge(opt)
15
22
  [[
16
- [:week, :itow_rcv, :year, :month, :mday, :hour, :min, :sec],
23
+ [:week, :itow_rcv, :year, :month, :mday, :hour, :min, :sec_rcv_UTC],
17
24
  proc{|pvt|
18
- [:week, :seconds, :c_tm].collect{|f| pvt.receiver_time.send(f)}.flatten
25
+ [:week, :seconds, :utc].collect{|f| pvt.receiver_time.send(f)}.flatten
19
26
  }
20
27
  ]] + [[
21
28
  [:receiver_clock_error_meter, :longitude, :latitude, :height, :rel_E, :rel_N, :rel_U],
@@ -47,23 +54,31 @@ class Receiver
47
54
  ]] + [
48
55
  [:used_satellites, proc{|pvt| pvt.used_satellites}],
49
56
  ] + opt[:system].collect{|sys, range|
50
- bit_flip = if range.kind_of?(Array) then
51
- proc{|res, i|
57
+ range = range.kind_of?(Array) ? proc{
58
+ # check whether inputs can be converted to Range
59
+ next nil if range.empty?
60
+ a, b = range.minmax
61
+ ((b - a) == (range.length - 1)) ? (a..b) : range
62
+ }.call : range
63
+ next nil unless range
64
+ bit_flip, label = case range
65
+ when Array
66
+ [proc{|res, i|
52
67
  res[i] = "1" if i = range.index(i)
53
68
  res
54
- }
55
- else # expect Range
69
+ }, range.collect{|pen| pen & 0xFF}.reverse.join('+')]
70
+ when Range
56
71
  base_prn = range.min
57
- proc{|res, i|
72
+ [proc{|res, i|
58
73
  res[i - base_prn] = "1" if range.include?(i)
59
74
  res
60
- }
75
+ }, [:max, :min].collect{|f| range.send(f) & 0xFF}.join('..')]
61
76
  end
62
- ["#{sys}_PRN", proc{|pvt|
77
+ ["#{sys}_PRN(#{label})", proc{|pvt|
63
78
  pvt.used_satellite_list.inject("0" * range.size, &bit_flip) \
64
79
  .scan(/.{1,8}/).join('_').reverse
65
80
  }]
66
- } + [[
81
+ }.compact + [[
67
82
  opt[:satellites].collect{|prn, label|
68
83
  [:range_residual, :weight, :azimuth, :elevation, :slopeH, :slopeV].collect{|str|
69
84
  "#{str}(#{label || prn})"
@@ -138,8 +153,11 @@ class Receiver
138
153
  rel_prop
139
154
  }
140
155
  @debug = {}
141
- [:gps_options, :sbas_options].each{|target|
142
- opt = @solver.send(target) # default solver options
156
+ solver_opts = [:gps_options, :sbas_options, :glonass_options].collect{|target|
157
+ @solver.send(target)
158
+ }
159
+ solver_opts.each{|opt|
160
+ # default solver options
143
161
  opt.elevation_mask = 0.0 / 180 * Math::PI # 0 deg (use satellite over horizon)
144
162
  opt.residual_mask = 1E4 # 10 km (without residual filter, practically)
145
163
  }
@@ -168,6 +186,13 @@ class Receiver
168
186
  when :identical # same as default
169
187
  next true
170
188
  end
189
+ when :elevation_mask_deg
190
+ raise "Unknown elevation mask angle: #{v}" unless elv_deg = (Float(v) rescue nil)
191
+ $stderr.puts "Elevation mask: #{elv_deg} deg"
192
+ solver_opts.each{|opt|
193
+ opt.elevation_mask = elv_deg / 180 * Math::PI # 0 deg (use satellite over horizon)
194
+ }
195
+ next true
171
196
  when :base_station
172
197
  crd, sys = v.split(/ *, */).collect.with_index{|item, i|
173
198
  case item
@@ -219,12 +244,14 @@ class Receiver
219
244
  i = -1
220
245
  output_options[:system] << [sys_target, []]
221
246
  else
222
- output_options[:system][i].reject!{|prn| prns.include?(prn)}
247
+ output_options[:system][i][1].reject!{|prn| prns.include?(prn)}
223
248
  end
224
249
  output_options[:satellites].reject!{|prn, label| prns.include?(prn)}
225
250
  if mode == :include then
226
251
  output_options[:system][i][1] += prns
252
+ output_options[:system][i][1].sort!
227
253
  output_options[:satellites] += (labels ? prns.zip(labels) : prns)
254
+ output_options[:satellites].sort!{|a, b| [a].flatten[0] <=> [b].flatten[0]}
228
255
  end
229
256
  }
230
257
  check_sys_svid = proc{|sys_target, range_in_sys, offset|
@@ -241,6 +268,11 @@ class Receiver
241
268
  prns.each{|prn| @solver.sbas_options.send(mode, prn)}
242
269
  elsif check_sys_svid.call(:QZSS, 193..202) then
243
270
  [svid || (193..202).to_a].flatten.each{|prn| @solver.gps_options.send(mode, prn)}
271
+ elsif check_sys_svid.call(:GLONASS, 1..24, 0x100) then
272
+ prns = [svid || (1..24).to_a].flatten.collect{|i| (i & 0xFF) + 0x100}
273
+ labels = prns.collect{|prn| "GLONASS:#{prn & 0xFF}"}
274
+ update_output.call(:GLONASS, prns, labels)
275
+ prns.each{|prn| @solver.glonass_options.send(mode, prn & 0xFF)}
244
276
  else
245
277
  raise "Unknown satellite: #{spec}"
246
278
  end
@@ -317,15 +349,21 @@ class Receiver
317
349
  [[:@azimuth, az], [:@elevation, el]].each{|k, values|
318
350
  self.instance_variable_set(k, Hash[*(sats.zip(values).flatten(1))])
319
351
  }
352
+ mat_S = self.S
320
353
  [:@slopeH, :@slopeV] \
321
- .zip((self.fd ? self.slope_HV_enu.to_a.transpose : [nil, nil])) \
354
+ .zip((self.fd ? self.slope_HV_enu(mat_S).to_a.transpose : [nil, nil])) \
322
355
  .each{|k, values|
323
356
  self.instance_variable_set(k,
324
357
  Hash[*(values ? sats.zip(values).flatten(1) : [])])
325
358
  }
359
+ # If a design matrix G has columns larger than 4,
360
+ # other states excluding position and time are estimated.
361
+ @other_state = self.position_solved? \
362
+ ? (mat_S * self.delta_r.partial(self.used_satellites, 1, 0, 0)).transpose.to_a[0][4..-1] \
363
+ : []
326
364
  instance_variable_get(target)
327
365
  }
328
- [:azimuth, :elevation, :slopeH, :slopeV].each{|k|
366
+ [:azimuth, :elevation, :slopeH, :slopeV, :other_state].each{|k|
329
367
  eval("define_method(:#{k}){@#{k} || self.post_solution(:@#{k})}")
330
368
  }
331
369
  }
@@ -336,7 +374,13 @@ class Receiver
336
374
  eph.svid = prn
337
375
  [prn, eph]
338
376
  }.flatten(1)]
339
- define_method(:register_ephemeris){|t_meas, sys, prn, bcast_data|
377
+ eph_glonass_list = Hash[*(1..24).collect{|num|
378
+ eph = GPS::Ephemeris_GLONASS::new
379
+ eph.svid = num
380
+ [num, eph]
381
+ }.flatten(1)]
382
+ define_method(:register_ephemeris){|t_meas, sys, prn, bcast_data, *options|
383
+ opt = options[0] || {}
340
384
  case sys
341
385
  when :GPS, :QZSS
342
386
  next unless eph = eph_list[prn]
@@ -366,6 +410,15 @@ class Receiver
366
410
  $stderr.puts str
367
411
  } if @debug[:SBAS_IGP]
368
412
  end
413
+ when :GLONASS
414
+ next unless eph = eph_glonass_list[prn]
415
+ leap_sec = @solver.gps_space_node.is_valid_utc ?
416
+ @solver.gps_space_node.iono_utc.delta_t_LS :
417
+ GPS::Time::guess_leap_seconds(t_meas)
418
+ next unless eph.parse(bcast_data[0..3], leap_sec)
419
+ eph.freq_ch = opt[:freq_ch] || 0
420
+ @solver.glonass_space_node.register_ephemeris(prn, eph)
421
+ eph.invalidate
369
422
  end
370
423
  }
371
424
  }.call
@@ -377,7 +430,7 @@ class Receiver
377
430
  ubx = UBX::new(open(ubx_fname))
378
431
  ubx_kind = Hash::new(0)
379
432
 
380
- after_run = b || proc{|pvt| puts pvt.to_s}
433
+ after_run = b || proc{|pvt| puts pvt.to_s if pvt}
381
434
 
382
435
  gnss_serial = proc{|svid, sys|
383
436
  if sys then # new numbering
@@ -443,7 +496,11 @@ class Receiver
443
496
  }
444
497
  sys, svid = gnss_serial.call(*loader.call(36, 2).reverse)
445
498
  case sys
446
- when :GPS, :QZSS;
499
+ when :GPS, :SBAS, :QZSS;
500
+ when :GLONASS
501
+ svid += 0x100
502
+ meas.add(svid, :L1_FREQUENCY,
503
+ GPS::SpaceNode_GLONASS::L1_frequency(loader.call(39, 1, "C") - 7))
447
504
  else; next
448
505
  end
449
506
  trk_stat = loader.call(46, 1)[0]
@@ -477,12 +534,14 @@ class Receiver
477
534
  })
478
535
  when [0x02, 0x13] # RXM-SFRBX
479
536
  sys, svid = gnss_serial.call(packet[6 + 1], packet[6])
537
+ opt = {}
538
+ opt[:freq_ch] = packet[6 + 3] - 7 if sys == :GLONASS
480
539
  register_ephemeris(
481
540
  t_meas,
482
541
  sys, svid,
483
542
  packet.slice(6 + 8, 4 * packet[6 + 4]).each_slice(4).collect{|v|
484
543
  v.pack("C*").unpack("V")[0]
485
- })
544
+ }, opt)
486
545
  end
487
546
  }
488
547
  $stderr.puts ", found packets are %s"%[ubx_kind.inspect]
@@ -492,6 +551,7 @@ class Receiver
492
551
  items = [
493
552
  @solver.gps_space_node,
494
553
  @solver.sbas_space_node,
554
+ @solver.glonass_space_node,
495
555
  ].inject(0){|res, sn|
496
556
  loaded_items = sn.send(:read, fname)
497
557
  raise "Format error! (Not RINEX) #{fname}" if loaded_items < 0
@@ -501,9 +561,10 @@ class Receiver
501
561
  end
502
562
 
503
563
  def parse_rinex_obs(fname, &b)
504
- after_run = b || proc{|pvt| puts pvt.to_s}
564
+ after_run = b || proc{|pvt| puts pvt.to_s if pvt}
505
565
  $stderr.print "Reading RINEX observation file (%s)"%[fname]
506
566
  types = nil
567
+ glonass_freq = nil
507
568
  count = 0
508
569
  GPS::RINEX_Observation::read(fname){|item|
509
570
  $stderr.print '.' if (count += 1) % 1000 == 0
@@ -526,12 +587,32 @@ class Receiver
526
587
  }.compact]
527
588
  }.flatten(1))]
528
589
 
590
+ glonass_freq ||= proc{|spec|
591
+ # frequency channels described in observation file
592
+ next {} unless spec
593
+ Hash[*(spec.collect{|line|
594
+ line[4..-1].scan(/R(\d{2}).([\s+-]\d)./).collect{|prn, ch|
595
+ [prn.to_i, GPS::SpaceNode_GLONASS::L1_frequency(ch.to_i)]
596
+ }
597
+ }.flatten(2))]
598
+ }.call(item[:header]["GLONASS SLOT / FRQ #"])
599
+
529
600
  meas = GPS::Measurement::new
530
601
  item[:meas].each{|k, v|
531
602
  sys, prn = k
532
603
  case sys
533
604
  when 'G', ' '
605
+ when 'S'; prn += 100
534
606
  when 'J'; prn += 192
607
+ when 'R'
608
+ freq = (glonass_freq[prn] ||= proc{|sn|
609
+ # frequency channels saved with ephemeris
610
+ sn.update_all_ephemeris(t_meas)
611
+ next nil unless sn.ephemeris(prn).in_range?(t_meas)
612
+ sn.ephemeris(prn).frequency_L1
613
+ }.call(@solver.glonass_space_node))
614
+ prn += 0x100
615
+ meas.add(prn, :L1_FREQUENCY, freq) if freq
535
616
  else; next
536
617
  end
537
618
  types[sys] = (types[' '] || []) unless types[sys]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GPS_PVT
4
- VERSION = "0.2.3"
4
+ VERSION = "0.4.0"
5
5
  end
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.2.3
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - fenrir(M.Naruoka)
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-02-22 00:00:00.000000000 Z
11
+ date: 2022-03-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -61,7 +61,10 @@ files:
61
61
  - ext/gps_pvt/GPS/GPS_wrap.cxx
62
62
  - ext/gps_pvt/SylphideMath/SylphideMath_wrap.cxx
63
63
  - ext/gps_pvt/extconf.rb
64
+ - ext/ninja-scan-light/tool/algorithm/integral.h
64
65
  - ext/ninja-scan-light/tool/navigation/EGM.h
66
+ - ext/ninja-scan-light/tool/navigation/GLONASS.h
67
+ - ext/ninja-scan-light/tool/navigation/GLONASS_Solver.h
65
68
  - ext/ninja-scan-light/tool/navigation/GPS.h
66
69
  - ext/ninja-scan-light/tool/navigation/GPS_Solver.h
67
70
  - ext/ninja-scan-light/tool/navigation/GPS_Solver_Base.h
@@ -100,7 +103,7 @@ licenses: []
100
103
  metadata:
101
104
  homepage_uri: https://github.com/fenrir-naru/gps_pvt
102
105
  source_code_uri: https://github.com/fenrir-naru/gps_pvt
103
- post_install_message:
106
+ post_install_message:
104
107
  rdoc_options: []
105
108
  require_paths:
106
109
  - lib
@@ -115,8 +118,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
115
118
  - !ruby/object:Gem::Version
116
119
  version: '0'
117
120
  requirements: []
118
- rubygems_version: 3.3.7
119
- signing_key:
121
+ rubygems_version: 3.1.2
122
+ signing_key:
120
123
  specification_version: 4
121
124
  summary: GPS position, velocity, and time (PVT) solver
122
125
  test_files: []