Class: GPS_PVT::Receiver

Inherits:
Object
  • Object
show all
Defined in:
lib/gps_pvt/receiver.rb,
lib/gps_pvt/receiver/agps.rb,
lib/gps_pvt/receiver/rtcm3.rb,
lib/gps_pvt/receiver/almanac.rb,
lib/gps_pvt/receiver/extension.rb

Constant Summary collapse

YUMA_ITEMS =
[
  [proc{|s| s.to_i}, {
    :ID => :svid,
    :Health => :SV_health,
    :week => :WN,
  }],
  [proc{|s| Float(s)}, {
    :Eccentricity => :e,
    "Time of Applicability" => [:t_oc, :t_oe],
    "Orbital Inclination" => :i0,
    "Rate of Right Ascen" => :dot_Omega0,
    'SQRT\(A\)' => :sqrt_A,
    "Right Ascen at Week" => :Omega0,
    "Argument of Perigee" => :omega,
    "Mean Anom" => :M0,
    "Af0" => :a_f0,
    "Af1" => :a_f1,
  }],
].collect{|cnv, key_list|
  key_list.collect{|k1, k2_list|
    [/#{k1}[^:]*:/, cnv,
        *([k2_list].flatten(1).collect{|k2|
          "#{k2}=".to_sym
        })]
  }
}.flatten(1)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Receiver

Returns a new instance of Receiver.



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/gps_pvt/receiver.rb', line 152

def initialize(options = {})
  @solver = GPS::Solver::new
  @solver.options = {
    :skip_exclusion => true, # default is to skip fault exclusion calculation
  }
  @debug = {}
  @semaphore = Mutex::new
  solver_opts = [:gps_options, :sbas_options, :glonass_options].collect{|target|
    @solver.send(target)
  }
  solver_opts.each{|opt|
    # default solver options
    opt.elevation_mask = 0.0 / 180 * Math::PI # 0 deg (use satellite over horizon)
    opt.residual_mask = 1E4 # 10 km (without residual filter, practically)
  }
  output_options = {
    :system => [[:GPS, 1..32], [:QZSS, 193..202]],
    :satellites => (1..32).to_a + (193..202).to_a, # [idx, ...] or [[idx, label], ...] is acceptable
    :FDE => false,
  }
  options = options.reject{|k, v|
    def v.to_b; !(self =~ /^(?:false|0|f|off)$/i); end
    case k
    when :debug
      v = v.split(/,/)
      @debug[v[0].upcase.to_sym] = v[1..-1]
      next true
    when :weight
      case v.to_sym
      when :elevation # (same as underneath C++ library except for ignoring broadcasted/calculated URA)
        @solver.hooks[:relative_property] = proc{|prn, rel_prop, meas, rcv_e, t_arv, usr_pos, usr_vel|
          if rel_prop[0] > 0 then
            elv = Coordinate::ENU::relative_rel(
                Coordinate::XYZ::new(*rel_prop[4..6]), usr_pos).elevation
            rel_prop[0] = (Math::sin(elv)/0.8)**2
          end
          rel_prop
        }
        next true
      when :identical # treat each satellite range having same accuracy
        @solver.hooks[:relative_property] = proc{|prn, rel_prop, meas, rcv_e, t_arv, usr_pos, usr_vel|
          rel_prop[0] = 1 if rel_prop[0] > 0 # weight = 1
          rel_prop
        }
        next true
      end
    when :elevation_mask_deg
      raise "Unknown elevation mask angle: #{v}" unless elv_deg = (Float(v) rescue nil)
      $stderr.puts "Elevation mask: #{elv_deg} deg"
      solver_opts.each{|opt|
        opt.elevation_mask = elv_deg / 180 * Math::PI # 0 deg (use satellite over horizon)
      }
      next true
    when :base_station
      crd, sys = v.split(/ *, */).collect.with_index{|item, i|
        case item
        when /^([\+-]?\d+\.?\d*)([XYZNEDU]?)$/ # ex) meter[X], degree[N]
          [$1.to_f, ($2 + "XY?"[i])[0]]
        when /^([\+-]?\d+)_(?:(\d+)_(\d+\.?\d*)|(\d+\.?\d*))([NE])$/ # ex) deg_min_secN
          [$1.to_f + ($2 || $4).to_f / 60 + ($3 || 0).to_f / 3600, $5]
        else
          raise "Unknown coordinate spec.: #{item}"
        end
      }.transpose
      raise "Unknown base station: #{v}" if crd.size != 3
      @base_station = case (sys = sys.join.to_sym)
      when :XYZ, :XY?
        Coordinate::XYZ::new(*crd)
      when :NED, :ENU, :NE?, :EN? # :NE? => :NEU, :EN? => :ENU
        (0..1).each{|i| crd[i] *= (Math::PI / 180)}
        ([:NED, :NE?].include?(sys) ?
            Coordinate::LLH::new(crd[0], crd[1], crd[2] * (:NED == sys ? -1 : 1)) :
            Coordinate::LLH::new(crd[1], crd[0], crd[2])).xyz
      else
        raise "Unknown coordinate system: #{sys}"
      end
      $stderr.puts "Base station (LLH): #{
        llh = @base_station.llh.to_a
        llh[0..1].collect{|rad| rad / Math::PI * 180} + [llh[2]]
      }"
      next true
    when :with, :without
      [v].flatten.each{|spec| # array is acceptable
        sys, svid = case spec
        when Integer
          [nil, spec]
        when /^([a-zA-Z]+)(?::(-?\d+))?$/
          [$1.upcase.to_sym, (Integer($2) rescue nil)]
        when /^-?\d+$/
          [nil, $&.to_i]
        else
          next false
        end
        mode = if svid && (svid < 0) then
          svid *= -1
          (k == :with) ? :exclude : :include
        else
          (k == :with) ? :include : :exclude
        end
        update_output = proc{|sys_target, prns, labels|
          unless (i = output_options[:system].index{|sys, range| sys == sys_target}) then
            i = -1
            output_options[:system] << [sys_target, []]
          else
            output_options[:system][i][1].reject!{|prn| prns.include?(prn)}
          end
          output_options[:satellites].reject!{|prn, label| prns.include?(prn)}
          if mode == :include then
            output_options[:system][i][1] += prns
            output_options[:system][i][1].sort!
            output_options[:satellites] += (labels ? prns.zip(labels) : prns)
            output_options[:satellites].sort!{|a, b| [a].flatten[0] <=> [b].flatten[0]}
          end
        }
        check_sys_svid = proc{|sys_target, range_in_sys, offset|
          next range_in_sys.include?(svid - (offset || 0)) unless sys # svid is specified without system
          next false unless sys == sys_target
          next true unless svid # All satellites in a target system (svid == nil)
          range_in_sys.include?(svid)
        }
        if check_sys_svid.call(:GPS, 1..32) then
          [svid || (1..32).to_a].flatten.each{|prn| @solver.gps_options.send(mode, prn)}
        elsif check_sys_svid.call(:SBAS, 120..158) then
          prns = [svid || (120..158).to_a].flatten
          update_output.call(:SBAS, prns)
          prns.each{|prn| @solver.sbas_options.send(mode, prn)}
        elsif check_sys_svid.call(:QZSS, 193..202) then
          [svid || (193..202).to_a].flatten.each{|prn| @solver.gps_options.send(mode, prn)}
        elsif check_sys_svid.call(:GLONASS, 1..24, 0x100) then
          prns = [svid || (1..24).to_a].flatten.collect{|i| (i & 0xFF) + 0x100}
          labels = prns.collect{|prn| "GLONASS:#{prn & 0xFF}"}
          update_output.call(:GLONASS, prns, labels)
          prns.each{|prn| @solver.glonass_options.send(mode, prn & 0xFF)}
        else
          raise "Unknown satellite: #{spec}"
        end
        $stderr.puts "#{mode.capitalize} satellite: #{[sys, svid].compact.join(':')}"
      }
      next true
    when :fault_exclusion
      @solver.options = {:skip_exclusion => !(output_options[:FDE] = v.to_b)}
      next true
    when :use_signal
      {
        :GPS_L2C => proc{@solver.gps_options.exclude_L2C = false},
      }[v.to_sym].call rescue next false
      next true
    end
    false
  }
  raise "Unknown receiver options: #{options.inspect}" unless options.empty?
  @output = {
    :pvt => Receiver::pvt_items(output_options),
    :meas => Receiver::meas_items(output_options),
  }
end

Instance Attribute Details

#base_stationObject

Returns the value of attribute base_station.



150
151
152
# File 'lib/gps_pvt/receiver.rb', line 150

def base_station
  @base_station
end

#solverObject

Returns the value of attribute solver.



149
150
151
# File 'lib/gps_pvt/receiver.rb', line 149

def solver
  @solver
end

Class Method Details

.make_critical(fname) ⇒ Object



318
319
320
321
322
323
# File 'lib/gps_pvt/receiver.rb', line 318

def make_critical(fname)
  f_orig = instance_method(fname)
  define_method(fname){|*args, &b|
    critical{f_orig.bind(self).call(*args, &b)}
  }
end

.meas_items(opt = {}) ⇒ Object



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/gps_pvt/receiver.rb', line 123

def self.meas_items(opt = {})
  opt = {
    :satellites => (1..32).to_a,
  }.merge(opt)
  keys = [:PSEUDORANGE, :RANGE_RATE, :DOPPLER, :FREQUENCY].collect{|k|
    GPS::Measurement.const_get("L1_#{k}".to_sym)
  }
  [[
    opt[:satellites].collect{|prn, label|
      [:L1_range, :L1_rate].collect{|str| "#{str}(#{label || prn})"}
    }.flatten,
    proc{|meas|
      meas_hash = meas.to_hash
      opt[:satellites].collect{|prn, label|
        pr, rate, doppler, freq = keys.collect{|k| meas_hash[prn][k] rescue nil}
        freq ||= GPS::SpaceNode.L1_Frequency
        [pr, rate || ((-doppler * GPS::SpaceNode::light_speed / freq) rescue nil)]
      }
    }
  ]]
end

.pvt_items(opt = {}) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/gps_pvt/receiver.rb', line 18

def self.pvt_items(opt = {})
  opt = {
    :system => [[:GPS, 1..32]],
    :satellites => (1..32).to_a,
    :FDE => true,
  }.merge(opt)
  [[
    [:week, :itow_rcv, :year, :month, :mday, :hour, :min, :sec_rcv_UTC],
    proc{|pvt|
      [:week, :seconds, :utc].collect{|f| pvt.receiver_time.send(f)}.flatten
    }
  ]] + [[
    [:receiver_clock_error_meter, :longitude, :latitude, :height, :rel_E, :rel_N, :rel_U],
    proc{|pvt|
      next [nil] * 7 unless pvt.position_solved?
      [
        pvt.receiver_error,
        pvt.llh.lng / Math::PI * 180,
        pvt.llh.lat / Math::PI * 180,
        pvt.llh.alt,
      ] + (pvt.rel_ENU.to_a rescue [nil] * 3)
    } 
  ]] + [proc{
    labels = [:g, :p, :h, :v, :t].collect{|k| "#{k}dop".to_sym} \
        + [:h, :v, :t].collect{|k| "#{k}sigma".to_sym}
    [
      labels,
      proc{|pvt|
        next [nil] * 8 unless pvt.position_solved?
        labels.collect{|k| pvt.send(k)}
      }
    ]
  }.call] + [[
    [:v_north, :v_east, :v_down, :receiver_clock_error_dot_ms, :vel_sigma],
    proc{|pvt|
      next [nil] * 5 unless pvt.velocity_solved?
      [:north, :east, :down].collect{|k| pvt.velocity.send(k)} \
          + [pvt.receiver_error_rate, pvt.vel_sigma] 
    }
  ]] + [
    [:used_satellites, proc{|pvt| pvt.used_satellites}],
  ] + opt[:system].collect{|sys, range|
    range = range.kind_of?(Array) ? proc{
      # check whether inputs can be converted to Range
      next nil if range.empty?
      a, b = range.minmax
      ((b - a) == (range.length - 1)) ? (a..b) : range
    }.call : range
    next nil unless range
    bit_flip, label = case range
    when Array
      [proc{|res, i|
        res[i] = "1" if i = range.index(i)
        res
      }, range.collect{|pen| pen & 0xFF}.reverse.join('+')]
    when Range
      base_prn = range.min
      [proc{|res, i|
        res[i - base_prn] = "1" if range.include?(i)
        res
      }, [:max, :min].collect{|f| range.send(f) & 0xFF}.join('..')]
    end
    ["#{sys}_PRN(#{label})", proc{|pvt|
      pvt.used_satellite_list.inject("0" * range.size, &bit_flip) \
          .scan(/.{1,8}/).join('_').reverse
    }]
  }.compact + [[
    opt[:satellites].collect{|prn, label|
      [:range_residual, :weight, :azimuth, :elevation, :slopeH, :slopeV].collect{|str|
        "#{str}(#{label || prn})"
      }
    }.flatten,
    proc{|pvt|
      next ([nil] * 6 * opt[:satellites].size) unless pvt.position_solved?
      sats = pvt.used_satellite_list
      r, w = [:delta_r, :W].collect{|f| pvt.send(f)}
      opt[:satellites].collect{|prn, label|
        next ([nil] * 6) unless i2 = sats.index(prn)
        [r[i2, 0], w[i2, i2]] +
            [:azimuth, :elevation].collect{|f|
              pvt.send(f)[prn] / Math::PI * 180
            } + [pvt.slopeH[prn], pvt.slopeV[prn]]
      }.flatten
    },
  ]] + [[
    [:wssr, :wssr_sf, :weight_max,
        :slopeH_max, :slopeH_max_PRN, :slopeH_max_elevation,
        :slopeV_max, :slopeV_max_PRN, :slopeV_max_elevation],
    proc{|pvt|
      next [nil] * 9 unless fd = pvt.fd
      el_deg = [4, 6].collect{|i| pvt.elevation[fd[i]] / Math::PI * 180}
      fd[0..4] + [el_deg[0]] + fd[5..6] + [el_deg[1]]
    }
  ]] + (opt[:FDE] ? [[
    [:wssr_FDE_min, :wssr_FDE_min_PRN, :wssr_FDE_2nd, :wssr_FDE_2nd_PRN],
    proc{|pvt|
      [:fde_min, :fde_2nd].collect{|f|
        info = pvt.send(f)
        next ([nil] * 2) if (!info) || info.empty?
        [info[0], info[-3]] 
      }.flatten
    }
  ]] : [])
end

Instance Method Details

#attach_antex(src) ⇒ Object



707
708
709
710
711
712
713
# File 'lib/gps_pvt/receiver.rb', line 707

def attach_antex(src)
  fname = Util::get_txt(src)
  raise "Specify SP3 before ANTEX application!" unless @sp3
  applied_items = critical{@sp3.apply_antex(fname)}
  raise "Format error! (Not ANTEX) #{src}" unless applied_items >= 0
  $stderr.puts "SP3 correction with ANTEX file (%s): %d items have been processed."%[src, applied_items]
end

#attach_online_ephemeris(uri_template = [nil]) ⇒ Object



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/gps_pvt/receiver/extension.rb', line 32

def attach_online_ephemeris(uri_template = [nil])
  uri_template = uri_template.collect{|v|
    if (!v) || (v =~ /^\s*$/) then
      "ftp://gssc.esa.int/gnss/data/daily/%Y/brdc/BRDC00IGS_R_%Y%j0000_01D_MN.rnx.gz"
    else
      v
    end
  }.uniq
  loader = proc{|t_meas|
    utc = Time::utc(*t_meas.c_tm)
    uri_template.each{|v|
      uri = URI::parse(utc.strftime(v))
      begin
        self.parse_rinex_nav(uri)
      rescue Net::FTPError, Net::HTTPExceptions => e
        $stderr.puts "Skip to read due to %s (%s)"%[e.inspect.gsub(/[\r\n]/, ' '), uri]
      end
    }
  }
  run_orig = self.method(:run)
  eph_list = {}
  self.define_singleton_method(:run){|meas, t_meas, *args|
    w_d = [t_meas.week, (t_meas.seconds.to_i / 86400)]
    eph_list[w_d] ||= loader.call(t_meas)
    run_orig.call(meas, t_meas, *args)
  }
end

#attach_rinex_clk(src) ⇒ Object



715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
# File 'lib/gps_pvt/receiver.rb', line 715

def attach_rinex_clk(src)
  fname = Util::get_txt(src)
  @clk ||= GPS::RINEX_Clock::new
  read_items = @clk.read(fname)
  raise "Format error! (Not RINEX clock) #{src}" if read_items < 0
  $stderr.puts "Read RINEX clock file (%s): %d items."%[src, read_items]
  sats = @clk.satellites
  @clk.class.constants.each{|sys|
    next unless /^SYS_(?!SYSTEMS)(.*)/ =~ sys.to_s
    idx, sys_name = [@clk.class.const_get(sys), $1]
    next unless sats[idx] > 0
    next unless critical{@clk.push(@solver, idx)}
    $stderr.puts "Change clock error source of #{sys_name} to RINEX clock" 
  }
end

#attach_sp3(src) ⇒ Object



691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
# File 'lib/gps_pvt/receiver.rb', line 691

def attach_sp3(src)
  fname = Util::get_txt(src)
  @sp3 ||= GPS::SP3::new
  read_items = @sp3.read(fname)
  raise "Format error! (Not SP3) #{src}" if read_items < 0
  $stderr.puts "Read SP3 file (%s): %d items."%[src, read_items]
  sats = @sp3.satellites
  @sp3.class.constants.each{|sys|
    next unless /^SYS_(?!SYSTEMS)(.*)/ =~ sys.to_s
    idx, sys_name = [@sp3.class.const_get(sys), $1]
    next unless sats[idx] > 0
    next unless critical{@sp3.push(@solver, idx)}
    $stderr.puts "Change ephemeris source of #{sys_name} to SP3" 
  }
end

#correct_week_sem_yuma_almanac(src, week_rem = 0) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/gps_pvt/receiver/almanac.rb', line 7

def correct_week_sem_yuma_almanac(src, week_rem = 0)
  t_ref = case src.to_s
  when /www\.navcen\.uscg\.gov\/.*\/(\d{4})\//
    # ex) https://www.navcen.uscg.gov/sites/default/files/gps/almanac/20XX/(Sem|Yuma)/003.(al3|alm)
    GPS_PVT::GPS::Time::new(Time::new($1.to_i).to_a.slice(0, 6).reverse)
  when /www\.navcen\.uscg\.gov\/.*\/current_(sem|yuma)/
    GPS_PVT::GPS::Time::now
  else
    raise
  end
  q, rem = t_ref.week.divmod(1024)
  delta = rem - (week_rem % 1024)
  if delta <= -512 then
    q -= 1
  elsif delta > 512 then
    q += 1
  end
  q * 1024 + week_rem
end

#critical(&b) ⇒ Object



309
310
311
312
313
314
315
# File 'lib/gps_pvt/receiver.rb', line 309

def critical(&b)
  begin
    @semaphore.synchronize{b.call}
  rescue ThreadError # recovery from deadlock
    b.call
  end
end

#ephemeris(t, sys, prn) ⇒ Object

shortcut to access ephemeris registered in receiver



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/gps_pvt/receiver/extension.rb', line 9

def ephemeris(t, sys, prn)
  eph = case sys
  when :GPS, :QZSS
    critical{
      @solver.gps_space_node.update_all_ephemeris(t)
      @solver.gps_space_node.ephemeris(prn)
    }
  when :SBAS
    critical{
      @solver.sbas_space_node.update_all_ephemeris(t)
      @solver.sbas_space_node.ephemeris(prn)
    }
  when :GLONASS
    critical{
      @solver.glonass_space_node.update_all_ephemeris(t)
      @solver.glonass_space_node.ephemeris(prn)
    }
  else
    return nil
  end
  return (eph.valid?(t) ? eph : nil)
end

#headerObject



145
146
147
# File 'lib/gps_pvt/receiver.rb', line 145

def header
  (@output[:pvt] + @output[:meas]).transpose[0].flatten.join(',')
end

#parse_rinex_nav(src) ⇒ Object



608
609
610
611
612
613
614
615
616
617
618
619
620
# File 'lib/gps_pvt/receiver.rb', line 608

def parse_rinex_nav(src)
  fname = Util::get_txt(src)
  items = [
    @solver.gps_space_node,
    @solver.sbas_space_node,
    @solver.glonass_space_node,
  ].inject(0){|res, sn|
    loaded_items = critical{sn.send(:read, fname)}
    raise "Format error! (Not RINEX) #{src}" if loaded_items < 0
    res + loaded_items
  }
  $stderr.puts "Read RINEX NAV file (%s): %d items."%[src, items]
end

#parse_rinex_obs(src, &b) ⇒ Object



622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
# File 'lib/gps_pvt/receiver.rb', line 622

def parse_rinex_obs(src, &b)
  fname = Util::get_txt(src)
  after_run = b || proc{|pvt| puts pvt.to_s if pvt}
  $stderr.print "Reading RINEX observation file (%s)"%[src]
  types = nil
  glonass_freq = nil
  count = 0
  GPS::RINEX_Observation::read(fname){|item|
    $stderr.print '.' if (count += 1) % 1000 == 0
    t_meas = item[:time]

    types ||= Hash[*(item[:meas_types].collect{|sys, values|
      [sys, values.collect.with_index{|type_, i|
        sig_obs_type = [case type_[1..-1]
          when /^1C?$/; :L1
          when /^2[XL]$/; :L2CL
          when /^2S$/; :L2CM
          else; nil
        end, {
          'C' => :PSEUDORANGE,
          'L' => :CARRIER_PHASE,
          'D' => :DOPPLER,
          'S' => :SIGNAL_STRENGTH_dBHz,
        }[type_[0]]]
        next nil unless sig_obs_type.all?
        [i, sig_obs_type.join('_').to_sym, *sig_obs_type]
      }.compact]
    }.flatten(1))]

    glonass_freq ||= proc{|spec|
      # frequency channels described in observation file
      next {} unless spec
      Hash[*(spec.collect{|line|
        line[4..-1].scan(/R(\d{2}).([\s+-]\d)./).collect{|prn, ch|
          [prn.to_i, GPS::SpaceNode_GLONASS::L1_frequency(ch.to_i)]
        }
      }.flatten(2))]
    }.call(item[:header]["GLONASS SLOT / FRQ #"])

    meas = GPS::Measurement::new
    item[:meas].each{|(sys, prn), v|
      case sys
      when 'G', ' '
      when 'S'; prn += 100
      when 'J'; prn += 192
      when 'R'
        freq = (glonass_freq[prn] ||= proc{|sn|
          # frequency channels saved with ephemeris
          sn.update_all_ephemeris(t_meas)
          next nil unless sn.ephemeris(prn).in_range?(t_meas)
          sn.ephemeris(prn).frequency_L1
        }.call(@solver.glonass_space_node))
        prn += 0x100
        meas.add(prn, :L1_FREQUENCY, freq) if freq
      else; next
      end
      types[sys] = (types[' '] || []) unless types[sys]
      types[sys].each{|i, type_, sig_type, obs_type|
        next unless v[i]
        meas.add(prn, type_, v[i][0])
        meas.add(prn, "#{sig_type}_CARRIER_PHASE_AMBIGUITY_SCALE".to_sym, 0.5) \
            if (obs_type == :CARRIER_PHASE) && (v[i][1] & 0x2 == 0x2)
      }
    }
    after_run.call(run(meas, t_meas), [meas, t_meas])
  }
  $stderr.puts ", %d epochs."%[count] 
end

#parse_rtcm3(src, opt = {}, &b) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/gps_pvt/receiver/rtcm3.rb', line 7

def parse_rtcm3(src, opt = {}, &b)
  $stderr.print "Reading RTCM3 stream (%s) "%[src]
  require_relative '../rtcm3'
  src_io = Util::open(src)
  rtcm3 = GPS_PVT::RTCM3::new(src_io)
  ref_time = case (ref_time = opt[:ref_time])
  when GPS::Time; 
  when Time
    t_array = ref_time.utc.to_a[0..5].reverse
    GPS::Time::new(t_array, GPS::Time::guess_leap_seconds(t_array))
  when nil; GPS::Time::now
  else; raise "reference time (#{ref_time}) should be GPS::Time or Time"
  end
  leap_sec = ref_time.leap_seconds
  ref_pos = opt[:ref_pos] || if src_io.respond_to?(:property) then
    Coordinate::LLH::new(*(src_io.property.values_at(:latitude, :longitude).collect{|v|
      v.to_f / 180 * Math::PI
    } + [0])).xyz
  else 
    defined?(@base_station) ? @base_station : nil
  end
  after_run = b || proc{|pvt| puts pvt.to_s if pvt}
  t_meas, meas = [nil, {}]
  # meas := {msg_num => [[], ...]} due to duplicated observation such as 1074 and 1077
  run_proc = proc{
    meas_ = GPS::Measurement::new
    meas.sort.each{|k, values| # larger msg_num entries have higher priority
      values.each{|prn_k_v| meas_.add(*prn_k_v)}
    }
    pvt = nil
    after_run.call(pvt = run(meas_, t_meas), [meas_, ref_time = t_meas]) if t_meas
    ref_pos = pvt.xyz if pvt && pvt.position_solved?
    t_meas, meas = [nil, {}]
  }
  dt_threshold = GPS::Time::Seconds_week / 2
  tow2t = proc{|tow_sec|
    dt = tow_sec - ref_time.seconds 
    GPS::Time::new(ref_time.week + if dt <= -dt_threshold then; 1
          elsif dt >= dt_threshold then; -1
          else; 0; end, tow_sec)
  }
  utc2t = proc{|utc_tod|
    if t_meas then
      delta = (t_meas.seconds - utc_tod).to_i % (60 * 60 * 24)
      leap_sec = (delta >= (60 * 60 * 12)) ? delta - (60 * 60 * 12) : delta
      t_meas
    else
      ref_dow, ref_tod = ref_time.seconds.divmod(60 * 60 * 24)
      tod = utc_tod + leap_sec
      tod_delta = ref_tod - tod
      if tod_delta > 12 * 60 * 60 then
        ref_dow -= 1
      elsif tod_delta < -12 * 60 * 60 then
        ref_dow += 1
      end
      GPS::Time::new(ref_time.week, 0) + tod + 60 * 60 * 24 * ref_dow
    end
  }
  restore_ranges = proc{
    c_1ms = 299_792.458
    threshold = c_1ms / 10 # 100 us =~ 30 km
    cache = {} # {[sys, svid] => [range, t], ...}
    get_rough = proc{|t, sys_svid_list|
      sn_list = sys_svid_list.collect{|sys, svid|
        case sys
        when :GPS, :QZSS; @solver.gps_space_node
        when :SBAS; @solver.sbas_space_node
        when :GLONASS; @solver.glonass_space_node
        end
      }
      critical{
        sn_list.uniq.compact{|sn| sn.update_all_ephemeris(t)}
        sys_svid_list.zip(sn_list).each{|(sys, svid), sn|
          next unless sn
          eph = sn.ephemeris(svid)
          cache[[sys, svid]] = [if eph.valid?(t) then
            sv_pos, clk_err = eph.constellation(t).values_at(0, 2)
            sv_pos.distance(ref_pos) - (clk_err * c_1ms * 1E3)
          end, t]
        }
      }
    }
    per_kind = proc{|t, sys_svid_list, ranges_rem|
      get_rough.call(t, sys_svid_list.uniq.reject{|sys, svid|
        next true unless sys
        range, t2 = cache[[sys, svid]]
        range && ((t2 - t).abs <= 60)
      })
      ranges_rem.zip(sys_svid_list).collect{|rem_in, (sys, svid)|
        range_ref, t2 = cache[[sys, svid]]
        next nil unless range_ref
        q, rem_ref = range_ref.divmod(c_1ms)
        delta = rem_in - rem_ref 
        res = if delta.abs <= threshold then
          q * c_1ms + rem_in
        elsif -delta + c_1ms <= threshold
          (q - 1) * c_1ms + rem_in
        elsif delta + c_1ms <= threshold
          (q + 1) * c_1ms + rem_in
        end
        #p [sys, svid, q, rem_in, rem_ref, res]
        (cache[[sys, svid]] = [res, t])[0]
      }
    }
    proc{|t, sys_svid_list, ranges|
      [
        :pseudo_range, # for MT 1001/3/9/11, MSM1/3
        :phase_range, # for MT 1003/11, MSM2/3
      ].each{|k|
        next if ranges[k]
        k_rem = "#{k}_rem".to_sym
        ranges[k] = per_kind.call(t, sys_svid_list, ranges[k_rem]) if ranges[k_rem]
      }
    }
  }.call

  while packet = rtcm3.read_packet
    msg_num = packet.message_number
    parsed = packet.parse
    t_meas2, meas2 = [nil, []] # per_packet
    add_proc = proc{
      t_meas ||= t_meas2
      meas[msg_num] = meas2 unless meas2.empty?
    }
    case msg_num
    when 1001..1004
      t_meas2 = tow2t.call(parsed[2][0]) # DF004
      ranges = parsed.ranges
      sys_svid_list = ranges[:sat].collect{|svid|
        case svid
        when 1..32; [:GPS, svid]
        when 40..58; [:SBAS, svid + 80]
        else; [nil, svid]
        end
      }
      restore_ranges.call(t_meas2, sys_svid_list, ranges)
      item_size = sys_svid_list.size
      ([sys_svid_list] + [:pseudo_range, :phase_range, :cn].collect{|k|
        ranges[k] || ([nil] * item_size)
      }).transpose.each{|(sys, svid), pr, cpr, cn|
        next unless sys
        meas2 << [svid, :L1_PSEUDORANGE, pr] if pr
        meas2 << [svid, :L1_CARRIER_PHASE, cpr / GPS::SpaceNode.L1_WaveLength] if cpr
        meas2 << [svid, :L1_SIGNAL_STRENGTH_dBHz, cn] if cn
      }
    when 1009..1012
      t_meas2 = utc2t.call(parsed[2][0] - 60 * 60 * 3) # DF034 UTC(SU)+3hr, time of day[sec]
      ranges = parsed.ranges
      sys_svid_list = ranges[:sat].collect{|svid|
        case svid
        when 1..24; [:GLONASS, svid]
        when 40..58; [:SBAS, svid + 80]
        else; [nil, svid]
        end
      }
      restore_ranges.call(t_meas2, sys_svid_list, ranges)
      item_size = sys_svid_list.size
      ([sys_svid_list] + [:freq_ch, :pseudo_range, :phase_range, :cn].collect{|k|
        ranges[k] || ([nil] * item_size)
      }).transpose.each{|(sys, svid), freq_ch, pr, cpr, cn|
        case sys
        when :GLONASS
          svid += 0x100
          freq = GPS::SpaceNode_GLONASS::L1_frequency(freq_ch)
          len = GPS::SpaceNode_GLONASS.light_speed / freq
          meas2 << [svid, :L1_FREQUENCY, freq]
          meas2 << [svid, :L1_CARRIER_PHASE, cpr / len] if cpr
        when :SBAS
          meas2 << [svid, :L1_CARRIER_PHASE, cpr / GPS::SpaceNode.L1_WaveLength] if cpr
        else; next
        end
        meas2 << [svid, :L1_PSEUDORANGE, pr] if pr
        meas2 << [svid, :L1_SIGNAL_STRENGTH_dBHz, cn] if cn
      }
    when 1013
      leap_sec = parsed[5][0]
    when 1019, 1044
      params = parsed.params
      if msg_num == 1044
        params[:svid] += 192
        params[:fit_interval] ||= 2 * 60 * 60
      end
      params[:WN] += ((ref_time.week - params[:WN]).to_f / 1024).round * 1024
      eph = GPS::Ephemeris::new
      params.each{|k, v| eph.send("#{k}=".to_sym, v)}
      critical{
        @solver.gps_space_node.register_ephemeris(eph.svid, eph)
      }
    when 1020
      params = parsed.params
      eph = GPS::Ephemeris_GLONASS::new
      eph.F_T = 10 # [m], default to be overwritten
      params.each{|k, v|
        next if [:P3, :NA, :N_4].include?(k)
        eph.send("#{k}=".to_sym, v)
      }
      proc{|date_src|
        eph.set_date(*(date_src.all? \
            ? date_src \
            : [(ref_time + 3 * 60 * 60).c_tm(leap_sec)])) # UTC -> Moscow time
      }.call([:N_4, :NA].collect{|k| params[k]})
      eph.N_T = params[:NA] || eph.NA unless params[:N_T] # N_T is available only for GLONASS-M
      eph.rehash(leap_sec)
      critical{
        @solver.glonass_space_node.register_ephemeris(eph.svid, eph)
      }
    when 1043
      params = parsed.params
      eph = GPS::Ephemeris_SBAS::new
      tod_delta = params[:tod] - (ref_time.seconds % (24 * 60 * 60)) 
      if tod_delta > (12 * 60 * 60) then
        tod_delta -= (24 * 60 * 60)
      elsif tod_delta < -(12 * 60 * 60) then
        tod_delta += (24 * 60 * 60)
      end
      toe = ref_time + tod_delta
      eph.WN, eph.t_0 = [:week, :seconds].collect{|k| toe.send(k)}
      params.each{|k, v| eph.send("#{k}=".to_sym, v) unless [:iodn, :tod].include?(k)}
      critical{
        @solver.sbas_space_node.register_ephemeris(eph.svid, eph)
      }
    when 1071..1077, 1081..1087, 1101..1107, 1111..1117
      ranges = parsed.ranges
      glonass_freq = nil
      case msg_num / 10 # check time of measurement
      when 107, 110, 111 # GPS, SBAS, QZSS
        t_meas2 = tow2t.call(parsed[2][0]) # DF004
      when 108 # GLONASS
        t_meas2 = utc2t.call(parsed[3][0] - 60 * 60 * 3) # DF034 UTC(SU)+3hr, time of day[sec]
        glonass_freq = critical{
          @solver.glonass_space_node.update_all_ephemeris(t_meas2)
          Hash[*(ranges[:sat_sig].collect{|svid, sig| svid}.uniq.collect{|svid|
            eph = @solver.glonass_space_node.ephemeris(svid)
            next nil unless eph.in_range?(t_meas2)
            [svid, {:L1 => eph.frequency_L1}]
          }.compact.flatten(1))]
        }
      end
      sig_list, sys, svid_offset = case msg_num / 10
        when 107 # GPS
          [{2 => [:L1, GPS::SpaceNode.L1_WaveLength],
              15 => [:L2CM, GPS::SpaceNode.L2_WaveLength],
              16 => [:L2CL, GPS::SpaceNode.L2_WaveLength]}, :GPS, 0]
        when 108 # GLONASS
          [{2 => [:L1, nil]}, :GLONASS, 0x100]
        when 110 # SBAS
          [{2 => [:L1, GPS::SpaceNode.L1_WaveLength]}, :SBAS, 120]
        when 111 # QZSS
          [{2 => [:L1, GPS::SpaceNode.L1_WaveLength],
              15 => [:L2CM, GPS::SpaceNode.L2_WaveLength],
              16 => [:L2CL, GPS::SpaceNode.L2_WaveLength]}, :QZSS, 192]
        else; [{}, nil, 0]
      end
      sys_svid_list = ranges[:sat_sig].collect{|sat, sig| [sys, (sat + svid_offset) & 0xFF]}
      restore_ranges.call(t_meas2, sys_svid_list, ranges)
      item_size = sys_svid_list.size
      [:sat_sig, :pseudo_range, :phase_range, :phase_range_rate, :cn, :halfc_amb].collect{|k|
        ranges[k] || ([nil] * item_size)
      }.transpose.each{|(svid, sig), pr, cpr, dr, cn, amb|
        prefix, len = sig_list[sig]
        next unless prefix
        proc{
          next unless freq = (glonass_freq[svid] || {})[prefix]
          meas2 << [svid, "#{prefix}_FREQUENCY".to_sym, freq]
          len = GPS::SpaceNode_GLONASS.light_speed / freq
        }.call if glonass_freq
        svid += svid_offset
        meas2 << [svid, "#{prefix}_PSEUDORANGE".to_sym, pr] if pr
        meas2 << [svid, "#{prefix}_RANGE_RATE".to_sym, dr] if dr
        meas2 << [svid, "#{prefix}_CARRIER_PHASE".to_sym, cpr / len] if cpr && len
        meas2 << [svid, "#{prefix}_SIGNAL_STRENGTH_dBHz".to_sym, cn] if cn
        meas2 << [svid, "#{prefix}_CARRIER_PHASE_AMBIGUITY_SCALE".to_sym, 0.5] if amb && (amb == 1)
      }
    else
      #p({msg_num => parsed})
    end
    
    run_proc.call if t_meas && t_meas2 && ((t_meas - t_meas2).abs > 1E-3) # fallback for incorrect more_data flag
    add_proc.call
    run_proc.call if (1070..1229).include?(msg_num) && (!parsed.more_data?)
  end
end

#parse_sem_almanac(src) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/gps_pvt/receiver/almanac.rb', line 27

def parse_sem_almanac(src)
  src_io = open(Util::get_txt(src))
  raise unless src_io.readline =~ /(\d+)\s+(\S+)/ # line 1
  num, name = [$1.to_i, $2]
  raise unless src_io.readline =~ /(\d+)\s+(\d+)/ # line 2
  week, t_oa = [$1.to_i, $2.to_i]
  week = correct_week_sem_yuma_almanac(src, week)
  src_io.readline # line 3
  
  num.times.each{
    eph = GPS::Ephemeris::new
    9.times{|i| # line R-1..9
      case i
      when 0, 1, 2, 6, 7
        # N/A items; 1 => SV reference number, 7 => configuration code
        k = {0 => :svid, 2 => :URA_index, 6 => :SV_health}[i]
        v = Integer(src_io.readline)
        eph.send("#{k}=".to_sym, v) if k
      when 3..5
        res = src_io.readline.scan(/[+-]?\d+(?:\.\d+)?(?:E[+-]\d+)?/).collect{|s| Float(s)}
        raise unless res.size == 3
        res.zip({
          3 => [:e, [:i0, GPS::GPS_SC2RAD], [:dot_Omega0, GPS::GPS_SC2RAD]],
          4 => [:sqrt_A, [:Omega0, GPS::GPS_SC2RAD], [:omega, GPS::GPS_SC2RAD]],
          5 => [[:M0, GPS::GPS_SC2RAD], :a_f0, :a_f1],
        }[i]).each{|v, (k, sf)|
          eph.send("#{k}=".to_sym, sf ? (sf * v) : v)
        }
      when 8
        src_io.readline
      end
    }
    eph.i0 = GPS::GPS_SC2RAD * 0.3 + eph.i0
    eph.WN = week
    eph.t_oc = eph.t_oe = t_oa
    [:iodc, :t_GD, :a_f2, :iode, :c_rs, :delta_n,
        :c_uc, :c_us, :c_ic, :c_is, :c_rc, :dot_i0, :iode_subframe3].each{|k|
      eph.send("#{k}=", 0)
    }
    critical{@solver.gps_space_node.register_ephemeris(eph.svid, eph)}
  }
  
  $stderr.puts "Read SEM Almanac file (%s): %d items."%[src, num]
end

#parse_supl(src, opt = {}, &b) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/gps_pvt/receiver/agps.rb', line 7

def parse_supl(src, opt = {}, &b)
  $stderr.print "A-GPS (%s) "%[src]
  opt = {
    :interval => 60 * 10, # 10 min.
  }.merge(opt)
  require_relative '../supl'
  src_io = Util::open(src)
  while data = src_io.get_assisted_data
    data.ephemeris.each{|eph|
      target = case eph
      when GPS::Ephemeris; @solver.gps_space_node
      when GPS::Ephemeris_GLONASS; @solver.glonass_space_node
      when GPS::Ephemeris_SBAS; @solver.sbas_space_node
      else nil
      end
      critical{target.register_ephemeris(eph.svid, eph)} if target
    } if data.respond_to?(:ephemeris)
    critical{
      @solver.gps_space_node.update_iono_utc(data.iono_utc)
    } if data.respond_to?(:iono_utc)
    sleep(opt[:interval])
  end
end

#parse_ubx(ubx_fname, &b) ⇒ Object



467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
# File 'lib/gps_pvt/receiver.rb', line 467

def parse_ubx(ubx_fname, &b)
  $stderr.print "Reading UBX file (%s) "%[ubx_fname]
  require_relative 'ubx'

  ubx = UBX::new(Util::open(ubx_fname))
  ubx_kind = Hash::new(0)
  
  after_run = b || proc{|pvt| puts pvt.to_s if pvt}
  
  gnss_serial = proc{|svid, sys|
    if sys then # new numbering
      sys = [:GPS, :SBAS, :Galileo, :BeiDou, :IMES, :QZSS, :GLONASS][sys] if sys.kind_of?(Integer)
      case sys
      when :QZSS; svid += 192
      end
    else # old numbering
      sys = case svid
      when 1..32; :GPS
      when 120..158; :SBAS
      when 193..202; :QZSS
      when 65..96; svid -= 64; :GLONASS
      when 255; :GLONASS
      end
    end
    [sys, svid]
  }
  
  t_meas = nil
  ubx.each_packet.with_index(1){|packet, i|
    $stderr.print '.' if i % 1000 == 0
    ubx_kind[packet[2..3]] += 1
    case packet[2..3]
    when [0x02, 0x10] # RXM-RAW
      msec, week = [[0, 4, "V"], [4, 2, "v"]].collect{|offset, len, str|
        packet.slice(6 + offset, len).pack("C*").unpack(str)[0]
      }
      t_meas = GPS::Time::new(week, msec.to_f / 1000)
      meas = GPS::Measurement::new
      packet[6 + 6].times{|i|
        loader = proc{|offset, len, str|
          ary = packet.slice(6 + offset + (i * 24), len)
          str ? ary.pack("C*").unpack(str)[0] : ary
        }
        prn = loader.call(28, 1)[0]
        {
          :L1_PSEUDORANGE => [16, 8, "E"],
          :L1_DOPPLER => [24, 4, "e"],
          :L1_CARRIER_PHASE => [8, 8, "E"],
          :L1_SIGNAL_STRENGTH_dBHz => [30, 1, "c"],
        }.each{|k, prop|
          meas.add(prn, k, loader.call(*prop))
        }
        lli = packet[6 + 31 + (i * 24)]
        # bit 0 of RINEX LLI (loss of lock indicator) shows lost lock
        # between previous and current observation, which maps negative lock seconds
        meas.add(prn, :L1_LOCK_SEC, (lli & 0x01 == 0x01) ? -1 : 0)
        # set bit 1 of LLI represents possibility of half cycle ambiguity
        meas.add(prn, :L1_CARRIER_PHASE_AMBIGUITY_SCALE, 0.5) if (lli & 0x02 == 0x02)
      }
      after_run.call(run(meas, t_meas), [meas, t_meas])
    when [0x02, 0x15] # RXM-RAWX
      sec, week = [[0, 8, "E"], [8, 2, "v"]].collect{|offset, len, str|
        packet.slice(6 + offset, len).pack("C*").unpack(str)[0]
      }
      t_meas = GPS::Time::new(week, sec)
      meas = GPS::Measurement::new
      packet[6 + 11].times{|i|
        loader = proc{|offset, len, str, post|
          v = packet.slice(6 + offset + (i * 32), len)
          v = str ? v.pack("C*").unpack(str)[0] : v
          v = post.call(v) if post
          v
        }
        sys, svid = gnss_serial.call(*loader.call(36, 2).reverse)
        sigid = (packet[6 + 13] != 0) ? loader.call(38, 1, "C") : 0 # sigID if version(>0); @see UBX-18010854 
        case sys
        when :GPS
          sigid = {0 => :L1, 3 => :L2CL, 4 => :L2CM}[sigid]
        when :SBAS
          sigid = :L1
        when :QZSS
          sigid = {0 => :L1, 5 => :L2CL, 4 => :L2CM}[sigid]
        when :GLONASS
          svid += 0x100
          sigid = {0 => :L1}[sigid] # TODO: to support {2 -> :L2}
          meas.add(svid, :L1_FREQUENCY, 
              GPS::SpaceNode_GLONASS::L1_frequency(loader.call(39, 1, "C") - 7))
        else; next
        end
        next unless sigid
        trk_stat = loader.call(46, 1)[0]
        {
          :PSEUDORANGE => [16, 8, "E", proc{|v| (trk_stat & 0x1 == 0x1) ? v : nil}],
          :PSEUDORANGE_SIGMA => [43, 1, nil, proc{|v|
            (trk_stat & 0x1 == 0x1) ? (1E-2 * (1 << (v[0] & 0xF))) : nil
          }],
          :DOPPLER => [32, 4, "e"],
          :DOPPLER_SIGMA => [45, 1, nil, proc{|v| 2E-3 * (1 << (v[0] & 0xF))}],
          :CARRIER_PHASE => [24, 8, "E", proc{|v|
            case (trk_stat & 0x6)
            when 0x6; (trk_stat & 0x8 == 0x8) ? (v + 0.5) : v
            when 0x2; meas.add(svid, "#{sigid}_CARRIER_PHASE_AMBIGUITY_SCALE".to_sym, 0.5); v
            else; nil
            end
          }],
          :CARRIER_PHASE_SIGMA => [44, 1, nil, proc{|v|
            (trk_stat & 0x2 == 0x2) ? (0.004 * (v[0] & 0xF)) : nil
          }],
          :SIGNAL_STRENGTH_dBHz => [42, 1, "C"],
          :LOCK_SEC => [40, 2, "v", proc{|v| 1E-3 * v}],
        }.each{|k, prop|
          next unless v = loader.call(*prop)
          meas.add(svid, "#{sigid}_#{k}".to_sym, v) rescue nil # unsupported signal
        }
      }
      after_run.call(run(meas, t_meas), [meas, t_meas])
    when [0x02, 0x11] # RXM-SFRB
      sys, svid = gnss_serial.call(packet[6 + 1])
      register_ephemeris(
          t_meas,
          sys, svid,
          proc{|data|
            case sys # adjust padding
            when :GPS; data.collect!{|v| (v & 0xFFFFFF) << 6}
            when :SBAS; data[7] <<= 6
            end
            data
          }.call(packet.slice(6 + 2, 40).pack("C*").unpack("V*")))
    when [0x02, 0x13] # RXM-SFRBX
      sys, svid = gnss_serial.call(packet[6 + 1], packet[6])
      opt = {}
      opt[:freq_ch] = packet[6 + 3] - 7 if sys == :GLONASS
      register_ephemeris(
          t_meas,
          sys, svid,
          packet.slice(6 + 8, 4 * packet[6 + 4]).pack("C*").unpack("V*"), opt)
    end
  }
  $stderr.puts ", found packets are %s"%[ubx_kind.inspect]
end

#parse_yuma_almanac(src) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/gps_pvt/receiver/almanac.rb', line 99

def parse_yuma_almanac(src)
  src_io = open(Util::get_txt(src))
  num = 0
  
  idx_line = -1
  eph, items = nil
  while !src_io.eof?
    line = src_io.readline.chomp
    if idx_line < 0 then
      if line =~ /^\*{8}/ then
        eph = GPS::Ephemeris::new
        items = YUMA_ITEMS.clone
        idx_line = 0
      end
      next
    end
    raise unless i = items.index{|re, cnv, *k_list|
      next false unless re =~ line
      v = cnv.call($')
      k_list.each{|k| eph.send(k, v)}
      true
    }
    items.delete_at(i)
    next unless items.empty?

    [:iodc, :t_GD, :a_f2, :iode, :c_rs, :delta_n,
        :c_uc, :c_us, :c_ic, :c_is, :c_rc, :dot_i0, :iode_subframe3].each{|k|
      eph.send("#{k}=", 0)
    }
    eph.WN = correct_week_sem_yuma_almanac(src, eph.WN)
    critical{@solver.gps_space_node.register_ephemeris(eph.svid, eph)}
    num += 1
    idx_line = -1
  end
  
  $stderr.puts "Read YUMA Almanac file (%s): %d items."%[src, num]
end

#register_ephemeris(t_meas, sys, prn, bcast_data, *options) ⇒ Object



412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
# File 'lib/gps_pvt/receiver.rb', line 412

def register_ephemeris(t_meas, sys, prn, bcast_data, *options)
  @eph_list ||= Hash[*((1..32).to_a + (193..202).to_a).collect{|prn|
    eph = GPS::Ephemeris::new
    eph.svid = prn
    [prn, eph]
  }.flatten(1)]
  @eph_glonass_list ||= Hash[*(1..24).collect{|num|
    eph = GPS::Ephemeris_GLONASS::new
    eph.svid = num
    [num, eph]
  }.flatten(1)]
  opt = options[0] || {}
  case sys
  when :GPS, :QZSS
    return unless bcast_data.size == 10 # 8 for QZSS(SAIF)
    return unless eph = @eph_list[prn]
    sn = @solver.gps_space_node
    subframe, iodc_or_iode = eph.parse(bcast_data)
    if iodc_or_iode < 0 then
      begin
        sn.update_iono_utc(
            GPS::Ionospheric_UTC_Parameters::parse(bcast_data))
        [:alpha, :beta].each{|k|
          $stderr.puts "Iono #{k}: #{sn.iono_utc.send(k)}"
        } if false
      rescue
      end
      return
    end
    if t_meas and eph.consistent? then
      eph.WN = ((t_meas.week / 1024).to_i * 1024) + (eph.WN % 1024)
      sn.register_ephemeris(prn, eph)
      eph.invalidate
    end
  when :SBAS
    case @solver.sbas_space_node.decode_message(bcast_data[0..7], prn, t_meas)
    when 26
      ['', "IGP broadcasted by PRN#{prn} @ #{Time::utc(*t_meas.c_tm)}",
          @solver.sbas_space_node.ionospheric_grid_points(prn)].each{|str|
        $stderr.puts str
      } if @debug[:SBAS_IGP]
    end if t_meas
  when :GLONASS
    return unless eph = @eph_glonass_list[prn]
    leap_sec = @solver.gps_space_node.is_valid_utc ? 
        @solver.gps_space_node.iono_utc.delta_t_LS :
        GPS::Time::guess_leap_seconds(t_meas)
    return unless eph.parse(bcast_data[0..3], leap_sec)
    eph.freq_ch = opt[:freq_ch] || 0
    @solver.glonass_space_node.register_ephemeris(prn, eph)
    eph.invalidate
  end
end

#run(meas, t_meas, ref_pos = @base_station) ⇒ Object



355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
# File 'lib/gps_pvt/receiver.rb', line 355

def run(meas, t_meas, ref_pos = @base_station)
=begin
  $stderr.puts "Measurement time: #{t_meas.to_a} (a.k.a #{"%d/%d/%d %d:%d:%d UTC"%[*t_meas.c_tm]})"
  meas.to_a.collect{|prn, k, v| prn}.uniq.each{|prn|
    eph = @solver.gps_space_node.ephemeris(prn)
    $stderr.puts "XYZ(PRN:#{prn}): #{eph.constellation(t_meas)[0].to_a} (iodc: #{eph.iodc}, iode: #{eph.iode})"
  }
=end

  #@solver.gps_space_node.update_all_ephemeris(t_meas) # internally called in the following solver.solve
  pvt = critical{@solver.solve(meas, t_meas)}
  pvt.define_singleton_method(:rel_ENU){
    Coordinate::ENU::relative(xyz, ref_pos)
  } if (ref_pos && pvt.position_solved?)
  output = @output
  pvt.define_singleton_method(:to_s){
    (output[:pvt].transpose[1].collect{|task|
      task.call(pvt)
    } + output[:meas].transpose[1].collect{|task|
      task.call(meas)
    }).flatten.join(',')
  }
  pvt
end