gps_pvt 0.2.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []