gps_pvt 0.2.1 → 0.3.3

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.
@@ -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