gps_pvt 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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__ */