gps_pvt 0.7.1 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 14551de3c8e0eae8a584044249ff80a04e5c834929ec7968b39839aa611b3050
4
- data.tar.gz: 0c82a31aec65f372d5221d5c6c7ef8e0fa72d6b5fa4d5d6f5d2174ad7a6cfa82
3
+ metadata.gz: b2eb20de6303a24b0e5b134c7c78cd1edb043ff38d823181e596b4626b6bc536
4
+ data.tar.gz: 183ae6e8cfd2577132113bb3684e6e225989ce444d6647820496b13ddd3a4903
5
5
  SHA512:
6
- metadata.gz: fb245345b2d07710c7df3419eefee79720b63dc9820776612d0d5e9270d1f2186f54abd4d92f8e6967457b54f8b3fc5ea2fa3448232780847c6e57b66b42a116
7
- data.tar.gz: a3f6edf6a85ef786305f15788a4e4b28d32287ec9727d2b3f2885ac3653524c264b10f82c6dcd009ee32c8cab956c6e1dcc8c14025495027a80b3d10100e89f0
6
+ metadata.gz: a35228704032066f682e15f5abf6f8a2238d90424e6b50182e07b8fc342a1f4150ba6747ca88a9fcaef163e0ce8149a68edf087e6c0f5525a2c7a09553beb504
7
+ data.tar.gz: 10ace25d753123375bf8ad46cbd4d7490b13219522b39ee5774a1df6d3ba1fd3fd9f318364a19253981f24a3b83998e5df9bb82700b37d6d7f17bd1ad27ad14d
data/README.md CHANGED
@@ -113,7 +113,7 @@ receiver.solver.hooks[:relative_property] = proc{|prn, rel_prop, meas, rcv_e, t_
113
113
  # rcv_e, t_arv, usr_pos, usr_vel are temporary solution of
114
114
  # receiver clock error [m], time of arrival [s], user position and velocity in ECEF, respectively.
115
115
 
116
- weight = 1 # same as default; identical weight for each visible satellite
116
+ weight = 1 # quick example: identical weight for each visible satellite
117
117
  # or weight based on elevation, for example:
118
118
  # elv = GPS_PVT::Coordinate::ENU::relative_rel(GPS_PVT::Coordinate::XYZ::new(*los_neg), usr_pos).elevation
119
119
  # weight = (Math::sin(elv)/0.8)**2
data/exe/gps_pvt CHANGED
@@ -11,7 +11,7 @@ As GPS_file, rinex_nav(*.YYn, *.YYh, *.YYq, *.YYg), rinex_obs(*.YYo), ubx(*.ubx)
11
11
  (YY = last two digit of year)
12
12
  File format is automatically determined based on its extention described in above parentheses.
13
13
  If you want to specify its format manually, command options like --rinex_nav=file_name are available.
14
- Other than --rinex_nav, --rinex_obs, --ubx, --sp3 or --antex are supported.
14
+ Other than --rinex_nav, --rinex_obs, -rinex_clk, --ubx, --sp3 or --antex are supported.
15
15
  Supported RINEX versions are 2 and 3.
16
16
  A file having additional ".gz" or ".Z" extension is recognized as a compressed file.
17
17
  Major URL such as http(s)://... or ftp://... is acceptable as an input file name.
@@ -3948,10 +3948,13 @@ SWIGINTERN double const &GPS_SolverOptions_Common_Sl_double_Sg__get_residual_mas
3948
3948
  static const int prop_items(sizeof(res.values) / sizeof(res.values[0]));
3949
3949
  VALUE hook(rb_hash_lookup(hooks, key));
3950
3950
  if(NIL_P(hook)){break;}
3951
+ double weight((res.prop.range_sigma > 0)
3952
+ ? (1. / std::pow(res.prop.range_sigma, 2)) // weight=1/(sigma^2)
3953
+ : res.prop.range_sigma);
3951
3954
  VALUE values[] = {
3952
3955
  SWIG_From_int (prn), // prn
3953
3956
  rb_ary_new_from_args(prop_items, // relative_property
3954
- swig::from(res.prop.weight),
3957
+ swig::from(weight),
3955
3958
  swig::from(res.prop.range_corrected),
3956
3959
  swig::from(res.prop.range_residual),
3957
3960
  swig::from(res.prop.rate_relative_neg),
@@ -3983,6 +3986,9 @@ SWIGINTERN double const &GPS_SolverOptions_Common_Sl_double_Sg__get_residual_mas
3983
3986
  .append(" @ [").append(std::to_string(i)).append("]"));
3984
3987
  }
3985
3988
  }
3989
+ if(res.values[0] > 0){
3990
+ res.values[0] = std::pow(1. / res.values[0], 0.5); // sigma=(1/weight)^0.5
3991
+ }
3986
3992
  }while(false);
3987
3993
 
3988
3994
  return res.prop;
@@ -814,11 +814,8 @@ if(std::abs(TARGET - t.TARGET) > raw_t::sf[raw_t::SF_ ## TARGET]){break;}
814
814
  static const float_t F_T_table[15]; ///< @see Table 4.4
815
815
 
816
816
  static const float_t F_T_value(const u8_t &F_T_){
817
- if(F_T_ >= (sizeof(F_T_table) / sizeof(F_T_table[0]))){
818
- return -1; // not used
819
- }else{
820
- return F_T_table[F_T_];
821
- }
817
+ return GPS_SpaceNode<float_t>::SatelliteProperties::Ephemeris::URA_meter(
818
+ F_T_, F_T_table);
822
819
  }
823
820
 
824
821
  static const uint_t P1_value(const u8_t &P1_){
@@ -1315,12 +1312,8 @@ typename GLONASS_SpaceNode<FloatT>::u8_t GLONASS_SpaceNode<FloatT>::SatellitePro
1315
1312
  if(F_T <= 0){ // invalid value
1316
1313
  return sizeof(raw_t::F_T_table) / sizeof(raw_t::F_T_table[0]);
1317
1314
  }
1318
- u8_t res(0);
1319
- while(res < (sizeof(raw_t::F_T_table) / sizeof(raw_t::F_T_table[0]))){
1320
- if(F_T <= raw_t::F_T_table[res]){break;}
1321
- ++res;
1322
- }
1323
- return res;
1315
+ return (u8_t)GPS_SpaceNode<float_t>::SatelliteProperties::Ephemeris::URA_index(
1316
+ F_T, raw_t::F_T_table);
1324
1317
  }
1325
1318
 
1326
1319
  template <class FloatT>
@@ -131,11 +131,15 @@ class GLONASS_SinglePositioning : public SolverBaseT {
131
131
  static float_t clock_error_dot(const void *ptr, const gps_time_t &t_tx) {
132
132
  return sat(ptr).clock_error_dot(t_tx);
133
133
  }
134
+ static float_t range_sigma(const void *ptr, const gps_time_t &t_tx) {
135
+ return sat(ptr).ephemeris().F_T;
136
+ }
134
137
  };
135
138
  satellite_t res = {
136
- &(it_sat->second), &(it_sat->second),
139
+ &(it_sat->second), &(it_sat->second), // position, time
137
140
  impl_t::position, impl_t::velocity,
138
- impl_t::clock_error, impl_t::clock_error_dot};
141
+ impl_t::clock_error, impl_t::clock_error_dot,
142
+ &(it_sat->second), impl_t::range_sigma, NULL}; // error model
139
143
  return res;
140
144
  }
141
145
  satellites_t(const space_node_t &sn)
@@ -292,21 +296,29 @@ class GLONASS_SinglePositioning : public SolverBaseT {
292
296
  res.range_residual += range_error.value[range_error_t::IONOSPHERIC];
293
297
  }
294
298
 
295
- // Setup weight
296
- if(std::abs(res.range_residual) > _options.residual_mask){
297
- // If residual is too big, gently exclude it by decreasing its weight.
298
- res.weight = 1E-8;
299
- }else{
299
+ // Setup range standard deviation, whose reciprocal square is used as weight
300
+ res.range_sigma = 1E+4; // sufficiently big value, 1E4 [m]
301
+ do{
302
+ // If residual is too big, gently exclude it.
303
+ if(std::abs(res.range_residual) > _options.residual_mask){break;}
300
304
 
301
305
  float_t elv(relative_pos.elevation());
302
306
  if(elv < _options.elevation_mask){
303
- res.weight = 0; // exclude it when elevation is less than threshold
304
- }else{
305
- // elevation weight based on "GPS���p�v���O���~���O" @see GPS_Solver.h
306
- res.weight = std::pow(sin(elv)/0.8, 2);
307
- if(res.weight < 1E-3){res.weight = 1E-3;}
307
+ res.range_sigma = 0; // exclude it when elevation is less than threshold
308
+ break;
308
309
  }
309
- }
310
+
311
+ res.range_sigma = sat.range_sigma(t_tx);
312
+
313
+ /* elevation weight based on "GPS���p�v���O���~���O"
314
+ * elevation[deg] : 90 53 45 30 15 10 5
315
+ * sf_sigma(k) : 0.80 1.00 1.13 1.60 3.09 4.61 9.18
316
+ * weight(k^-2) : 1.56 1.00 0.78 0.39 0.10 0.05 0.01
317
+ */
318
+ static const float_t max_sf(10);
319
+ static const float_t elv_limit(std::asin(0.8/max_sf)); // limit
320
+ res.range_sigma *= (elv > elv_limit) ? (0.8 / sin(elv)) : max_sf;
321
+ }while(false);
310
322
 
311
323
  res.range_corrected = range;
312
324
 
@@ -990,7 +990,7 @@ static s ## bits ## _t name(const InputT *buf){ \
990
990
 
991
991
  // Subframe.1
992
992
  uint_t WN; ///< Week number
993
- int_t URA; ///< User range accuracy (index)
993
+ float_t URA; ///< User range accuracy (m)
994
994
  uint_t SV_health; ///< Health status
995
995
  int_t iodc; ///< Issue of clock data
996
996
  float_t t_GD; ///< Group delay (s)
@@ -1061,22 +1061,29 @@ static s ## bits ## _t name(const InputT *buf){ \
1061
1061
  return !((delta_t >= 0) && (delta_t < transmission_interval));
1062
1062
  }
1063
1063
 
1064
- static const float_t URA_limits[];
1065
- static const int URA_MAX_INDEX;
1064
+ static const float_t URA_limits[15];
1066
1065
 
1067
- static float_t URA_meter(const int_t &index){
1066
+ template <std::size_t N>
1067
+ static float_t URA_meter(const int_t &index, const float_t (&table)[N]){
1068
1068
  if(index < 0){return -1;}
1069
- return (index < URA_MAX_INDEX)
1070
- ? URA_limits[index]
1071
- : URA_limits[URA_MAX_INDEX - 1] * 2;
1069
+ return (index < N)
1070
+ ? table[index]
1071
+ : table[N - 1] * 2;
1072
+ }
1073
+ inline static float_t URA_meter(const int_t &index){
1074
+ return URA_meter(index, URA_limits);
1072
1075
  }
1073
1076
 
1074
- static int_t URA_index(const float_t &meter){
1077
+ template <std::size_t N>
1078
+ static int_t URA_index(const float_t &meter, const float_t (&table)[N]){
1075
1079
  if(meter < 0){return -1;}
1076
- for(int i(0); i < URA_MAX_INDEX; ++i){
1077
- if(meter <= URA_limits[i]){return i;}
1080
+ for(std::size_t i(0); i < N; ++i){
1081
+ if(meter <= table[i]){return i;}
1078
1082
  }
1079
- return URA_MAX_INDEX;
1083
+ return N;
1084
+ }
1085
+ inline static int_t URA_index(const float_t &meter){
1086
+ return URA_index(meter, URA_limits);
1080
1087
  }
1081
1088
 
1082
1089
  float_t eccentric_anomaly(const float_t &period_from_toe) const {
@@ -1399,7 +1406,7 @@ static s ## bits ## _t name(const InputT *buf){ \
1399
1406
  converted.svid = svid;
1400
1407
 
1401
1408
  converted.WN = WN;
1402
- converted.URA = URA;
1409
+ converted.URA = URA_meter(URA);
1403
1410
  converted.SV_health = SV_health;
1404
1411
  converted.iodc = iodc;
1405
1412
  CONVERT(t_GD);
@@ -1438,7 +1445,7 @@ static s ## bits ## _t name(const InputT *buf){ \
1438
1445
  svid = eph.svid;
1439
1446
 
1440
1447
  WN = eph.WN;
1441
- URA = eph.URA;
1448
+ URA = URA_index(eph.URA);
1442
1449
  SV_health = eph.SV_health;
1443
1450
  iodc = eph.iodc;
1444
1451
  CONVERT(t_GD);
@@ -1475,7 +1482,7 @@ static s ## bits ## _t name(const InputT *buf){ \
1475
1482
  bool is_equivalent(const Ephemeris &eph) const {
1476
1483
  do{
1477
1484
  if(WN != eph.WN){break;}
1478
- if(URA != eph.URA){break;}
1485
+ if(URA_index(URA) != URA_index(eph.URA)){break;}
1479
1486
  if(SV_health != eph.SV_health){break;}
1480
1487
 
1481
1488
  #define CHECK(TARGET) \
@@ -2478,10 +2485,6 @@ const typename GPS_SpaceNode<FloatT>::float_t GPS_SpaceNode<FloatT>::SatellitePr
2478
2485
  6144.00,
2479
2486
  };
2480
2487
 
2481
- template <class FloatT>
2482
- const int GPS_SpaceNode<FloatT>::SatelliteProperties::Ephemeris::URA_MAX_INDEX
2483
- = sizeof(URA_limits) / sizeof(URA_limits[0]);
2484
-
2485
2488
  #ifdef POW2_ALREADY_DEFINED
2486
2489
  #undef POW2_ALREADY_DEFINED
2487
2490
  #else
@@ -155,11 +155,15 @@ class GPS_SinglePositioning : public SolverBaseT {
155
155
  static float_t clock_error_dot(const void *ptr, const gps_time_t &t_tx) {
156
156
  return sat(ptr).clock_error_dot(t_tx);
157
157
  }
158
+ static float_t range_sigma(const void *ptr, const gps_time_t &t_tx) {
159
+ return sat(ptr).ephemeris().URA;
160
+ }
158
161
  };
159
162
  satellite_t res = {
160
- &(it_sat->second), &(it_sat->second),
163
+ &(it_sat->second), &(it_sat->second), // position, clock
161
164
  impl_t::position, impl_t::velocity,
162
- impl_t::clock_error, impl_t::clock_error_dot};
165
+ impl_t::clock_error, impl_t::clock_error_dot,
166
+ &(it_sat->second), impl_t::range_sigma, NULL}; // error model
163
167
  return res;
164
168
  }
165
169
  satellites_t(const space_node_t &sn)
@@ -248,7 +252,7 @@ class GPS_SinglePositioning : public SolverBaseT {
248
252
  float_t &los_neg_x;
249
253
  float_t &los_neg_y;
250
254
  float_t &los_neg_z;
251
- float_t &weight;
255
+ float_t &range_sigma;
252
256
  };
253
257
 
254
258
  /**
@@ -258,8 +262,8 @@ class GPS_SinglePositioning : public SolverBaseT {
258
262
  * @param range "corrected" pseudo range subtracted by (temporal solution of) receiver clock error in meter
259
263
  * @param time_arrival time when signal arrive at receiver
260
264
  * @param usr_pos (temporal solution of) user position
261
- * @param residual calculated residual with line of site vector, and weight;
262
- * When weight is equal to or less than zero, the calculated results should not be used.
265
+ * @param residual calculated residual with line of site vector, and pseudorange standard deviation (sigma);
266
+ * When sigma is equal to or less than zero, the calculated results should not be used.
263
267
  * @param error Some correction can be overwritten. If its unknown_flag is zero,
264
268
  * corrections will be skipped as possible. @see range_errors_t
265
269
  * @return (float_t) corrected range just including delay, and excluding receiver/satellite error.
@@ -281,7 +285,8 @@ class GPS_SinglePositioning : public SolverBaseT {
281
285
 
282
286
  // Calculate satellite position
283
287
  float_t dt_transit(range / c);
284
- xyz_t sat_pos(sat.position(time_arrival - dt_transit, dt_transit));
288
+ gps_time_t time_depature(time_arrival - dt_transit);
289
+ xyz_t sat_pos(sat.position(time_depature, dt_transit));
285
290
  float_t geometric_range(usr_pos.xyz.dist(sat_pos));
286
291
 
287
292
  // Calculate residual
@@ -305,25 +310,30 @@ class GPS_SinglePositioning : public SolverBaseT {
305
310
  ? tropospheric_correction(time_arrival, usr_pos, relative_pos)
306
311
  : error.value[range_error_t::TROPOSPHERIC];
307
312
 
308
- // Setup weight
309
- if(std::abs(residual.residual) > _options.residual_mask){
310
- // If residual is too big, gently exclude it by decreasing its weight.
311
- residual.weight = 1E-8;
312
- }else{
313
+ // Setup range standard deviation, whose reciprocal square is used as weight
314
+ residual.range_sigma = 1E+4; // sufficiently big value, 1E4 [m]
315
+ do{
316
+ // If residual is too big, gently exclude it.
317
+ if(std::abs(residual.residual) > _options.residual_mask){break;}
313
318
 
314
319
  float_t elv(relative_pos.elevation());
315
320
  if(elv < _options.elevation_mask){
316
- residual.weight = 0; // exclude it when elevation is less than threshold
317
- }else{
318
- /* elevation weight based on "GPS���p�v���O���~���O"
319
- * elevation[deg] : 90 53 45 30 15 10 5
320
- * sigma(s) : 0.80 1.00 1.13 1.60 3.09 4.61 9.18
321
- * weight(s^-2) : 1.56 1.00 0.78 0.39 0.10 0.05 0.01
322
- */
323
- residual.weight = std::pow(sin(elv)/0.8, 2); // weight=1 @ elv=53[deg]
324
- if(residual.weight < 1E-3){residual.weight = 1E-3;}
321
+ residual.range_sigma = 0; // exclude it when elevation is less than threshold
322
+ break;
325
323
  }
326
- }
324
+
325
+ residual.range_sigma = sat.range_sigma(time_depature);
326
+
327
+ /* elevation weight based on "GPS���p�v���O���~���O"
328
+ * elevation[deg] : 90 53 45 30 15 10 5
329
+ * sf_sigma(k) : 0.80 1.00 1.13 1.60 3.09 4.61 9.18
330
+ * weight(k^-2) : 1.56 1.00 0.78 0.39 0.10 0.05 0.01
331
+ */
332
+ static const float_t max_sf(10);
333
+ static const float_t elv_limit(std::asin(0.8/max_sf)); // limit
334
+ residual.range_sigma *= (elv > elv_limit) ? (0.8 / sin(elv)) : max_sf;
335
+ }while(false);
336
+
327
337
 
328
338
  return range;
329
339
  }
@@ -396,16 +406,16 @@ class GPS_SinglePositioning : public SolverBaseT {
396
406
  float_t range;
397
407
  range_error_t range_error;
398
408
  if(!this->range(measurement, range, &range_error)){
399
- return res; // If no range entry, return with weight = 0
409
+ return res; // If no range entry, return with range_sigma = 0
400
410
  }
401
411
 
402
412
  satellite_t sat(select_satellite(prn, time_arrival));
403
- if(!sat.is_available()){return res;} // If satellite is unavailable, return with weight = 0
413
+ if(!sat.is_available()){return res;} // If satellite is unavailable, return with range_sigma = 0
404
414
 
405
415
  residual_t residual = {
406
416
  res.range_residual,
407
417
  res.los_neg[0], res.los_neg[1], res.los_neg[2],
408
- res.weight,
418
+ res.range_sigma,
409
419
  };
410
420
 
411
421
  res.range_corrected = range_corrected(
@@ -414,8 +424,15 @@ class GPS_SinglePositioning : public SolverBaseT {
414
424
  res.rate_relative_neg = rate_relative_neg(sat, res.range_corrected, time_arrival, usr_vel,
415
425
  res.los_neg[0], res.los_neg[1], res.los_neg[2]);
416
426
 
417
- return res;
427
+ #if 0
428
+ // TODO consider case when standard deviation of pseudorange measurement is provided by receiver
429
+ if(!this->range_sigma(measurement, res.range_sigma)){
430
+ // If receiver's range variance is not provided
431
+ res.range_sigma = 1E0; // TODO range error variance [m]
432
+ }
433
+ #endif
418
434
 
435
+ return res;
419
436
  }
420
437
 
421
438
  /**
@@ -232,6 +232,9 @@ struct GPS_Solver_Base {
232
232
  xyz_t (*impl_velocity)(const void *, const gps_time_t &, const float_t &);
233
233
  float_t (*impl_clock_error)(const void *, const gps_time_t &);
234
234
  float_t (*impl_clock_error_dot)(const void *, const gps_time_t &);
235
+ const void *impl_error; ///< pointer to actual implementation of error model
236
+ float_t (*impl_range_sigma)(const void *, const gps_time_t &);
237
+ float_t (*impl_rate_sigma)(const void *, const gps_time_t &);
235
238
  inline bool is_available() const {
236
239
  return (impl_xyz != NULL) && (impl_t != NULL);
237
240
  }
@@ -267,6 +270,26 @@ struct GPS_Solver_Base {
267
270
  inline float_t clock_error_dot(const gps_time_t &t_tx) const {
268
271
  return impl_clock_error_dot(impl_t, t_tx);
269
272
  }
273
+ /**
274
+ * Return expected user range accuracy (URA) in standard deviation (1-sigma)
275
+ * at the transmission time.
276
+ * @param t_tx transmission time
277
+ */
278
+ inline float_t range_sigma(const gps_time_t &t_tx) const {
279
+ return impl_range_sigma
280
+ ? impl_range_sigma(impl_error, t_tx)
281
+ : 3.5; // 7.0 [m] of 95% (2-sigma) URE in Sec. 3.4.1 of April 2020 GPS SPS PS;
282
+ }
283
+ /**
284
+ * Return expected user range rate accuracy (URRA) in standard deviation (1-sigma)
285
+ * at the transmission time.
286
+ * @param t_tx transmission time
287
+ */
288
+ inline float_t rate_sigma(const gps_time_t &t_tx) const {
289
+ return impl_rate_sigma
290
+ ? impl_rate_sigma(impl_error, t_tx)
291
+ : 0.003; // 0.006 [m/s] of 95% (2-sigma) URRE in Sec. 3.4.2 of April 2020 GPS SPS PS
292
+ }
270
293
  static const satellite_t &unavailable() {
271
294
  struct impl_t {
272
295
  static xyz_t v3(const void *, const gps_time_t &, const float_t &){
@@ -276,7 +299,9 @@ struct GPS_Solver_Base {
276
299
  return float_t(0);
277
300
  }
278
301
  };
279
- static const satellite_t res = {NULL, NULL, impl_t::v3, impl_t::v3, impl_t::v, impl_t::v};
302
+ static const satellite_t res = {
303
+ NULL, NULL, impl_t::v3, impl_t::v3, impl_t::v, impl_t::v,
304
+ NULL, NULL, NULL};
280
305
  return res;
281
306
  }
282
307
  };
@@ -359,7 +384,7 @@ struct GPS_Solver_Base {
359
384
  }
360
385
 
361
386
  struct relative_property_t {
362
- float_t weight; ///< How useful this information is. square value is required; thus, only positive value activates the other values.
387
+ float_t range_sigma; ///< Standard deviation of pseudorange; If zero or negative, other values are invalid.
363
388
  float_t range_corrected; ///< corrected range just including delay, and excluding receiver/satellite error
364
389
  float_t range_residual; ///< residual range
365
390
  float_t rate_relative_neg; /// relative rate
@@ -514,6 +539,9 @@ protected:
514
539
  * @param G_ original design matrix
515
540
  * @param rotation_matrix 3 by 3 rotation matrix
516
541
  * @return transformed design matrix G'
542
+ * @see Eq.(3) and (10) in https://gssc.esa.int/navipedia/index.php/Positioning_Error,
543
+ * however, be careful that their R is used as both range error covariance in Eq.(1)
544
+ * and rotation matrix in Eq.(3).
517
545
  */
518
546
  static matrix_t rotate_G(const MatrixT &G_, const matrix_t &rotation_matrix){
519
547
  unsigned int r(G_.rows()), c(G_.columns()); // Normally c=4
@@ -529,17 +557,15 @@ protected:
529
557
  }
530
558
 
531
559
  /**
532
- * Calculate C matrix, which is required to obtain DOP
533
- * C = G^t * W * G
534
- *
560
+ * Calculate C matrix, where C = G^t * G to be used for DOP calculation
535
561
  * @return C matrix
536
562
  */
537
563
  matrix_t C() const {
538
- return (G.transpose() * W * G).inverse();
564
+ return (G.transpose() * G).inverse();
539
565
  }
540
566
  /**
541
- * Transform coordinate of matrix C, which will be used to calculate HDOP/VDOP
542
- * C' = (G * R^t)^t W * (G * R^t) = R * G^t * W * G * R^t = R * C * R^t,
567
+ * Transform coordinate of matrix C
568
+ * C' = (G * R^t)^t * (G * R^t) = R * G^t * G * R^t = R * C * R^t,
543
569
  * where R is a rotation matrix, for example, ECEF to ENU.
544
570
  *
545
571
  * @param rotation_matrix 3 by 3 rotation matrix
@@ -804,14 +830,14 @@ protected:
804
830
  res.receiver_error, time_arrival,
805
831
  res.user_position, zero));
806
832
 
807
- if(prop.weight <= 0){
833
+ if(prop.range_sigma <= 0){
808
834
  continue; // intentionally excluded satellite
809
835
  }else{
810
836
  res.used_satellite_mask.set(it->prn);
811
837
  }
812
838
 
813
839
  if(coarse_estimation){
814
- prop.weight = 1;
840
+ prop.range_sigma = 1;
815
841
  }else{
816
842
  idx_rate_rel.push_back(std::make_pair(i_measurement, prop.rate_relative_neg));
817
843
  }
@@ -820,7 +846,7 @@ protected:
820
846
  geomat.G(i_row, 0) = prop.los_neg[0];
821
847
  geomat.G(i_row, 1) = prop.los_neg[1];
822
848
  geomat.G(i_row, 2) = prop.los_neg[2];
823
- geomat.W(i_row, i_row) = prop.weight;
849
+ geomat.W(i_row, i_row) = 1. / std::pow(prop.range_sigma, 2);
824
850
 
825
851
  ++i_row;
826
852
  }
@@ -353,7 +353,6 @@ struct RINEX_NAV {
353
353
  int t_oc_year4, t_oc_year2, t_oc_mon12;
354
354
  FloatT t_oc_sec;
355
355
  FloatT t_oe_WN;
356
- FloatT ura_meter;
357
356
  FloatT t_ot; ///< Transmitting time [s]
358
357
  FloatT fit_interval_hr;
359
358
  FloatT dummy;
@@ -367,7 +366,6 @@ struct RINEX_NAV {
367
366
  t_oc_mon12(t_oc_tm.tm_mon + 1),
368
367
  t_oc_sec(std::fmod(eph.t_oc, 60)),
369
368
  t_oe_WN(eph.WN),
370
- ura_meter(ephemeris_t::URA_meter(eph.URA)),
371
369
  t_ot(0), // TODO
372
370
  fit_interval_hr(eph.fit_interval / (60 * 60)),
373
371
  dummy(0) {
@@ -378,8 +376,6 @@ struct RINEX_NAV {
378
376
  eph.WN = t_oc.week;
379
377
  eph.t_oc = t_oc.seconds;
380
378
 
381
- eph.URA = ephemeris_t::URA_index(ura_meter); // meter to index
382
-
383
379
  /* @see ftp://igs.org/pub/data/format/rinex210.txt
384
380
  * 6.7 Satellite Health
385
381
  * RINEX Value: 0 Health OK
@@ -1434,14 +1430,14 @@ const typename RINEX_NAV_Reader<FloatT>::convert_item_t RINEX_NAV_Reader<FloatT>
1434
1430
 
1435
1431
  template <class FloatT>
1436
1432
  const typename RINEX_NAV_Reader<FloatT>::convert_item_t RINEX_NAV_Reader<FloatT>::eph6_v2[] = {
1437
- GEN_E ( 3, 19, 12, message_t, ura_meter),
1433
+ GEN_E ( 3, 19, 12, message_t, eph.URA),
1438
1434
  GEN_E2(22, 19, 12, message_t, eph.SV_health, unsigned int),
1439
1435
  GEN_E (41, 19, 12, message_t, eph.t_GD),
1440
1436
  GEN_E2(60, 19, 12, message_t, eph.iodc, int),
1441
1437
  };
1442
1438
  template <class FloatT>
1443
1439
  const typename RINEX_NAV_Reader<FloatT>::convert_item_t RINEX_NAV_Reader<FloatT>::eph6_v3[] = {
1444
- GEN_E ( 4, 19, 12, message_t, ura_meter),
1440
+ GEN_E ( 4, 19, 12, message_t, eph.URA),
1445
1441
  GEN_E2(23, 19, 12, message_t, eph.SV_health, unsigned int),
1446
1442
  GEN_E (42, 19, 12, message_t, eph.t_GD),
1447
1443
  GEN_E2(61, 19, 12, message_t, eph.iodc, int),
@@ -1711,7 +1711,7 @@ sf[SF_ ## TARGET] * msg_t::TARGET(buf)
1711
1711
  uint_t WN; ///< Week number
1712
1712
 
1713
1713
  float_t t_0; ///< Time of applicability (s) <= time of a week
1714
- int_t URA; ///< User range accuracy (index)
1714
+ float_t URA; ///< User range accuracy (m)
1715
1715
  float_t x, y, z; ///< ECEF position (m)
1716
1716
  float_t dx, dy, dz; ///< ECEF velocity (m/s)
1717
1717
  float_t ddx, ddy, ddz; ///< ECEF acceleration (m/s^2)
@@ -1804,6 +1804,15 @@ sf[SF_ ## TARGET] * msg_t::TARGET(buf)
1804
1804
  return res;
1805
1805
  }
1806
1806
 
1807
+ static const float_t URA_table[15];
1808
+
1809
+ inline static float_t URA_meter(const int_t &index){
1810
+ return gps_space_node_t::SatelliteProperties::Ephemeris::URA_meter(index, URA_table);
1811
+ }
1812
+ inline static int_t URA_index(const float_t &meter){
1813
+ return gps_space_node_t::SatelliteProperties::Ephemeris::URA_index(meter, URA_table);
1814
+ }
1815
+
1807
1816
  struct raw_t {
1808
1817
  u8_t svid; ///< Satellite number
1809
1818
 
@@ -1850,7 +1859,7 @@ sf[SF_ ## TARGET] * msg_t::TARGET(buf)
1850
1859
  converted.svid = svid;
1851
1860
  converted.WN = 0; // Week number (must be configured later) @see adjust_time
1852
1861
 
1853
- converted.URA = URA;
1862
+ converted.URA = URA_meter(URA);
1854
1863
  CONVERT(t_0); // Time of a day => time of a week (must be configured later) @see adjust_time
1855
1864
  CONVERT2(x, xy); CONVERT2(y, xy); CONVERT(z);
1856
1865
  CONVERT2(dx, dxy); CONVERT2(dy, dxy); CONVERT(dz);
@@ -1869,7 +1878,7 @@ sf[SF_ ## TARGET] * msg_t::TARGET(buf)
1869
1878
  #define CONVERT(TARGET) CONVERT2(TARGET, TARGET)
1870
1879
  svid = eph.svid;
1871
1880
 
1872
- URA = eph.URA;
1881
+ URA = URA_index(eph.URA);
1873
1882
  CONVERT3(t_0, std::fmod(t_0, gps_time_t::seconds_day), t_0);
1874
1883
  CONVERT2(x, xy); CONVERT2(y, xy); CONVERT(z);
1875
1884
  CONVERT2(dx, dxy); CONVERT2(dy, dxy); CONVERT(dz);
@@ -2350,4 +2359,23 @@ const typename SBAS_SpaceNode<FloatT>::float_t SBAS_SpaceNode<FloatT>::UTC_Param
2350
2359
 
2351
2360
  #undef POWER_2
2352
2361
 
2362
+ template <class FloatT>
2363
+ const typename SBAS_SpaceNode<FloatT>::float_t SBAS_SpaceNode<FloatT>::SatelliteProperties::Ephemeris::URA_table[] = {
2364
+ 2,
2365
+ 2.8,
2366
+ 4,
2367
+ 5.7,
2368
+ 8,
2369
+ 11.3,
2370
+ 16,
2371
+ 32,
2372
+ 64,
2373
+ 128,
2374
+ 256,
2375
+ 512,
2376
+ 1024,
2377
+ 2048,
2378
+ 4096,
2379
+ };
2380
+
2353
2381
  #endif /* __SBAS_H__ */
@@ -126,11 +126,15 @@ class SBAS_SinglePositioning : public SolverBaseT {
126
126
  static float_t clock_error_dot(const void *ptr, const gps_time_t &t_tx) {
127
127
  return sat(ptr).ephemeris().clock_error_dot(t_tx);
128
128
  }
129
+ static float_t range_sigma(const void *ptr, const gps_time_t &t_tx) {
130
+ return sat(ptr).ephemeris().URA;
131
+ }
129
132
  };
130
133
  satellite_t res = {
131
- &(it_sat->second), &(it_sat->second),
134
+ &(it_sat->second), &(it_sat->second), // position, time
132
135
  impl_t::position, impl_t::velocity,
133
- impl_t::clock_error, impl_t::clock_error_dot};
136
+ impl_t::clock_error, impl_t::clock_error_dot,
137
+ &(it_sat->second), impl_t::range_sigma, NULL}; // error model
134
138
  return res;
135
139
  }
136
140
  satellites_t(const space_node_t &sn)
@@ -245,7 +249,7 @@ class SBAS_SinglePositioning : public SolverBaseT {
245
249
  float_t range;
246
250
  range_error_t range_error;
247
251
  if(!this->range(measurement, range, &range_error)){
248
- return res; // If no range entry, return with weight = 0
252
+ return res; // If no range entry, return with sigma = 0
249
253
  }
250
254
 
251
255
  satellite_t sat(select_satellite(prn, time_arrival));
@@ -297,21 +301,29 @@ class SBAS_SinglePositioning : public SolverBaseT {
297
301
 
298
302
  // TODO Fast corrections (2.1.1.4.12)
299
303
 
300
- // TODO Setup weight
301
- if(std::abs(res.range_residual) > _options.residual_mask){
302
- // If residual is too big, gently exclude it by decreasing its weight.
303
- res.weight = 1E-8;
304
- }else{
304
+ // Setup weight
305
+ res.range_sigma = 1E+4; // sufficiently big value, 1E4 [m]
306
+ do{
307
+ // If residual is too big, gently exclude it.
308
+ if(std::abs(res.range_residual) > _options.residual_mask){break;}
305
309
 
306
310
  float_t elv(relative_pos.elevation());
307
311
  if(elv < _options.elevation_mask){
308
- res.weight = 0; // exclude it when elevation is less than threshold
309
- }else{
310
- // elevation weight based on "GPS���p�v���O���~���O" @see GPS_Solver.h
311
- res.weight = std::pow(sin(elv)/0.8, 2);
312
- if(res.weight < 1E-3){res.weight = 1E-3;}
312
+ res.range_sigma = 0; // exclude it when elevation is less than threshold
313
+ break;
313
314
  }
314
- }
315
+
316
+ res.range_sigma = sat.range_sigma(t_tx);
317
+
318
+ /* elevation weight based on "GPS���p�v���O���~���O"
319
+ * elevation[deg] : 90 53 45 30 15 10 5
320
+ * sf_sigma(k) : 0.80 1.00 1.13 1.60 3.09 4.61 9.18
321
+ * weight(k^-2) : 1.56 1.00 0.78 0.39 0.10 0.05 0.01
322
+ */
323
+ static const float_t max_sf(10);
324
+ static const float_t elv_limit(std::asin(0.8/max_sf)); // limit
325
+ res.range_sigma *= (elv > elv_limit) ? (0.8 / sin(elv)) : max_sf;
326
+ }while(false);
315
327
 
316
328
  res.range_corrected = range;
317
329
 
@@ -185,9 +185,10 @@ struct SP3_Product {
185
185
  }
186
186
  };
187
187
  typename GPS_Solver_Base<FloatT>::satellite_t res = {
188
- this, this,
188
+ this, this, // position, time
189
189
  impl_t::position, impl_t::velocity,
190
- impl_t::clock_error, impl_t::clock_error_dot
190
+ impl_t::clock_error, impl_t::clock_error_dot,
191
+ NULL, NULL, NULL, // TODO error model
191
192
  };
192
193
  return res;
193
194
  }
@@ -1124,10 +1124,13 @@ struct GPS_RangeCorrector
1124
1124
  static const int prop_items(sizeof(res.values) / sizeof(res.values[0]));
1125
1125
  VALUE hook(rb_hash_lookup(hooks, key));
1126
1126
  if(NIL_P(hook)){break;}
1127
+ FloatT weight((res.prop.range_sigma > 0)
1128
+ ? (1. / std::pow(res.prop.range_sigma, 2)) // weight=1/(sigma^2)
1129
+ : res.prop.range_sigma);
1127
1130
  VALUE values[] = {
1128
1131
  SWIG_From(int)(prn), // prn
1129
1132
  rb_ary_new_from_args(prop_items, // relative_property
1130
- swig::from(res.prop.weight),
1133
+ swig::from(weight),
1131
1134
  swig::from(res.prop.range_corrected),
1132
1135
  swig::from(res.prop.range_residual),
1133
1136
  swig::from(res.prop.rate_relative_neg),
@@ -1159,6 +1162,9 @@ struct GPS_RangeCorrector
1159
1162
  .append(" @ [").append(std::to_string(i)).append("]"));
1160
1163
  }
1161
1164
  }
1165
+ if(res.values[0] > 0){
1166
+ res.values[0] = std::pow(1. / res.values[0], 0.5); // sigma=(1/weight)^0.5
1167
+ }
1162
1168
  }while(false);
1163
1169
  #endif
1164
1170
  return res.prop;
@@ -800,20 +800,20 @@ __RINEX_CLK_TEXT__
800
800
  expect(pvt.position_solved?).to be(true)
801
801
  expect(pvt.receiver_time.to_a).to eq([1849, 172413])
802
802
  expect(pvt.llh.to_a).to eq([:lat, :lng, :alt].collect{|k| pvt.llh.send(k)})
803
- expect(pvt.llh.lat / Math::PI * 180).to be_within(1E-9).of(35.6992591268) # latitude
804
- expect(pvt.llh.lng / Math::PI * 180).to be_within(1E-9).of(139.541502292) # longitude
805
- expect(pvt.llh.alt) .to be_within(1E-4).of(104.279402455) # altitude
806
- expect(pvt.receiver_error).to be_within(1E-4).of(1259087.83603)
807
- expect(pvt.gdop).to be_within(1E-10).of(3.83282723293)
808
- expect(pvt.pdop).to be_within(1E-10).of(3.30873220653)
809
- expect(pvt.hdop).to be_within(1E-10).of(2.05428293774)
810
- expect(pvt.vdop).to be_within(1E-10).of(2.59376761222)
811
- expect(pvt.tdop).to be_within(1E-10).of(1.9346461648)
803
+ expect(pvt.llh.lat / Math::PI * 180).to be_within(1E-3).of(35.7) # latitude
804
+ expect(pvt.llh.lng / Math::PI * 180).to be_within(1E-3).of(139.542) # longitude
805
+ expect(pvt.llh.alt) .to be_within(1E-1).of(104.3) # altitude
806
+ expect(pvt.receiver_error).to be_within(1E-1).of(1259087.8)
807
+ expect(pvt.gdop).to be_within(1E-2).of(2.42)
808
+ expect(pvt.pdop).to be_within(1E-2).of(2.17)
809
+ expect(pvt.hdop).to be_within(1E-2).of(1.11)
810
+ expect(pvt.vdop).to be_within(1E-2).of(1.87)
811
+ expect(pvt.tdop).to be_within(1E-2).of(1.08)
812
812
  expect(pvt.velocity.to_a).to eq([:e, :n, :u].collect{|k| pvt.velocity.send(k)})
813
- expect(pvt.velocity.north).to be_within(1E-7).of(-0.839546227836) # north
814
- expect(pvt.velocity.east) .to be_within(1E-7).of(-1.05805616381) # east
815
- expect(pvt.velocity.down) .to be_within(1E-7).of(-0.12355474006) # down
816
- expect(pvt.receiver_error_rate).to be_within(1E-7).of(-1061.92654151)
813
+ expect(pvt.velocity.north).to be_within(1E-2).of(-0.86) # north
814
+ expect(pvt.velocity.east) .to be_within(1E-2).of(-1.10) # east
815
+ expect(pvt.velocity.down) .to be_within(1E-2).of(-0.22) # down
816
+ expect(pvt.receiver_error_rate).to be_within(1E-2).of(-1061.86)
817
817
  expect(pvt.G.rows).to eq(6)
818
818
  expect(pvt.W.rows).to eq(6)
819
819
  expect(pvt.delta_r.rows).to eq(6)
@@ -150,10 +150,6 @@ class Receiver
150
150
 
151
151
  def initialize(options = {})
152
152
  @solver = GPS::Solver::new
153
- @solver.hooks[:relative_property] = proc{|prn, rel_prop, meas, rcv_e, t_arv, usr_pos, usr_vel|
154
- rel_prop[0] = 1 if rel_prop[0] > 0 # weight = 1
155
- rel_prop
156
- }
157
153
  @solver.options = {
158
154
  :skip_exclusion => true, # default is to skip fault exclusion calculation
159
155
  }
@@ -180,7 +176,7 @@ class Receiver
180
176
  next true
181
177
  when :weight
182
178
  case v.to_sym
183
- when :elevation # (same as underneath C++ library)
179
+ when :elevation # (same as underneath C++ library except for ignoring broadcasted/calculated URA)
184
180
  @solver.hooks[:relative_property] = proc{|prn, rel_prop, meas, rcv_e, t_arv, usr_pos, usr_vel|
185
181
  if rel_prop[0] > 0 then
186
182
  elv = Coordinate::ENU::relative_rel(
@@ -190,7 +186,11 @@ class Receiver
190
186
  rel_prop
191
187
  }
192
188
  next true
193
- when :identical # same as default
189
+ when :identical # treat each satellite range having same accuracy
190
+ @solver.hooks[:relative_property] = proc{|prn, rel_prop, meas, rcv_e, t_arv, usr_pos, usr_vel|
191
+ rel_prop[0] = 1 if rel_prop[0] > 0 # weight = 1
192
+ rel_prop
193
+ }
194
194
  next true
195
195
  end
196
196
  when :elevation_mask_deg
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GPS_PVT
4
- VERSION = "0.7.1"
4
+ VERSION = "0.7.2"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gps_pvt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - fenrir(M.Naruoka)
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-08-25 00:00:00.000000000 Z
11
+ date: 2022-09-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake