gps_pvt 0.2.1 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -238,6 +238,23 @@ INSTANTIATE_COMPLEX(double, D);
238
238
 
239
239
  #undef INSTANTIATE_COMPLEX
240
240
 
241
+ #if defined(SWIGRUBY)
242
+ /* Work around of miss detection of negative value on Windows Ruby (devkit).
243
+ * This results from SWIG_AsVal(unsigned int) depends on SWIG_AsVal(unsigned long),
244
+ * and sizeof(long) == sizeof(int).
245
+ */
246
+ %fragment("check_value"{unsigned int}, "header"){
247
+ inline bool is_lt_zero_after_asval(const unsigned int &i){
248
+ return ((sizeof(unsigned int) == sizeof(unsigned long)) && ((UINT_MAX >> 1) <= i));
249
+ }
250
+ void raise_if_lt_zero_after_asval(const unsigned int &i){
251
+ if(is_lt_zero_after_asval(i)){
252
+ SWIG_exception(SWIG_ValueError, "Expected positive value.");
253
+ }
254
+ }
255
+ }
256
+ #endif
257
+
241
258
  #define DO_NOT_INSTANTIATE_SCALAR_MATRIX
242
259
  #define USE_MATRIX_VIEW_FILTER
243
260
 
@@ -420,8 +437,6 @@ class Matrix : public Matrix_Frozen<T, Array2D_Type, ViewType> {
420
437
  #endif
421
438
 
422
439
  typedef Matrix<T, Array2D_Type, ViewType> self_t;
423
- self_t &swapRows(const unsigned int &row1, const unsigned int &row2);
424
- self_t &swapColumns(const unsigned int &column1, const unsigned int &column2);
425
440
  };
426
441
 
427
442
  %inline {
@@ -949,6 +964,7 @@ MAKE_TO_S(Matrix_Frozen)
949
964
  }
950
965
  #if defined(SWIGRUBY)
951
966
  %fragment(SWIG_AsVal_frag(unsigned int));
967
+ %fragment("check_value"{unsigned int});
952
968
  Matrix(const void *replacer){
953
969
  const SWIG_Object *value(static_cast<const SWIG_Object *>(replacer));
954
970
  static const ID id_r(rb_intern("row_size")), id_c(rb_intern("column_size"));
@@ -959,13 +975,10 @@ MAKE_TO_S(Matrix_Frozen)
959
975
  MatrixUtil::replace(res, replacer);
960
976
  return new Matrix<T, Array2D_Type, ViewType>(res);
961
977
  }else if(value && rb_respond_to(*value, id_r) && rb_respond_to(*value, id_c)){
962
- /* "unsigned" is remove because SWIG_AsVal(unsigned int)
963
- * can not detect less than zero in Windows Ruby devkit.
964
- */
965
- int r, c;
978
+ unsigned int r, c;
966
979
  VALUE v_r(rb_funcall(*value, id_r, 0, 0)), v_c(rb_funcall(*value, id_c, 0, 0));
967
- if(!SWIG_IsOK(SWIG_AsVal(int)(v_r, &r)) || (r < 0)
968
- || !SWIG_IsOK(SWIG_AsVal(int)(v_c, &c)) || (c < 0)){
980
+ if(!SWIG_IsOK(SWIG_AsVal(unsigned int)(v_r, &r)) || is_lt_zero_after_asval(r)
981
+ || !SWIG_IsOK(SWIG_AsVal(unsigned int)(v_c, &c)) || is_lt_zero_after_asval(c)){
969
982
  throw std::runtime_error(
970
983
  std::string("Unexpected length [")
971
984
  .append(inspect_str(v_r)).append(", ")
@@ -980,7 +993,23 @@ MAKE_TO_S(Matrix_Frozen)
980
993
  }
981
994
  #endif
982
995
 
983
- %typemap(out) self_t & "$result = self;"
996
+ /*
997
+ * Returning (*this) requires special care;
998
+ * "self_t &func(){return (*this);}" in a C++ file may generate
999
+ * a new wrapped object deleted by GC in the target language
1000
+ * unless the care.
1001
+ *
1002
+ * Work around 1)
1003
+ * %typemap(in, numinputs=0) self_t *self_p "";
1004
+ * %typemap(argout) self_t *self_p "$result = self;";
1005
+ * void func(self_t *self_p){...}
1006
+ *
1007
+ * Work around 2) (useful without overwrite of the original source, but may overfit)
1008
+ * %typemap(out) self_t & "$result = self;"
1009
+ * self_t &func(){...; return *$self;}
1010
+ */
1011
+ %typemap(in, numinputs=0) self_t *self_p "";
1012
+ %typemap(argout) self_t *self_p "$result = self;";
984
1013
 
985
1014
  T &__setitem__(const unsigned int &row, const unsigned int &column, const T &value) {
986
1015
  return (($self)->operator()(row, column) = value);
@@ -997,41 +1026,50 @@ MAKE_TO_S(Matrix_Frozen)
997
1026
  #endif
998
1027
  %rename("scalar") getScalar;
999
1028
  %rename("I") getI;
1000
- %rename("swap_rows") swapRows;
1001
- %rename("swap_columns") swapColumns;
1029
+
1030
+ void swap_rows(
1031
+ self_t *self_p,
1032
+ const unsigned int &r1, const unsigned int &r2){
1033
+ $self->swapRows(r1, r2);
1034
+ }
1035
+ void swap_columns(
1036
+ self_t *self_p,
1037
+ const unsigned int &c1, const unsigned int &c2){
1038
+ $self->swapColumns(c1, c2);
1039
+ }
1002
1040
 
1003
1041
  template <class T2, class Array2D_Type2, class ViewType2>
1004
- self_t &replace(const Matrix_Frozen<T2, Array2D_Type2, ViewType2> &matrix){
1005
- return $self->replace(matrix);
1042
+ void replace(
1043
+ self_t *self_p,
1044
+ const Matrix_Frozen<T2, Array2D_Type2, ViewType2> &matrix){
1045
+ $self->replace(matrix);
1006
1046
  }
1007
1047
  INSTANTIATE_MATRIX_FUNC(replace, replace);
1008
1048
 
1009
- self_t &replace(const void *replacer = NULL){
1049
+ void replace(self_t *self_p, const void *replacer = NULL){
1010
1050
  if(!MatrixUtil::replace(*$self, replacer)){
1011
1051
  throw std::runtime_error("Unsupported replacement");
1012
1052
  }
1013
- return *$self;
1014
1053
  }
1015
1054
 
1016
- self_t &replace(const T *serialized){
1055
+ void replace(self_t *self_p, const T *serialized){
1017
1056
  if(!MatrixUtil::replace(*$self, serialized)){
1018
1057
  throw std::runtime_error("Unsupported replacement");
1019
1058
  }
1020
- return *$self;
1021
1059
  }
1022
1060
 
1023
1061
  #ifdef SWIGRUBY
1024
- %bang swapRows(const unsigned int &, const unsigned int &);
1025
- %bang swapColumns(const unsigned int &, const unsigned int &);
1062
+ %bang swap_rows;
1063
+ %bang swap_columns;
1026
1064
  %rename("replace!") replace;
1027
1065
 
1028
- self_t &map_bang(
1066
+ void map_bang(
1067
+ self_t *self_p,
1029
1068
  void (*each_func)(
1030
1069
  const T &src, T *dst,
1031
1070
  const unsigned int &i, const unsigned int &j),
1032
1071
  const typename MatrixUtil::each_which_t &each_which = MatrixUtil::EACH_ALL){
1033
1072
  MatrixUtil::each(*$self, each_func, each_which, $self);
1034
- return *$self;
1035
1073
  }
1036
1074
  %rename("map!") map_bang;
1037
1075
  %alias map_bang "collect!,map_with_index!,collect_with_index!";
@@ -1147,6 +1185,8 @@ INSTANTIATE_MATRIX_EIGEN2(type, ctype, Array2D_Dense<type >, MatView_pt);
1147
1185
  #endif
1148
1186
 
1149
1187
  %define INSTANTIATE_MATRIX(type, suffix)
1188
+ %typemap(check, fragment="check_value"{unsigned int})
1189
+ const unsigned int & "raise_if_lt_zero_after_asval(*$1);"
1150
1190
  #if !defined(DO_NOT_INSTANTIATE_SCALAR_MATRIX)
1151
1191
  %extend Matrix_Frozen<type, Array2D_ScaledUnit<type >, MatViewBase> {
1152
1192
  const Matrix_Frozen<type, Array2D_ScaledUnit<type >, MatViewBase> &transpose() const {
@@ -1200,6 +1240,35 @@ INSTANTIATE_MATRIX_PARTIAL(type, Array2D_Dense<type >, MatView_pt, MatView_pt);
1200
1240
  %template(Matrix_Frozen ## suffix ## _pt) Matrix_Frozen<type, Array2D_Dense<type >, MatView_pt>;
1201
1241
  #endif
1202
1242
 
1243
+ %extend Matrix<type, Array2D_Dense<type > > {
1244
+ #if defined(SWIGRUBY)
1245
+ %bang resize;
1246
+ #endif
1247
+ %typemap(in, fragment="check_value"{unsigned int})
1248
+ unsigned int *r_p (unsigned int temp), unsigned int *c_p (unsigned int temp) {
1249
+ if(SWIG_IsOK(SWIG_AsVal(unsigned int)($input, &temp))){
1250
+ #if defined(SWIGRUBY)
1251
+ raise_if_lt_zero_after_asval(temp);
1252
+ #endif
1253
+ $1 = &temp;
1254
+ }
1255
+ #if defined(SWIGRUBY)
1256
+ else if(NIL_P($input)){$1 = NULL;}
1257
+ #endif
1258
+ else{SWIG_exception(SWIG_TypeError, "$*1_ltype is expected");}
1259
+ }
1260
+ Matrix<type, Array2D_Dense<type > > &resize(
1261
+ const unsigned int *r_p, const unsigned int *c_p){
1262
+ unsigned int r(r_p ? *r_p : $self->rows()), c(c_p ? *c_p : self->columns());
1263
+ Matrix<type, Array2D_Dense<type > > mat_new(r, c);
1264
+ unsigned int r_min(r), c_min(c);
1265
+ if(r_min > $self->rows()){r_min = $self->rows();}
1266
+ if(c_min > $self->columns()){c_min = $self->columns();}
1267
+ mat_new.partial(r_min, c_min).replace($self->partial(r_min, c_min), false);
1268
+ return (*($self) = mat_new);
1269
+ }
1270
+ };
1271
+
1203
1272
  %template(Matrix ## suffix) Matrix<type, Array2D_Dense<type > >;
1204
1273
  #if defined(SWIGRUBY)
1205
1274
  %fragment("init"{Matrix<type, Array2D_Dense<type > >}, "init") {
@@ -1212,6 +1281,7 @@ INSTANTIATE_MATRIX_PARTIAL(type, Array2D_Dense<type >, MatView_pt, MatView_pt);
1212
1281
  }
1213
1282
  %fragment("init"{Matrix<type, Array2D_Dense<type > >});
1214
1283
  #endif
1284
+ %typemap(check) const unsigned int &;
1215
1285
  %enddef
1216
1286
 
1217
1287
  INSTANTIATE_MATRIX(double, D);
@@ -221,7 +221,11 @@ __RINEX_OBS_TEXT__
221
221
  f.path
222
222
  },
223
223
  }}
224
- let(:solver){GPS::Solver::new}
224
+ let(:solver){
225
+ res = GPS::Solver::new
226
+ res.correction = {:gps_ionospheric => :klobuchar, :gps_tropospheric => :hopfield}
227
+ res
228
+ }
225
229
 
226
230
  describe 'demo' do
227
231
  it 'calculates position without any error' do
@@ -249,13 +253,13 @@ __RINEX_OBS_TEXT__
249
253
  t_meas = GPS::Time::new(1849, 172413)
250
254
  puts "Measurement time: #{t_meas.to_a} (a.k.a #{"%d/%d/%d %02d:%02d:%02d UTC"%[*t_meas.c_tm]})"
251
255
  expect(t_meas.c_tm).to eq([2015, 6, 15, 23, 53, 33])
256
+ expect(GPS::Time::new(0, t_meas.serialize)).to eq(t_meas)
252
257
 
253
258
  sn.update_all_ephemeris(t_meas)
254
259
 
255
260
  [:alpha, :beta].each{|k|
256
261
  puts "Iono #{k}: #{sn.iono_utc.send(k)}"
257
262
  }
258
- puts solver.gps_options.ionospheric_models
259
263
 
260
264
  meas.each{|prn, k, v|
261
265
  eph = sn.ephemeris(prn)
@@ -336,6 +340,20 @@ __RINEX_OBS_TEXT__
336
340
 
337
341
  it 'can be modified through hooks' do
338
342
  sn = solver.gps_space_node
343
+ expect(solver.correction[:gps_ionospheric]).to include(:klobuchar)
344
+ expect(solver.correction[:gps_tropospheric]).to include(:hopfield)
345
+ expect{solver.correction = nil}.to raise_error(RuntimeError)
346
+ expect{solver.correction = {
347
+ :gps_ionospheric => [proc{|t, usr_pos, sat_pos|
348
+ expect(t).to be_a_kind_of(GPS::Time)
349
+ expect(usr_pos).to be_a_kind_of(Coordinate::XYZ) unless usr_pos
350
+ expect(sat_pos).to be_a_kind_of(Coordinate::ENU) unless sat_pos
351
+ false
352
+ }, :klobuchar, :no_correction],
353
+ :options => {:f_10_7 => 10},
354
+ }}.not_to raise_error
355
+ expect(solver.correction[:gps_ionospheric]).to include(:no_correction)
356
+ expect(solver.correction[:options][:f_10_7]).to eq(10)
339
357
  sn.read(input[:rinex_nav])
340
358
  t_meas = GPS::Time::new(1849, 172413)
341
359
  sn.update_all_ephemeris(t_meas)
@@ -349,11 +367,13 @@ __RINEX_OBS_TEXT__
349
367
  weight = 1
350
368
  [weight, range_c, range_r, rate_rel_neg] + los_neg
351
369
  }
352
- solver.hooks[:update_position_solution] = proc{|*mats|
353
- mats.each{|mat|
370
+ solver.hooks[:update_position_solution] = proc{|mat_G, mat_W, mat_delta_r, temp_pvt|
371
+ expect(temp_pvt).to be_a_kind_of(GPS::PVT)
372
+ [mat_G, mat_W, mat_delta_r].each{|mat|
354
373
  expect(mat).to be_a_kind_of(SylphideMath::MatrixD)
374
+ expect(mat.rows).to be >= temp_pvt.used_satellites
375
+ expect(mat).to respond_to(:resize!)
355
376
  }
356
- mat_G, mat_W, mat_delta_r = mats
357
377
  }
358
378
  solver.hooks[:satellite_position] = proc{
359
379
  i = 0
@@ -19,6 +19,12 @@ shared_examples 'Matrix' do
19
19
  expect( mat_type::new(*params[:rc]).rows ).to equal(params[:rc][0])
20
20
  expect( mat_type::new(*params[:rc]).columns ).to equal(params[:rc][1])
21
21
  end
22
+ it 'declines negative number argument' do
23
+ [[-1, 1], [1, -1]].each{|sf|
24
+ r, c = params[:rc].zip(sf).collect{|v1, v2| v1 * v2}
25
+ expect{ mat_type::new(r, c) }.to raise_error(ArgumentError)
26
+ }
27
+ end
22
28
  it 'accepts ([[]])' do
23
29
  expect{ mat_type::new(compare_with) }.not_to raise_error
24
30
  expect( mat_type::new(compare_with).rows ).to equal(params[:rc][0])
@@ -44,9 +50,11 @@ shared_examples 'Matrix' do
44
50
  a.define_singleton_method(:[]){|i, j| raise(IndexError) if i != j; 0}
45
51
  expect{ mat_type::new(a) }.to raise_error(IndexError)
46
52
 
47
- a = a_gen.call
48
- a.define_singleton_method(:row_size){-1}
49
- expect{ mat_type::new(a) }.to raise_error(RuntimeError)
53
+ [:row_size, :column_size].each{|f|
54
+ a = a_gen.call
55
+ a.define_singleton_method(f){-1}
56
+ expect{ mat_type::new(a) }.to raise_error(RuntimeError)
57
+ }
50
58
  end
51
59
  it 'is invoked with I, identity, unit' do
52
60
  [:I, :identity, :unit].each{|f|
@@ -132,7 +140,7 @@ shared_examples 'Matrix' do
132
140
  expect(mat[:square].sum).to eq(Matrix[*mat[:square].to_a].sum)
133
141
  expect(mat[:not_square].sum).to eq(Matrix[*mat[:not_square].to_a].sum)
134
142
  end
135
- it 'determinat, det' do
143
+ it 'determinant, det' do
136
144
  [:determinant, :det].each{|f|
137
145
  #expect(mat[:square].send(f)).to eq(Matrix[*mat[:square].to_a].det)
138
146
  expect{mat[:not_square].send(f)}.to raise_error(RuntimeError)
@@ -163,6 +171,15 @@ shared_examples 'Matrix' do
163
171
  }
164
172
  }
165
173
  end
174
+ it 'is inaccessible and unchangeable with negative number arguments' do
175
+ [[-1, 0], [0, -1]].each{|i, j|
176
+ [[:[], i, j], [:[]=, i, j, 0]].each{|args|
177
+ expect{ mat[0].send(*args) }.to raise_error{|err|
178
+ expect(err).to be_a(RangeError).or be_a(ArgumentError)
179
+ }
180
+ }
181
+ }
182
+ end
166
183
  end
167
184
 
168
185
  describe 'elements' do
@@ -197,9 +214,19 @@ shared_examples 'Matrix' do
197
214
  it 'is swappable with swap_rows! or swap_cloumns!' do
198
215
  mat_builtin = Matrix[*compare_with[0]]
199
216
  [:swap_rows!, :swap_columns!].each.with_index{|func, i|
200
- params[:rc][i].times.to_a.combination(2).to_a{|a, b|
201
- mat[0].send(func, a, b)
202
- mat_builtin.send(func, a, b)
217
+ params[:rc][i].times.to_a.combination(2).to_a.each{|a, b|
218
+ mat_mod = mat[0].send(func, a, b)
219
+ case func
220
+ when :swap_rows!
221
+ idxs = mat_builtin.row_count.times.to_a
222
+ idxs[a], idxs[b] = [b, a]
223
+ mat_builtin = Matrix::rows(mat_builtin.row_vectors.values_at(*idxs))
224
+ when :swap_columns!
225
+ idxs = mat_builtin.column_count.times.to_a
226
+ idxs[a], idxs[b] = [b, a]
227
+ mat_builtin = Matrix::columns(mat_builtin.column_vectors.values_at(*idxs))
228
+ end
229
+ expect(mat_mod).to equal(mat[0])
203
230
  expect(mat[0].to_a).to eq(mat_builtin.to_a)
204
231
  }
205
232
  }
@@ -394,6 +421,23 @@ shared_examples 'Matrix' do
394
421
  }
395
422
  expect{mat[2] / mat[3]}.to raise_error(RuntimeError)
396
423
  end
424
+ it 'have resize!' do
425
+ [-1, :sym].each{|arg|
426
+ [[arg, nil], [nil, arg]].each{|args|
427
+ expect{mat[0].resize!(*args)}.to raise_error{|err|
428
+ expect(err).to be_a(TypeError).or be_a(ArgumentError)
429
+ }
430
+ }
431
+ }
432
+ mat_orig = mat[0].to_a
433
+ r, c = [:rows, :columns].collect{|f| mat[0].send(f)}
434
+ expect(mat[0].resize!(r, c).to_a).to eq(mat_orig)
435
+ expect(mat[0].resize!(r, nil).to_a).to eq(mat_orig)
436
+ expect(mat[0].resize!(nil, c).to_a).to eq(mat_orig)
437
+ expect(mat[0].resize!(nil, nil).to_a).to eq(mat_orig)
438
+ expect(mat[0].resize!(r * 2, c * 2).to_a).to \
439
+ eq(Matrix::build(r * 2, c * 2){|i, j| (i < r && j < c) ? mat_orig[i][j] : 0}.to_a)
440
+ end
397
441
  end
398
442
 
399
443
  describe 'decomposition' do
data/gps_pvt.gemspec ADDED
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/gps_pvt/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "gps_pvt"
7
+ spec.version = GPS_PVT::VERSION
8
+ spec.authors = ["fenrir(M.Naruoka)"]
9
+ spec.email = ["fenrir.naru@gmail.com"]
10
+
11
+ spec.summary = "GPS position, velocity, and time (PVT) solver"
12
+ spec.description = "This module calculate PVT by using raw observation obtained from a GPS receiver"
13
+ spec.homepage = "https://github.com/fenrir-naru/gps_pvt"
14
+ spec.required_ruby_version = ">= 2.3.0"
15
+
16
+ #spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
17
+
18
+ spec.metadata["homepage_uri"] = spec.homepage
19
+ spec.metadata["source_code_uri"] = spec.homepage
20
+ #spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
21
+
22
+ spec.extensions = ["ext/gps_pvt/extconf.rb"]
23
+
24
+ # Specify which files should be added to the gem when it is released.
25
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
26
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
27
+ `git ls-files -z`.split("\x0").reject do |f|
28
+ (f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
29
+ end
30
+ end
31
+ spec.bindir = "exe"
32
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
33
+ spec.require_paths = ["lib"]
34
+
35
+ spec.files += proc{
36
+ require 'pathname'
37
+ base_dir = Pathname::new(File::absolute_path(File.dirname(__FILE__)))
38
+ # get an array of submodule dirs by executing 'pwd' inside each submodule
39
+ `git submodule --quiet foreach pwd`.split($/).collect{|dir|
40
+ # issue git ls-files in submodule's directory
41
+ `git -C #{dir} ls-files -v`.split($/).collect{|f|
42
+ next nil unless f =~ /^H */ # consider git sparse checkout
43
+ # get relative path
44
+ f = Pathname::new(File::join(dir, $'))
45
+ begin
46
+ (f.relative? ? f : f.relative_path_from(base_dir)).to_s
47
+ rescue
48
+ # Patch for Windows drive letter problem
49
+ base_dir = Pathname::new(base_dir.to_s.sub(/^([^\/])+:\//){"/#{$1}/"})
50
+ f.relative_path_from(base_dir).to_s
51
+ end
52
+ }.compact
53
+ }.flatten
54
+ }.call
55
+
56
+ # Uncomment to register a new dependency of your gem
57
+ # spec.add_dependency "example-gem", "~> 1.0"
58
+ spec.add_development_dependency "rake"
59
+ spec.add_development_dependency "rake-compiler"
60
+
61
+ # For more information and examples about making a new gem, checkout our
62
+ # guide at: https://bundler.io/guides/creating_gem.html
63
+ end
@@ -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})"
@@ -73,12 +88,12 @@ class Receiver
73
88
  next ([nil] * 6 * opt[:satellites].size) unless pvt.position_solved?
74
89
  sats = pvt.used_satellite_list
75
90
  r, w = [:delta_r, :W].collect{|f| pvt.send(f)}
76
- opt[:satellites].collect{|i|
77
- next ([nil] * 6) unless i2 = sats.index(i)
91
+ opt[:satellites].collect{|prn, label|
92
+ next ([nil] * 6) unless i2 = sats.index(prn)
78
93
  [r[i2, 0], w[i2, i2]] +
79
94
  [:azimuth, :elevation].collect{|f|
80
- pvt.send(f)[i] / Math::PI * 180
81
- } + [pvt.slopeH[i], pvt.slopeV[i]]
95
+ pvt.send(f)[prn] / Math::PI * 180
96
+ } + [pvt.slopeH[prn], pvt.slopeV[prn]]
82
97
  }.flatten
83
98
  },
84
99
  ]] + [[
@@ -106,16 +121,19 @@ class Receiver
106
121
  opt = {
107
122
  :satellites => (1..32).to_a,
108
123
  }.merge(opt)
124
+ keys = [:PSEUDORANGE, :RANGE_RATE, :DOPPLER, :FREQUENCY].collect{|k|
125
+ GPS::Measurement.const_get("L1_#{k}".to_sym)
126
+ }
109
127
  [[
110
128
  opt[:satellites].collect{|prn, label|
111
129
  [:L1_range, :L1_rate].collect{|str| "#{str}(#{label || prn})"}
112
130
  }.flatten,
113
131
  proc{|meas|
114
- meas_hash = Hash[*(meas.collect{|prn, k, v| [[prn, k], v]}.flatten(1))]
115
- opt[:satellites].collect{|prn|
116
- [:L1_PSEUDORANGE, [:L1_DOPPLER, GPS::SpaceNode.L1_WaveLength]].collect{|k, sf|
117
- meas_hash[[prn, GPS::Measurement.const_get(k)]] * (sf || 1) rescue nil
118
- }
132
+ meas_hash = meas.to_hash
133
+ opt[:satellites].collect{|prn, label|
134
+ pr, rate, doppler, freq = keys.collect{|k| meas_hash[prn][k] rescue nil}
135
+ freq ||= GPS::SpaceNode.L1_Frequency
136
+ [pr, rate || ((doppler * GPS::SpaceNode::light_speed / freq) rescue nil)]
119
137
  }
120
138
  }
121
139
  ]]
@@ -135,6 +153,14 @@ class Receiver
135
153
  rel_prop
136
154
  }
137
155
  @debug = {}
156
+ solver_opts = [:gps_options, :sbas_options].collect{|target|
157
+ @solver.send(target)
158
+ }
159
+ solver_opts.each{|opt|
160
+ # default solver options
161
+ opt.elevation_mask = 0.0 / 180 * Math::PI # 0 deg (use satellite over horizon)
162
+ opt.residual_mask = 1E4 # 10 km (without residual filter, practically)
163
+ }
138
164
  output_options = {
139
165
  :system => [[:GPS, 1..32], [:QZSS, 193..202]],
140
166
  :satellites => (1..32).to_a + (193..202).to_a, # [idx, ...] or [[idx, label], ...] is acceptable
@@ -160,6 +186,13 @@ class Receiver
160
186
  when :identical # same as default
161
187
  next true
162
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
163
196
  when :base_station
164
197
  crd, sys = v.split(/ *, */).collect.with_index{|item, i|
165
198
  case item
@@ -193,9 +226,9 @@ class Receiver
193
226
  sys, svid = case spec
194
227
  when Integer
195
228
  [nil, spec]
196
- when /([a-zA-Z]+)(?::(-?\d+))?/
197
- [$1.upcase.to_sym, (Integre($2) rescue nil)]
198
- when /-?\d+/
229
+ when /^([a-zA-Z]+)(?::(-?\d+))?$/
230
+ [$1.upcase.to_sym, (Integer($2) rescue nil)]
231
+ when /^-?\d+$/
199
232
  [nil, $&.to_i]
200
233
  else
201
234
  next false
@@ -206,27 +239,37 @@ class Receiver
206
239
  else
207
240
  (k == :with) ? :include : :exclude
208
241
  end
209
- if (sys == :GPS) || (sys == :QZSS) \
210
- || (svid && ((1..32).include?(svid) || (193..202).include?(svid))) then
211
- [svid || ((1..32).to_a + (193..202).to_a)].flatten.each{
212
- @solver.gps_options.send(mode, svid)
213
- }
214
- elsif (sys == :SBAS) || (svid && (120..158).include?(svid)) then
215
- prns = [svid || (120..158).to_a].flatten
216
- unless (i = output_options[:system].index{|sys, range| sys == :SBAS}) then
242
+ update_output = proc{|sys_target, prns, labels|
243
+ unless (i = output_options[:system].index{|sys, range| sys == sys_target}) then
217
244
  i = -1
218
- output_options[:system] << [:SBAS, []]
245
+ output_options[:system] << [sys_target, []]
219
246
  else
220
- output_options[:system][i].reject!{|prn| prns.include?(prn)}
247
+ output_options[:system][i][1].reject!{|prn| prns.include?(prn)}
221
248
  end
222
249
  output_options[:satellites].reject!{|prn, label| prns.include?(prn)}
223
250
  if mode == :include then
224
251
  output_options[:system][i][1] += prns
225
- output_options[:satellites] += prns
252
+ output_options[:system][i][1].sort!
253
+ output_options[:satellites] += (labels ? prns.zip(labels) : prns)
254
+ output_options[:satellites].sort!{|a, b| [a].flatten[0] <=> [b].flatten[0]}
226
255
  end
256
+ }
257
+ check_sys_svid = proc{|sys_target, range_in_sys, offset|
258
+ next range_in_sys.include?(svid - (offset || 0)) unless sys # svid is specified without system
259
+ next false unless sys == sys_target
260
+ next true unless svid # All satellites in a target system (svid == nil)
261
+ range_in_sys.include?(svid)
262
+ }
263
+ if check_sys_svid.call(:GPS, 1..32) then
264
+ [svid || (1..32).to_a].flatten.each{|prn| @solver.gps_options.send(mode, prn)}
265
+ elsif check_sys_svid.call(:SBAS, 120..158) then
266
+ prns = [svid || (120..158).to_a].flatten
267
+ update_output.call(:SBAS, prns)
227
268
  prns.each{|prn| @solver.sbas_options.send(mode, prn)}
269
+ elsif check_sys_svid.call(:QZSS, 193..202) then
270
+ [svid || (193..202).to_a].flatten.each{|prn| @solver.gps_options.send(mode, prn)}
228
271
  else
229
- next false
272
+ raise "Unknown satellite: #{spec}"
230
273
  end
231
274
  $stderr.puts "#{mode.capitalize} satellite: #{[sys, svid].compact.join(':')}"
232
275
  }
@@ -235,10 +278,6 @@ class Receiver
235
278
  false
236
279
  }
237
280
  raise "Unknown receiver options: #{options.inspect}" unless options.empty?
238
- proc{|opt|
239
- opt.elevation_mask = 0.0 / 180 * Math::PI # 0 deg
240
- opt.residual_mask = 1E4 # 10 km
241
- }.call(@solver.gps_options)
242
281
  @output = {
243
282
  :pvt => Receiver::pvt_items(output_options),
244
283
  :meas => Receiver::meas_items(output_options),
@@ -316,6 +355,11 @@ class Receiver
316
355
  [:azimuth, :elevation, :slopeH, :slopeV].each{|k|
317
356
  eval("define_method(:#{k}){@#{k} || self.post_solution(:@#{k})}")
318
357
  }
358
+ define_method(:other_state){
359
+ # If a design matrix G has columns larger than 4,
360
+ # other states excluding position and time are estimated.
361
+ (self.G.rows <= 4) ? [] : (self.S * self.delta_r).transpose.to_a[0][4..-1]
362
+ }
319
363
  }
320
364
 
321
365
  proc{
@@ -365,7 +409,7 @@ class Receiver
365
409
  ubx = UBX::new(open(ubx_fname))
366
410
  ubx_kind = Hash::new(0)
367
411
 
368
- after_run = b || proc{|pvt| puts pvt.to_s}
412
+ after_run = b || proc{|pvt| puts pvt.to_s if pvt}
369
413
 
370
414
  gnss_serial = proc{|svid, sys|
371
415
  if sys then # new numbering
@@ -489,7 +533,7 @@ class Receiver
489
533
  end
490
534
 
491
535
  def parse_rinex_obs(fname, &b)
492
- after_run = b || proc{|pvt| puts pvt.to_s}
536
+ after_run = b || proc{|pvt| puts pvt.to_s if pvt}
493
537
  $stderr.print "Reading RINEX observation file (%s)"%[fname]
494
538
  types = nil
495
539
  count = 0
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GPS_PVT
4
- VERSION = "0.2.1"
4
+ VERSION = "0.3.3"
5
5
  end