gps_pvt 0.1.1

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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/CHANGELOG.md +5 -0
  4. data/CODE_OF_CONDUCT.md +84 -0
  5. data/Gemfile +10 -0
  6. data/README.md +86 -0
  7. data/Rakefile +86 -0
  8. data/bin/console +15 -0
  9. data/bin/setup +8 -0
  10. data/ext/gps_pvt/Coordinate/Coordinate_wrap.cxx +6613 -0
  11. data/ext/gps_pvt/GPS/GPS_wrap.cxx +16019 -0
  12. data/ext/gps_pvt/SylphideMath/SylphideMath_wrap.cxx +21050 -0
  13. data/ext/gps_pvt/extconf.rb +70 -0
  14. data/ext/ninja-scan-light/tool/navigation/EGM.h +2971 -0
  15. data/ext/ninja-scan-light/tool/navigation/GPS.h +2432 -0
  16. data/ext/ninja-scan-light/tool/navigation/GPS_Solver.h +479 -0
  17. data/ext/ninja-scan-light/tool/navigation/GPS_Solver_Base.h +1081 -0
  18. data/ext/ninja-scan-light/tool/navigation/GPS_Solver_MultiFrequency.h +199 -0
  19. data/ext/ninja-scan-light/tool/navigation/GPS_Solver_RAIM.h +210 -0
  20. data/ext/ninja-scan-light/tool/navigation/MagneticField.h +928 -0
  21. data/ext/ninja-scan-light/tool/navigation/NTCM.h +211 -0
  22. data/ext/ninja-scan-light/tool/navigation/RINEX.h +1781 -0
  23. data/ext/ninja-scan-light/tool/navigation/WGS84.h +186 -0
  24. data/ext/ninja-scan-light/tool/navigation/coordinate.h +406 -0
  25. data/ext/ninja-scan-light/tool/param/bit_array.h +145 -0
  26. data/ext/ninja-scan-light/tool/param/complex.h +558 -0
  27. data/ext/ninja-scan-light/tool/param/matrix.h +4049 -0
  28. data/ext/ninja-scan-light/tool/param/matrix_fixed.h +665 -0
  29. data/ext/ninja-scan-light/tool/param/matrix_special.h +562 -0
  30. data/ext/ninja-scan-light/tool/param/quaternion.h +765 -0
  31. data/ext/ninja-scan-light/tool/param/vector3.h +651 -0
  32. data/ext/ninja-scan-light/tool/swig/Coordinate.i +177 -0
  33. data/ext/ninja-scan-light/tool/swig/GPS.i +1102 -0
  34. data/ext/ninja-scan-light/tool/swig/SylphideMath.i +1234 -0
  35. data/ext/ninja-scan-light/tool/swig/extconf.rb +5 -0
  36. data/ext/ninja-scan-light/tool/swig/makefile +53 -0
  37. data/ext/ninja-scan-light/tool/swig/spec/GPS_spec.rb +417 -0
  38. data/ext/ninja-scan-light/tool/swig/spec/SylphideMath_spec.rb +489 -0
  39. data/gps_pvt.gemspec +57 -0
  40. data/lib/gps_pvt/receiver.rb +375 -0
  41. data/lib/gps_pvt/ubx.rb +148 -0
  42. data/lib/gps_pvt/version.rb +5 -0
  43. data/lib/gps_pvt.rb +9 -0
  44. data/sig/gps_pvt.rbs +4 -0
  45. metadata +117 -0
@@ -0,0 +1,479 @@
1
+ /**
2
+ * @file GPS solver
3
+ * - Mainly, single positioning
4
+ *
5
+ */
6
+
7
+ /*
8
+ * Copyright (c) 2016, M.Naruoka (fenrir)
9
+ * All rights reserved.
10
+ *
11
+ * Redistribution and use in source and binary forms, with or without modification,
12
+ * are permitted provided that the following conditions are met:
13
+ *
14
+ * - Redistributions of source code must retain the above copyright notice,
15
+ * this list of conditions and the following disclaimer.
16
+ * - Redistributions in binary form must reproduce the above copyright notice,
17
+ * this list of conditions and the following disclaimer in the documentation
18
+ * and/or other materials provided with the distribution.
19
+ * - Neither the name of the naruoka.org nor the names of its contributors
20
+ * may be used to endorse or promote products derived from this software
21
+ * without specific prior written permission.
22
+ *
23
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
25
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
27
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
28
+ * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
29
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
34
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35
+ *
36
+ */
37
+
38
+ #ifndef __GPS_SOLVER_H__
39
+ #define __GPS_SOLVER_H__
40
+
41
+ #include <utility>
42
+ #include <vector>
43
+ #include <exception>
44
+
45
+ #include <cmath>
46
+
47
+ #include "param/bit_array.h"
48
+
49
+ #include "GPS.h"
50
+ #include "GPS_Solver_Base.h"
51
+ #include "NTCM.h"
52
+
53
+ template <class FloatT>
54
+ struct GPS_Solver_GeneralOptions {
55
+ FloatT elevation_mask;
56
+ FloatT residual_mask;
57
+
58
+ enum ionospheric_model_t {
59
+ IONOSPHERIC_KLOBUCHAR,
60
+ IONOSPHERIC_NTCM_GL,
61
+ IONOSPHERIC_NONE, // which allows no correction
62
+ IONOSPHERIC_MODELS,
63
+ IONOSPHERIC_SKIP = IONOSPHERIC_MODELS, // which means delegating the next slot
64
+ };
65
+ ionospheric_model_t ionospheric_models[IONOSPHERIC_MODELS]; // lower index means having higher priority
66
+
67
+ int count_ionospheric_models() const {
68
+ int res(0);
69
+ for(int i(0); i < sizeof(ionospheric_models) / sizeof(ionospheric_models[0]); ++i){
70
+ if(ionospheric_models[i] != IONOSPHERIC_SKIP){res++;}
71
+ }
72
+ return res;
73
+ }
74
+
75
+ FloatT f_10_7;
76
+
77
+ GPS_Solver_GeneralOptions()
78
+ : elevation_mask(0), // elevation mask default is 0 [deg]
79
+ residual_mask(30), // range residual mask is 30 [m]
80
+ f_10_7(-1) {
81
+ for(int i(0); i < sizeof(ionospheric_models) / sizeof(ionospheric_models[0]); ++i){
82
+ ionospheric_models[i] = IONOSPHERIC_SKIP;
83
+ }
84
+ // default: broadcasted Klobuchar parameters are at least required for solution.
85
+ ionospheric_models[0] = IONOSPHERIC_KLOBUCHAR;
86
+ }
87
+
88
+ bool insert_ionospheric_model(const ionospheric_model_t &model, const int &index = 0) {
89
+ if((index < 0)
90
+ || (index >= sizeof(ionospheric_models) / sizeof(ionospheric_models[0]))){
91
+ return false;
92
+ }
93
+
94
+ // shift, then replace
95
+ for(int i(index), j(i + 1); j < sizeof(ionospheric_models) / sizeof(ionospheric_models[0]); ++i, ++j){
96
+ ionospheric_models[j] = ionospheric_models[i];
97
+ }
98
+ ionospheric_models[index] = model;
99
+
100
+ return true;
101
+ }
102
+ };
103
+
104
+ template <class FloatT>
105
+ struct GPS_SinglePositioning_Options : public GPS_Solver_GeneralOptions<FloatT> {
106
+
107
+ // PRN ranges from 1 to 256 (including GPS compatible systems such as QZSS)
108
+ typename GPS_Solver_Base<FloatT>::options_t::template exclude_prn_t<1, 256> exclude_prn;
109
+
110
+ GPS_SinglePositioning_Options()
111
+ : GPS_Solver_GeneralOptions<FloatT>(), exclude_prn() {
112
+
113
+ }
114
+ };
115
+
116
+ template <class FloatT, class SolverBaseT = GPS_Solver_Base<FloatT> >
117
+ class GPS_SinglePositioning : public SolverBaseT {
118
+ private:
119
+ GPS_SinglePositioning<FloatT> &operator=(const GPS_SinglePositioning<FloatT> &);
120
+ public:
121
+ typedef GPS_SinglePositioning<FloatT> self_t;
122
+ typedef SolverBaseT base_t;
123
+
124
+ #if defined(__GNUC__) && (__GNUC__ < 5)
125
+ #define inheritate_type(x) typedef typename base_t::x x;
126
+ #else
127
+ #define inheritate_type(x) using typename base_t::x;
128
+ #endif
129
+
130
+ inheritate_type(float_t);
131
+ inheritate_type(matrix_t);
132
+ inheritate_type(prn_t);
133
+
134
+ typedef typename base_t::space_node_t space_node_t;
135
+ inheritate_type(gps_time_t);
136
+ typedef typename space_node_t::Satellite satellite_t;
137
+
138
+ inheritate_type(xyz_t);
139
+ inheritate_type(llh_t);
140
+ inheritate_type(enu_t);
141
+
142
+ inheritate_type(pos_t);
143
+
144
+ inheritate_type(prn_obs_t);
145
+ typedef typename base_t::measurement_t measurement_t;
146
+ inheritate_type(measurement_items_t);
147
+ typedef typename base_t::range_error_t range_error_t;
148
+
149
+ inheritate_type(relative_property_t);
150
+ inheritate_type(geometric_matrices_t);
151
+ inheritate_type(user_pvt_t);
152
+ #undef inheritate_type
153
+
154
+ typedef typename GPS_Solver_Base<float_t>::options_t::template merge_t<
155
+ GPS_SinglePositioning_Options<float_t>, base_t> options_t;
156
+
157
+ protected:
158
+ const space_node_t &_space_node;
159
+ GPS_SinglePositioning_Options<float_t> _options;
160
+
161
+ public:
162
+ const space_node_t &space_node() const {return _space_node;}
163
+
164
+ /**
165
+ * Check availability of ionospheric models
166
+ * If a model is completely unavailable, it will be replaced to IONOSPHERIC_SKIP.
167
+ * It implies that when a model has conditional applicability (such as SBAS), it will be retained.
168
+ *
169
+ * @return (int) number of (possibly) available models
170
+ */
171
+ int filter_ionospheric_models(GPS_SinglePositioning_Options<float_t> &opt) const {
172
+
173
+ int available_models(0);
174
+
175
+ for(int i(0); i < sizeof(opt.ionospheric_models) / sizeof(opt.ionospheric_models[0]); ++i){
176
+ switch(opt.ionospheric_models[i]){
177
+ case options_t::IONOSPHERIC_KLOBUCHAR:
178
+ // check whether Klobuchar parameters alpha and beta have been already received
179
+ if(_space_node.is_valid_iono()){break;}
180
+ opt.ionospheric_models[i] = options_t::IONOSPHERIC_SKIP;
181
+ continue;
182
+ case options_t::IONOSPHERIC_NTCM_GL:
183
+ if(opt.f_10_7 >= 0){break;}
184
+ // check F10.7 has been already configured
185
+ opt.ionospheric_models[i] = options_t::IONOSPHERIC_SKIP;
186
+ continue;
187
+ case options_t::IONOSPHERIC_SKIP:
188
+ continue;
189
+ }
190
+ available_models++;
191
+ }
192
+
193
+ return available_models;
194
+ }
195
+
196
+ options_t available_options() const {
197
+ return options_t(base_t::available_options(), _options);
198
+ }
199
+
200
+ options_t available_options(const options_t &opt_wish) const {
201
+ GPS_SinglePositioning_Options<float_t> opt(opt_wish);
202
+ filter_ionospheric_models(opt);
203
+ return options_t(base_t::available_options(opt_wish), opt);
204
+ }
205
+
206
+ options_t update_options(const options_t &opt_wish){
207
+ filter_ionospheric_models(_options = opt_wish);
208
+ return options_t(base_t::update_options(opt_wish), _options);
209
+ }
210
+
211
+ GPS_SinglePositioning(const space_node_t &sn)
212
+ : base_t(), _space_node(sn), _options() {
213
+ filter_ionospheric_models(_options);
214
+ }
215
+
216
+ ~GPS_SinglePositioning(){}
217
+
218
+ struct residual_t {
219
+ float_t &residual;
220
+ float_t &los_neg_x;
221
+ float_t &los_neg_y;
222
+ float_t &los_neg_z;
223
+ float_t &weight;
224
+ };
225
+
226
+ /**
227
+ * Get corrected range in accordance with current status
228
+ *
229
+ * @param sat satellite
230
+ * @param range "corrected" pseudo range subtracted by (temporal solution of) receiver clock error in meter
231
+ * @param time_arrival time when signal arrive at receiver
232
+ * @param usr_pos (temporal solution of) user position
233
+ * @param residual calculated residual with line of site vector, and weight;
234
+ * When weight is equal to or less than zero, the calculated results should not be used.
235
+ * @param error Some correction can be overwritten. If its unknown_flag is zero,
236
+ * corrections will be skipped as possible. @see range_errors_t
237
+ * @return (float_t) corrected range just including delay, and excluding receiver/satellite error.
238
+ */
239
+ float_t range_corrected(
240
+ const satellite_t &sat,
241
+ float_t range,
242
+ const gps_time_t &time_arrival,
243
+ const pos_t &usr_pos,
244
+ residual_t &residual,
245
+ const range_error_t &error = range_error_t::not_corrected) const {
246
+
247
+ // Clock error correction
248
+ range += ((error.unknown_flag & range_error_t::SATELLITE_CLOCK)
249
+ ? (sat.clock_error(time_arrival, range) * space_node_t::light_speed)
250
+ : error.value[range_error_t::SATELLITE_CLOCK]);
251
+
252
+ // Calculate satellite position
253
+ xyz_t sat_pos(sat.position(time_arrival, range));
254
+ float_t geometric_range(usr_pos.xyz.dist(sat_pos));
255
+
256
+ // Calculate residual
257
+ residual.residual = range - geometric_range;
258
+
259
+ // Setup design matrix
260
+ residual.los_neg_x = -(sat_pos.x() - usr_pos.xyz.x()) / geometric_range;
261
+ residual.los_neg_y = -(sat_pos.y() - usr_pos.xyz.y()) / geometric_range;
262
+ residual.los_neg_z = -(sat_pos.z() - usr_pos.xyz.z()) / geometric_range;
263
+
264
+ enu_t relative_pos(enu_t::relative(sat_pos, usr_pos.xyz));
265
+
266
+ if(error.unknown_flag & range_error_t::MASK_IONOSPHERIC){
267
+ // Ionospheric model selection, the fall back is no correction
268
+ bool iono_model_hit(false);
269
+ for(int i(0); i < sizeof(_options.ionospheric_models) / sizeof(_options.ionospheric_models[0]); ++i){
270
+ switch(_options.ionospheric_models[i]){
271
+ case options_t::IONOSPHERIC_KLOBUCHAR:
272
+ residual.residual += _space_node.iono_correction(relative_pos, usr_pos.llh, time_arrival);
273
+ break;
274
+ case options_t::IONOSPHERIC_NTCM_GL: {
275
+ // TODO f_10_7 setup, optimization (mag_model etc.)
276
+ typename space_node_t::pierce_point_res_t pp(_space_node.pierce_point(relative_pos, usr_pos.llh));
277
+ residual.residual -= space_node_t::tec2delay(
278
+ _space_node.slant_factor(relative_pos)
279
+ * NTCM_GL_Generic<float_t>::tec_vert(
280
+ pp.latitude, pp.longitude,
281
+ time_arrival.year(), _options.f_10_7));
282
+ break;
283
+ }
284
+ case options_t::IONOSPHERIC_NONE:
285
+ break;
286
+ default:
287
+ continue;
288
+ }
289
+ iono_model_hit = true;
290
+ break;
291
+ }
292
+ }else{
293
+ residual.residual += error.value[range_error_t::IONOSPHERIC];
294
+ }
295
+
296
+ // Tropospheric
297
+ residual.residual += (error.unknown_flag & range_error_t::MASK_TROPOSPHERIC)
298
+ ? _space_node.tropo_correction(relative_pos, usr_pos.llh)
299
+ : error.value[range_error_t::TROPOSPHERIC];
300
+
301
+ // Setup weight
302
+ if(std::abs(residual.residual) > _options.residual_mask){
303
+ // If residual is too big, gently exclude it by decreasing its weight.
304
+ residual.weight = 1E-8;
305
+ }else{
306
+
307
+ float_t elv(relative_pos.elevation());
308
+ if(elv < _options.elevation_mask){
309
+ residual.weight = 0; // exclude it when elevation is less than threshold
310
+ }else{
311
+ /* elevation weight based on "GPS���p�v���O���~���O"
312
+ * elevation[deg] : 90 53 45 30 15 10 5
313
+ * sigma(s) : 0.80 1.00 1.13 1.60 3.09 4.61 9.18
314
+ * weight(s^-2) : 1.56 1.00 0.78 0.39 0.10 0.05 0.01
315
+ */
316
+ residual.weight = std::pow(sin(elv)/0.8, 2); // weight=1 @ elv=53[deg]
317
+ if(residual.weight < 1E-3){residual.weight = 1E-3;}
318
+ }
319
+ }
320
+
321
+ return range;
322
+ }
323
+
324
+ /**
325
+ * Get relative rate (negative polarity) in accordance with current status
326
+ *
327
+ * @param sat satellite
328
+ * @param range "corrected" pseudo range subtracted by (temporal solution of) receiver clock error in meter
329
+ * @param time_arrival time when signal arrive at receiver
330
+ * @param usr_vel (temporal solution of) user velocity
331
+ * @param los_neg_x line of site X
332
+ * @param los_neg_y line of site Y
333
+ * @param los_neg_z line of site Z
334
+ * @return (float_t) relative rate.
335
+ */
336
+ float_t rate_relative_neg(
337
+ const satellite_t &sat,
338
+ const float_t &range,
339
+ const gps_time_t &time_arrival,
340
+ const xyz_t &usr_vel,
341
+ const float_t &los_neg_x, const float_t &los_neg_y, const float_t &los_neg_z) const {
342
+
343
+ xyz_t rel_vel(sat.velocity(time_arrival, range) - usr_vel); // Calculate velocity
344
+ return los_neg_x * rel_vel.x()
345
+ + los_neg_y * rel_vel.y()
346
+ + los_neg_z * rel_vel.z()
347
+ + sat.clock_error_dot(time_arrival, range) * space_node_t::light_speed; // considering clock rate error
348
+ }
349
+
350
+ /**
351
+ * Check availability of a satellite with which observation is associated
352
+ *
353
+ * @param prn satellite number
354
+ * @param receiver_time receiver time
355
+ * @return (const satellite_t *) If available, const pointer to satellite is returned,
356
+ * otherwise NULL.
357
+ */
358
+ const satellite_t *is_available(
359
+ const typename space_node_t::satellites_t::key_type &prn,
360
+ const gps_time_t &receiver_time) const {
361
+
362
+ if(_options.exclude_prn[prn]){return NULL;}
363
+
364
+ const typename space_node_t::satellites_t &sats(_space_node.satellites());
365
+ const typename space_node_t::satellites_t::const_iterator it_sat(sats.find(prn));
366
+ if((it_sat == sats.end()) // has ephemeris?
367
+ || (!it_sat->second.ephemeris().is_valid(receiver_time))){ // valid ephemeris?
368
+ return NULL;
369
+ }
370
+
371
+ return &(it_sat->second);
372
+ }
373
+
374
+ /**
375
+ * Calculate relative range and rate information to a satellite
376
+ *
377
+ * @param prn satellite number
378
+ * @param measurement measurement (per satellite) containing pseudo range
379
+ * @param receiver_error (temporal solution of) receiver clock error in meter
380
+ * @param time_arrival time when signal arrive at receiver
381
+ * @param usr_pos (temporal solution of) user position
382
+ * @param usr_vel (temporal solution of) user velocity
383
+ * @return (relative_property_t) relative information
384
+ */
385
+ relative_property_t relative_property(
386
+ const prn_t &prn,
387
+ const typename measurement_t::mapped_type &measurement,
388
+ const float_t &receiver_error,
389
+ const gps_time_t &time_arrival,
390
+ const pos_t &usr_pos,
391
+ const xyz_t &usr_vel) const {
392
+
393
+ relative_property_t res = {0};
394
+
395
+ float_t range;
396
+ range_error_t range_error;
397
+ if(!this->range(measurement, range, &range_error)){
398
+ return res; // If no range entry, return with weight = 0
399
+ }
400
+
401
+ const satellite_t *sat(is_available(prn, time_arrival));
402
+ if(!sat){return res;} // If satellite is unavailable, return with weight = 0
403
+
404
+ residual_t residual = {
405
+ res.range_residual,
406
+ res.los_neg[0], res.los_neg[1], res.los_neg[2],
407
+ res.weight,
408
+ };
409
+
410
+ res.range_corrected = range_corrected(
411
+ *sat, range - receiver_error, time_arrival,
412
+ usr_pos, residual, range_error);
413
+ res.rate_relative_neg = rate_relative_neg(*sat, res.range_corrected, time_arrival, usr_vel,
414
+ res.los_neg[0], res.los_neg[1], res.los_neg[2]);
415
+
416
+ return res;
417
+
418
+ }
419
+
420
+ /**
421
+ * Calculate User position/velocity with hint
422
+ * This is optimized version for GPS-only constellation
423
+ *
424
+ * @param res (out) calculation results and matrices used for calculation
425
+ * @param measurement PRN, pseudo-range, pseudo-range rate information
426
+ * @param receiver_time receiver time at measurement
427
+ * @param user_position_init initial solution of user position in XYZ meters and LLH
428
+ * @param receiver_error_init initial solution of receiver clock error in meters
429
+ * @param good_init if true, initial position and clock error are goodly guessed.
430
+ * @param with_velocity if true, perform velocity estimation.
431
+ * @see update_ephemeris(), register_ephemeris
432
+ */
433
+ void user_pvt(
434
+ user_pvt_t &res,
435
+ const measurement_t &measurement,
436
+ const gps_time_t &receiver_time,
437
+ const pos_t &user_position_init,
438
+ const float_t &receiver_error_init,
439
+ const bool &good_init = true,
440
+ const bool &with_velocity = true) const {
441
+
442
+ res.receiver_time = receiver_time;
443
+
444
+ if(_options.count_ionospheric_models() == 0){
445
+ res.error_code = user_pvt_t::ERROR_INVALID_IONO_MODEL;
446
+ return;
447
+ }
448
+
449
+ typename base_t::measurement2_t measurement2;
450
+ measurement2.reserve(measurement.size());
451
+ for(typename measurement_t::const_iterator it(measurement.begin()), it_end(measurement.end());
452
+ it != it_end; ++it){
453
+
454
+ float_t range;
455
+ if(!this->range(it->second, range)){continue;} // No range entry
456
+
457
+ if(!is_available(it->first, receiver_time)){continue;} // No satellite
458
+
459
+ typename base_t::measurement2_t::value_type v = {
460
+ it->first, &(it->second), this}; // prn, measurement, solver
461
+ measurement2.push_back(v);
462
+ }
463
+ base_t::user_pvt(
464
+ res,
465
+ measurement2, receiver_time, user_position_init, receiver_error_init,
466
+ typename base_t::user_pvt_opt_t(good_init, with_velocity));
467
+ }
468
+
469
+ xyz_t *satellite_position(
470
+ const prn_t &prn,
471
+ const gps_time_t &time,
472
+ xyz_t &res) const {
473
+
474
+ const satellite_t *sat(is_available(prn, time));
475
+ return sat ? &(res = sat->position(time)) : NULL;
476
+ }
477
+ };
478
+
479
+ #endif /* __GPS_SOLVER_H__ */