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.
- checksums.yaml +4 -4
- data/README.md +33 -5
- data/Rakefile +0 -0
- data/exe/gps_pvt +63 -0
- data/ext/gps_pvt/GPS/GPS_wrap.cxx +784 -745
- data/ext/gps_pvt/SylphideMath/SylphideMath_wrap.cxx +723 -436
- data/ext/ninja-scan-light/tool/navigation/GPS.h +15 -44
- data/ext/ninja-scan-light/tool/navigation/GPS_Solver.h +61 -147
- data/ext/ninja-scan-light/tool/navigation/GPS_Solver_Base.h +56 -2
- data/ext/ninja-scan-light/tool/navigation/SBAS.h +2 -2
- data/ext/ninja-scan-light/tool/navigation/SBAS_Solver.h +55 -78
- data/ext/ninja-scan-light/tool/param/bit_array.h +4 -3
- data/ext/ninja-scan-light/tool/swig/GPS.i +255 -63
- data/ext/ninja-scan-light/tool/swig/SylphideMath.i +91 -21
- data/ext/ninja-scan-light/tool/swig/spec/GPS_spec.rb +25 -5
- data/ext/ninja-scan-light/tool/swig/spec/SylphideMath_spec.rb +51 -7
- data/gps_pvt.gemspec +63 -0
- data/lib/gps_pvt/receiver.rb +84 -40
- data/lib/gps_pvt/version.rb +1 -1
- metadata +7 -6
@@ -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
|
-
|
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
|
968
|
-
|| !SWIG_IsOK(SWIG_AsVal(int)(v_c, &c)) || (c
|
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
|
-
|
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
|
-
|
1001
|
-
|
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
|
-
|
1005
|
-
|
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
|
-
|
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
|
-
|
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
|
1025
|
-
%bang
|
1062
|
+
%bang swap_rows;
|
1063
|
+
%bang swap_columns;
|
1026
1064
|
%rename("replace!") replace;
|
1027
1065
|
|
1028
|
-
|
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){
|
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{
|
353
|
-
|
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
|
-
|
48
|
-
|
49
|
-
|
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 '
|
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
|
-
|
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
|
data/lib/gps_pvt/receiver.rb
CHANGED
@@ -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, :
|
23
|
+
[:week, :itow_rcv, :year, :month, :mday, :hour, :min, :sec_rcv_UTC],
|
17
24
|
proc{|pvt|
|
18
|
-
[:week, :seconds, :
|
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
|
-
|
51
|
-
|
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
|
-
|
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{|
|
77
|
-
next ([nil] * 6) unless i2 = sats.index(
|
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)[
|
81
|
-
} + [pvt.slopeH[
|
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 =
|
115
|
-
opt[:satellites].collect{|prn|
|
116
|
-
|
117
|
-
|
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
|
197
|
-
[$1.upcase.to_sym, (
|
198
|
-
when
|
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
|
-
|
210
|
-
|
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] << [
|
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[:
|
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
|
-
|
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
|
data/lib/gps_pvt/version.rb
CHANGED