]> git.mxchange.org Git - flightgear.git/blob - src/Instrumentation/gps.cxx
Fix un-inited vars found by valgrind.
[flightgear.git] / src / Instrumentation / gps.cxx
1 // gps.cxx - distance-measuring equipment.
2 // Written by David Megginson, started 2003.
3 //
4 // This file is in the Public Domain and comes with no warranty.
5
6 #ifdef HAVE_CONFIG_H
7 #  include <config.h>
8 #endif
9
10 #include "gps.hxx"
11
12 #include <boost/tuple/tuple.hpp>
13
14 #include <memory>
15 #include <set>
16 #include <cstring>
17 #include <cstdio>
18
19 #include "Main/fg_props.hxx"
20 #include "Main/globals.hxx" // for get_subsystem
21 #include "Main/util.hxx" // for fgLowPass
22 #include "Navaids/positioned.hxx"
23 #include <Navaids/waypoint.hxx>
24 #include "Navaids/navrecord.hxx"
25 #include "Airports/airport.hxx"
26 #include "Airports/runways.hxx"
27 #include "Autopilot/route_mgr.hxx"
28
29 #include <simgear/math/sg_random.h>
30 #include <simgear/sg_inlines.h>
31 #include <simgear/math/sg_geodesy.hxx>
32 #include <simgear/structure/exception.hxx>
33
34 using std::auto_ptr;
35 using std::string;
36 using namespace flightgear;
37
38
39 static const char* makeTTWString(double TTW)
40 {
41   if ((TTW <= 0.0) || (TTW >= 356400.5)) { // 99 hours
42     return "--:--:--";
43   }
44       
45   unsigned int TTW_seconds = (int) (TTW + 0.5);
46   unsigned int TTW_minutes = 0;
47   unsigned int TTW_hours   = 0;
48   static char TTW_str[9];
49   TTW_hours   = TTW_seconds / 3600;
50   TTW_minutes = (TTW_seconds / 60) % 60;
51   TTW_seconds = TTW_seconds % 60;
52   snprintf(TTW_str, 9, "%02d:%02d:%02d",
53     TTW_hours, TTW_minutes, TTW_seconds);
54   return TTW_str;
55 }
56
57 /////////////////////////////////////////////////////////////////////////////
58
59 class GPSListener : public SGPropertyChangeListener
60 {
61 public:
62   GPSListener(GPS *m) : 
63     _gps(m),
64     _guard(false) {}
65     
66   virtual void valueChanged (SGPropertyNode * prop)
67   {
68     if (_guard) {
69       return;
70     }
71     
72     _guard = true;
73     if (prop == _gps->_route_current_wp_node) {
74       _gps->routeManagerSequenced();
75     } else if (prop == _gps->_route_active_node) {
76       _gps->routeActivated();
77     } else if (prop == _gps->_ref_navaid_id_node) {
78       _gps->referenceNavaidSet(prop->getStringValue(""));
79     } else if (prop == _gps->_routeEditedSignal) {
80       _gps->routeEdited();
81     } else if (prop == _gps->_routeFinishedSignal) {
82       _gps->routeFinished();
83     }
84         
85     _guard = false;
86   }
87   
88   void setGuard(bool g) {
89     _guard = g;
90   }
91 private:
92   GPS* _gps;
93   bool _guard; // re-entrancy guard
94 };
95
96 ////////////////////////////////////////////////////////////////////////////
97 /**
98  * Helper to monitor for Nasal or other code accessing properties we haven't
99  * defined. For the moment we complain about all such activites, since various
100  * users assume all kinds of weird, wonderful and non-existent interfaces.
101  */
102  
103 class DeprecatedPropListener : public SGPropertyChangeListener
104 {
105 public:
106   DeprecatedPropListener(SGPropertyNode* gps)
107   {
108     _parents.insert(gps);
109     SGPropertyNode* wp = gps->getChild("wp", 0, true); 
110     _parents.insert(wp);
111     _parents.insert(wp->getChild("wp", 0, true));
112     _parents.insert(wp->getChild("wp", 1, true));
113     
114     std::set<SGPropertyNode*>::iterator it;
115     for (it = _parents.begin(); it != _parents.end(); ++it) {
116       (*it)->addChangeListener(this);
117     }
118   }
119   
120   virtual void valueChanged (SGPropertyNode * prop)
121   {
122   }
123   
124   virtual void childAdded (SGPropertyNode * parent, SGPropertyNode * child)
125   {
126     if (isDeprecated(parent, child)) {
127       SG_LOG(SG_INSTR, SG_WARN, "GPS: someone accessed a deprecated property:"
128         << child->getPath(true));
129     }
130   }
131 private:
132   bool isDeprecated(SGPropertyNode * parent, SGPropertyNode * child) const 
133   {
134     if (_parents.count(parent) < 1) {
135       return false;
136     }
137     
138     // no child exclusions yet
139     return true;
140   }
141   
142   std::set<SGPropertyNode*> _parents;
143 };
144
145 ////////////////////////////////////////////////////////////////////////////
146 // configuration helper object
147
148 GPS::Config::Config() :
149   _enableTurnAnticipation(true),
150   _turnRate(3.0), // degrees-per-second, so 180 degree turn takes 60 seconds
151   _overflightArmDistance(1.0),
152   _waypointAlertTime(30.0),
153   _requireHardSurface(true),
154   _cdiMaxDeflectionNm(3.0), // linear mode, 3nm at the peg
155   _driveAutopilot(true),
156   _courseSelectable(false)
157 {
158   _enableTurnAnticipation = false;
159 }
160
161 void GPS::Config::bind(GPS* aOwner, SGPropertyNode* aCfg)
162 {
163   aOwner->tie(aCfg, "turn-rate-deg-sec", SGRawValuePointer<double>(&_turnRate));
164   aOwner->tie(aCfg, "turn-anticipation", SGRawValuePointer<bool>(&_enableTurnAnticipation));
165   aOwner->tie(aCfg, "wpt-alert-time", SGRawValuePointer<double>(&_waypointAlertTime));
166   aOwner->tie(aCfg, "hard-surface-runways-only", SGRawValuePointer<bool>(&_requireHardSurface));
167   aOwner->tie(aCfg, "cdi-max-deflection-nm", SGRawValuePointer<double>(&_cdiMaxDeflectionNm));
168   aOwner->tie(aCfg, "drive-autopilot", SGRawValuePointer<bool>(&_driveAutopilot));
169   aOwner->tie(aCfg, "course-selectable", SGRawValuePointer<bool>(&_courseSelectable));
170 }
171
172 ////////////////////////////////////////////////////////////////////////////
173
174 GPS::GPS ( SGPropertyNode *node) : 
175   _selectedCourse(0.0),
176   _desiredCourse(0.0),
177   _dataValid(false),
178   _lastPosValid(false),
179   _mode("init"),
180   _name(node->getStringValue("name", "gps")),
181   _num(node->getIntValue("number", 0)),
182   _searchResultIndex(0),
183   _searchExact(true),
184   _searchIsRoute(false),
185   _searchHasNext(false),
186   _searchNames(false),
187   _computeTurnData(false),
188   _anticipateTurn(false),
189   _inTurn(false)
190 {
191   string branch = "/instrumentation/" + _name;
192   _gpsNode = fgGetNode(branch.c_str(), _num, true );
193   _scratchNode = _gpsNode->getChild("scratch", 0, true);
194   
195   SGPropertyNode *wp_node = _gpsNode->getChild("wp", 0, true);
196   _currentWayptNode = wp_node->getChild("wp", 1, true);
197 }
198
199 GPS::~GPS ()
200 {
201 }
202
203 void
204 GPS::init ()
205 {
206   _routeMgr = (FGRouteMgr*) globals->get_subsystem("route-manager");
207   assert(_routeMgr);
208   
209   _magvar_node = fgGetNode("/environment/magnetic-variation-deg", true);
210   _serviceable_node = _gpsNode->getChild("serviceable", 0, true);
211   _serviceable_node->setBoolValue(true);
212   _electrical_node = fgGetNode("/systems/electrical/outputs/gps", true);
213
214 // basic GPS outputs
215   _raim_node = _gpsNode->getChild("raim", 0, true);
216   _odometer_node = _gpsNode->getChild("odometer", 0, true);
217   _trip_odometer_node = _gpsNode->getChild("trip-odometer", 0, true);
218   _true_bug_error_node = _gpsNode->getChild("true-bug-error-deg", 0, true);
219   _magnetic_bug_error_node = _gpsNode->getChild("magnetic-bug-error-deg", 0, true);
220   _eastWestVelocity = _gpsNode->getChild("ew-velocity-msec", 0, true);
221   _northSouthVelocity = _gpsNode->getChild("ns-velocity-msec", 0, true);
222   
223 // waypoints
224   // for compatability, alias selected course down to wp/wp[1]/desired-course-deg
225   SGPropertyNode* wp1Crs = _currentWayptNode->getChild("desired-course-deg", 0, true);
226   wp1Crs->alias(_gpsNode->getChild("desired-course-deg", 0, true));
227
228   _tracking_bug_node = _gpsNode->getChild("tracking-bug", 0, true);
229          
230 // reference navid
231   SGPropertyNode_ptr ref_navaid = _gpsNode->getChild("ref-navaid", 0, true);
232   _ref_navaid_id_node = ref_navaid->getChild("id", 0, true);
233   _ref_navaid_name_node = ref_navaid->getChild("name", 0, true);
234   _ref_navaid_bearing_node = ref_navaid->getChild("bearing-deg", 0, true);
235   _ref_navaid_frequency_node = ref_navaid->getChild("frequency-mhz", 0, true);
236   _ref_navaid_distance_node = ref_navaid->getChild("distance-nm", 0, true);
237   _ref_navaid_mag_bearing_node = ref_navaid->getChild("mag-bearing-deg", 0, true);
238   _ref_navaid_elapsed = 0.0;
239   _ref_navaid_set = false;
240     
241 // route properties    
242   // should these move to the route manager?
243   _routeDistanceNm = _gpsNode->getChild("route-distance-nm", 0, true);
244   _routeETE = _gpsNode->getChild("ETE", 0, true);
245   _routeEditedSignal = fgGetNode("/autopilot/route-manager/signals/edited", true);
246   _routeFinishedSignal = fgGetNode("/autopilot/route-manager/signals/finished", true);
247   
248 // add listener to various things
249   _listener = new GPSListener(this);
250   _route_current_wp_node = fgGetNode("/autopilot/route-manager/current-wp", true);
251   _route_current_wp_node->addChangeListener(_listener);
252   _route_active_node = fgGetNode("/autopilot/route-manager/active", true);
253   _route_active_node->addChangeListener(_listener);
254   _ref_navaid_id_node->addChangeListener(_listener);
255   _routeEditedSignal->addChangeListener(_listener);
256   _routeFinishedSignal->addChangeListener(_listener);
257   
258 // navradio slaving properties  
259   SGPropertyNode* toFlag = _gpsNode->getChild("to-flag", 0, true);
260   toFlag->alias(_currentWayptNode->getChild("to-flag"));
261   
262   SGPropertyNode* fromFlag = _gpsNode->getChild("from-flag", 0, true);
263   fromFlag->alias(_currentWayptNode->getChild("from-flag"));
264     
265 // autopilot drive properties
266   _apDrivingFlag = fgGetNode("/autopilot/settings/gps-driving-true-heading", true);
267   _apTrueHeading = fgGetNode("/autopilot/settings/true-heading-deg",true);
268   _apTargetAltitudeFt = fgGetNode("/autopilot/settings/target-altitude-ft", true);
269   _apAltitudeLock = fgGetNode("/autopilot/locks/altitude", true);
270   
271 // realism prop[s]
272   _realismSimpleGps = fgGetNode("/sim/realism/simple-gps", true);
273   if (!_realismSimpleGps->hasValue()) {
274     _realismSimpleGps->setBoolValue(true);
275   }
276   
277   clearOutput();
278     
279   // last thing, add the deprecated prop watcher
280   new DeprecatedPropListener(_gpsNode);
281 }
282
283 void
284 GPS::reinit ()
285 {
286   clearOutput();
287 }
288
289 void
290 GPS::bind()
291 {
292   _config.bind(this, _gpsNode->getChild("config", 0, true));
293
294 // basic GPS outputs
295   tie(_gpsNode, "selected-course-deg", SGRawValueMethods<GPS, double>
296     (*this, &GPS::getSelectedCourse, &GPS::setSelectedCourse));
297
298   tie(_gpsNode, "desired-course-deg", SGRawValueMethods<GPS, double>
299     (*this, &GPS::getDesiredCourse, NULL));
300   _desiredCourseNode = _gpsNode->getChild("desired-course-deg", 0, true);
301     
302   tieSGGeodReadOnly(_gpsNode, _indicated_pos, "indicated-longitude-deg", 
303         "indicated-latitude-deg", "indicated-altitude-ft");
304
305   tie(_gpsNode, "indicated-vertical-speed", SGRawValueMethods<GPS, double>
306     (*this, &GPS::getVerticalSpeed, NULL));
307   tie(_gpsNode, "indicated-track-true-deg", SGRawValueMethods<GPS, double>
308     (*this, &GPS::getTrueTrack, NULL));
309   tie(_gpsNode, "indicated-track-magnetic-deg", SGRawValueMethods<GPS, double>
310     (*this, &GPS::getMagTrack, NULL));
311   tie(_gpsNode, "indicated-ground-speed-kt", SGRawValueMethods<GPS, double>
312     (*this, &GPS::getGroundspeedKts, NULL));
313   
314 // command system
315   tie(_gpsNode, "mode", SGRawValueMethods<GPS, const char*>(*this, &GPS::getMode, NULL));
316   tie(_gpsNode, "command", SGRawValueMethods<GPS, const char*>(*this, &GPS::getCommand, &GPS::setCommand));
317     
318   tieSGGeod(_scratchNode, _scratchPos, "longitude-deg", "latitude-deg", "altitude-ft");
319   tie(_scratchNode, "valid", SGRawValueMethods<GPS, bool>(*this, &GPS::getScratchValid, NULL));
320   tie(_scratchNode, "distance-nm", SGRawValueMethods<GPS, double>(*this, &GPS::getScratchDistance, NULL));
321   tie(_scratchNode, "true-bearing-deg", SGRawValueMethods<GPS, double>(*this, &GPS::getScratchTrueBearing, NULL));
322   tie(_scratchNode, "mag-bearing-deg", SGRawValueMethods<GPS, double>(*this, &GPS::getScratchMagBearing, NULL));
323   tie(_scratchNode, "has-next", SGRawValueMethods<GPS, bool>(*this, &GPS::getScratchHasNext, NULL));
324   _scratchValid = false;
325
326   
327   SGPropertyNode *wp_node = _gpsNode->getChild("wp", 0, true);
328   SGPropertyNode* wp0_node = wp_node->getChild("wp", 0, true);
329   
330   tieSGGeodReadOnly(wp0_node, _wp0_position, "longitude-deg", "latitude-deg", "altitude-ft");
331   tie(_currentWayptNode, "ID", SGRawValueMethods<GPS, const char*>
332     (*this, &GPS::getWP1Ident, NULL));
333   
334   tie(_currentWayptNode, "distance-nm", SGRawValueMethods<GPS, double>
335     (*this, &GPS::getWP1Distance, NULL));
336   tie(_currentWayptNode, "bearing-true-deg", SGRawValueMethods<GPS, double>
337     (*this, &GPS::getWP1Bearing, NULL));
338   tie(_currentWayptNode, "bearing-mag-deg", SGRawValueMethods<GPS, double>
339     (*this, &GPS::getWP1MagBearing, NULL));
340   tie(_currentWayptNode, "TTW-sec", SGRawValueMethods<GPS, double>
341     (*this, &GPS::getWP1TTW, NULL));
342   tie(_currentWayptNode, "TTW", SGRawValueMethods<GPS, const char*>
343     (*this, &GPS::getWP1TTWString, NULL));
344   
345   tie(_currentWayptNode, "course-deviation-deg", SGRawValueMethods<GPS, double>
346     (*this, &GPS::getWP1CourseDeviation, NULL));
347   tie(_currentWayptNode, "course-error-nm", SGRawValueMethods<GPS, double>
348     (*this, &GPS::getWP1CourseErrorNm, NULL));
349   tie(_currentWayptNode, "to-flag", SGRawValueMethods<GPS, bool>
350     (*this, &GPS::getWP1ToFlag, NULL));
351   tie(_currentWayptNode, "from-flag", SGRawValueMethods<GPS, bool>
352     (*this, &GPS::getWP1FromFlag, NULL));
353
354 // leg properties (only valid in DTO/LEG modes, not OBS)
355   tie(wp_node, "leg-distance-nm", SGRawValueMethods<GPS, double>(*this, &GPS::getLegDistance, NULL));
356   tie(wp_node, "leg-true-course-deg", SGRawValueMethods<GPS, double>(*this, &GPS::getLegCourse, NULL));
357   tie(wp_node, "leg-mag-course-deg", SGRawValueMethods<GPS, double>(*this, &GPS::getLegMagCourse, NULL));
358
359 // navradio slaving properties  
360   tie(_gpsNode, "cdi-deflection", SGRawValueMethods<GPS,double>
361     (*this, &GPS::getCDIDeflection));
362 }
363
364 void
365 GPS::unbind()
366 {
367   _tiedProperties.Untie();
368 }
369
370 void
371 GPS::clearOutput()
372 {
373   _dataValid = false;
374   _last_speed_kts = 0.0;
375   _last_pos = SGGeod();
376   _lastPosValid = false;
377   _indicated_pos = SGGeod();
378   _last_vertical_speed = 0.0;
379   _last_true_track = 0.0;
380   _lastEWVelocity = _lastNSVelocity = 0.0;
381   _currentWaypt = _prevWaypt = NULL;
382   _legDistanceNm = -1.0;
383   
384   _raim_node->setDoubleValue(0.0);
385   _indicated_pos = SGGeod();
386   _odometer_node->setDoubleValue(0);
387   _trip_odometer_node->setDoubleValue(0);
388   _tracking_bug_node->setDoubleValue(0);
389   _true_bug_error_node->setDoubleValue(0);
390   _magnetic_bug_error_node->setDoubleValue(0);
391   _northSouthVelocity->setDoubleValue(0.0);
392   _eastWestVelocity->setDoubleValue(0.0);
393 }
394
395 void
396 GPS::update (double delta_time_sec)
397 {
398   if (!_realismSimpleGps->getBoolValue()) {
399     // If it's off, don't bother.
400     if (!_serviceable_node->getBoolValue() || !_electrical_node->getBoolValue()) {
401       clearOutput();
402       return;
403     }
404   }
405   
406   if (delta_time_sec <= 0.0) {
407     return; // paused, don't bother
408   }    
409   
410   _raim_node->setDoubleValue(1.0);
411   _indicated_pos = globals->get_aircraft_position();
412   updateBasicData(delta_time_sec);
413
414   if (_dataValid) {
415     if (_wayptController.get()) {
416       _wayptController->update();
417       SGGeod p(_wayptController->position());
418       _currentWayptNode->setDoubleValue("longitude-deg", p.getLongitudeDeg());
419       _currentWayptNode->setDoubleValue("latitude-deg", p.getLatitudeDeg());
420       _currentWayptNode->setDoubleValue("altitude-ft", p.getElevationFt());
421       
422       _desiredCourse = getLegMagCourse();
423       
424       updateTurn();
425       updateRouteData();
426     }
427
428     
429     updateTrackingBug();
430     updateReferenceNavaid(delta_time_sec);
431     driveAutopilot();
432   }
433   
434   if (_dataValid && (_mode == "init")) {
435         
436     if (_route_active_node->getBoolValue()) {
437       // GPS init with active route
438       SG_LOG(SG_INSTR, SG_INFO, "GPS init with active route");
439       selectLegMode();
440     } else {
441       // initialise in OBS mode, with waypt set to the nearest airport.
442       // keep in mind at this point, _dataValid is not set
443     
444       auto_ptr<FGPositioned::Filter> f(createFilter(FGPositioned::AIRPORT));
445       FGPositionedRef apt = FGPositioned::findClosest(_indicated_pos, 20.0, f.get());
446       if (apt) {
447         setScratchFromPositioned(apt, 0);
448         selectOBSMode();
449       }
450     }
451
452     if (_mode != "init")
453     {
454       // allow a realistic delay in the future, here
455     }
456   } // of init mode check
457   
458   _last_pos = _indicated_pos;
459   _lastPosValid = !(_last_pos == SGGeod());
460 }
461
462 ///////////////////////////////////////////////////////////////////////////
463 // implement the RNAV interface 
464 SGGeod GPS::position()
465 {
466   if (!_dataValid) {
467     return SGGeod();
468   }
469   
470   return _indicated_pos;
471 }
472
473 double GPS::trackDeg()
474 {
475   return _last_true_track;
476 }
477
478 double GPS::groundSpeedKts()
479 {
480   return _last_speed_kts;
481 }
482
483 double GPS::vspeedFPM()
484 {
485   return _last_vertical_speed;
486 }
487
488 double GPS::magvarDeg()
489 {
490   return _magvar_node->getDoubleValue();
491 }
492
493 double GPS::overflightArmDistanceM()
494 {
495   return _config.overflightArmDistanceNm() * SG_NM_TO_METER;
496 }
497
498 double GPS::selectedMagCourse()
499 {
500   return _selectedCourse;
501 }
502
503 ///////////////////////////////////////////////////////////////////////////
504
505 void
506 GPS::updateBasicData(double dt)
507 {
508   if (!_lastPosValid) {
509     return;
510   }
511   
512   double distance_m;
513   double track2_deg;
514   SGGeodesy::inverse(_last_pos, _indicated_pos, _last_true_track, track2_deg, distance_m );
515     
516   double speed_kt = ((distance_m * SG_METER_TO_NM) * ((1 / dt) * 3600.0));
517   double vertical_speed_mpm = ((_indicated_pos.getElevationM() - _last_pos.getElevationM()) * 60 / dt);
518   _last_vertical_speed = vertical_speed_mpm * SG_METER_TO_FEET;
519   
520   speed_kt = fgGetLowPass(_last_speed_kts, speed_kt, dt/10.0);
521   _last_speed_kts = speed_kt;
522   
523   SGGeod g = _indicated_pos;
524   g.setLongitudeDeg(_last_pos.getLongitudeDeg());
525   double northSouthM = SGGeodesy::distanceM(_last_pos, g);
526   northSouthM = copysign(northSouthM, _indicated_pos.getLatitudeDeg() - _last_pos.getLatitudeDeg());
527   
528   double nsMSec = fgGetLowPass(_lastNSVelocity, northSouthM / dt, dt/2.0);
529   _lastNSVelocity = nsMSec;
530   _northSouthVelocity->setDoubleValue(nsMSec);
531
532
533   g = _indicated_pos;
534   g.setLatitudeDeg(_last_pos.getLatitudeDeg());
535   double eastWestM = SGGeodesy::distanceM(_last_pos, g);
536   eastWestM = copysign(eastWestM, _indicated_pos.getLongitudeDeg() - _last_pos.getLongitudeDeg());
537   
538   double ewMSec = fgGetLowPass(_lastEWVelocity, eastWestM / dt, dt/2.0);
539   _lastEWVelocity = ewMSec;
540   _eastWestVelocity->setDoubleValue(ewMSec);
541   
542   double odometer = _odometer_node->getDoubleValue();
543   _odometer_node->setDoubleValue(odometer + distance_m * SG_METER_TO_NM);
544   odometer = _trip_odometer_node->getDoubleValue();
545   _trip_odometer_node->setDoubleValue(odometer + distance_m * SG_METER_TO_NM);
546   
547   if (!_dataValid) {
548     _dataValid = true;
549   }
550 }
551
552 void
553 GPS::updateTrackingBug()
554 {
555   double tracking_bug = _tracking_bug_node->getDoubleValue();
556   double true_bug_error = tracking_bug - getTrueTrack();
557   double magnetic_bug_error = tracking_bug - getMagTrack();
558
559   // Get the errors into the (-180,180) range.
560   SG_NORMALIZE_RANGE(true_bug_error, -180.0, 180.0);
561   SG_NORMALIZE_RANGE(magnetic_bug_error, -180.0, 180.0);
562
563   _true_bug_error_node->setDoubleValue(true_bug_error);
564   _magnetic_bug_error_node->setDoubleValue(magnetic_bug_error);
565 }
566
567 void GPS::updateReferenceNavaid(double dt)
568 {
569   if (!_ref_navaid_set) {
570     _ref_navaid_elapsed += dt;
571     if (_ref_navaid_elapsed > 5.0) {
572
573       FGPositioned::TypeFilter vorFilter(FGPositioned::VOR);
574       FGPositionedRef nav = FGPositioned::findClosest(_indicated_pos, 400.0, &vorFilter);
575       if (!nav) {
576         SG_LOG(SG_INSTR, SG_DEBUG, "GPS couldn't find a reference navaid");
577         _ref_navaid_id_node->setStringValue("");
578         _ref_navaid_name_node->setStringValue("");
579         _ref_navaid_bearing_node->setDoubleValue(0.0);
580         _ref_navaid_mag_bearing_node->setDoubleValue(0.0);
581         _ref_navaid_distance_node->setDoubleValue(0.0);
582         _ref_navaid_frequency_node->setStringValue("");
583       } else if (nav != _ref_navaid) {
584         SG_LOG(SG_INSTR, SG_DEBUG, "GPS code selected new ref-navaid:" << nav->ident());
585         _listener->setGuard(true);
586         _ref_navaid_id_node->setStringValue(nav->ident().c_str());
587         _ref_navaid_name_node->setStringValue(nav->name().c_str());
588         FGNavRecord* vor = (FGNavRecord*) nav.ptr();
589         _ref_navaid_frequency_node->setDoubleValue(vor->get_freq() / 100.0);
590         _listener->setGuard(false);
591       } else {
592         // SG_LOG(SG_INSTR, SG_ALERT, "matched existing");
593       }
594       
595       _ref_navaid = nav;
596       // reset elapsed time (do not do that before updating the properties above, since their
597       // listeners may request another update (_ref_navaid_elapsed = 9999), which results in
598       // excessive load (FGPositioned::findClosest called in every update loop...)
599       _ref_navaid_elapsed = 0.0; 
600     }
601   }
602   
603   if (_ref_navaid) {
604     double trueCourse, distanceM, az2;
605     SGGeodesy::inverse(_indicated_pos, _ref_navaid->geod(), trueCourse, az2, distanceM);
606     _ref_navaid_distance_node->setDoubleValue(distanceM * SG_METER_TO_NM);
607     _ref_navaid_bearing_node->setDoubleValue(trueCourse);
608     _ref_navaid_mag_bearing_node->setDoubleValue(trueCourse - _magvar_node->getDoubleValue());
609   }
610 }
611
612 void GPS::referenceNavaidSet(const std::string& aNavaid)
613 {
614   _ref_navaid = NULL;
615   // allow setting an empty string to restore normal nearest-vor selection
616   if (aNavaid.size() > 0) {
617     FGPositioned::TypeFilter vorFilter(FGPositioned::VOR);
618     _ref_navaid = FGPositioned::findClosestWithIdent(aNavaid, 
619       _indicated_pos, &vorFilter);
620     
621     if (!_ref_navaid) {
622       SG_LOG(SG_INSTR, SG_ALERT, "GPS: unknown ref navaid:" << aNavaid);
623     }
624   }
625
626   if (_ref_navaid) {
627     _ref_navaid_set = true;
628     SG_LOG(SG_INSTR, SG_INFO, "GPS code set explicit ref-navaid:" << _ref_navaid->ident());
629     _ref_navaid_id_node->setStringValue(_ref_navaid->ident().c_str());
630     _ref_navaid_name_node->setStringValue(_ref_navaid->name().c_str());
631     FGNavRecord* vor = (FGNavRecord*) _ref_navaid.ptr();
632     _ref_navaid_frequency_node->setDoubleValue(vor->get_freq() / 100.0);
633   } else {
634     _ref_navaid_set = false;
635     _ref_navaid_elapsed = 9999.0; // update next tick
636   }
637 }
638
639 void GPS::routeActivated()
640 {
641   if (_route_active_node->getBoolValue()) {
642     SG_LOG(SG_INSTR, SG_INFO, "GPS::route activated, switching to LEG mode");
643     selectLegMode();
644     
645     // if we've already passed the current waypoint, sequence.
646     if (_dataValid && getWP1FromFlag()) {
647       SG_LOG(SG_INSTR, SG_INFO, "GPS::route activated, FROM wp1, sequencing");
648       _routeMgr->sequence();
649     }
650   } else if (_mode == "leg") {
651     SG_LOG(SG_INSTR, SG_INFO, "GPS::route deactivated, switching to OBS mode");
652     // select OBS mode, but keep current waypoint-as is
653     _mode = "obs";
654     wp1Changed();
655   }
656 }
657
658 void GPS::routeManagerSequenced()
659 {
660   if (_mode != "leg") {
661     SG_LOG(SG_INSTR, SG_DEBUG, "GPS ignoring route sequencing, not in LEG mode");
662     return;
663   }
664   
665   int index = _routeMgr->currentIndex(),
666     count = _routeMgr->numWaypts();
667   if ((index < 0) || (index >= count)) {
668     _currentWaypt=NULL;
669     _prevWaypt=NULL;
670     // no active leg on the route
671     return;
672   }
673   
674   SG_LOG(SG_INSTR, SG_DEBUG, "GPS waypoint index is now " << index);
675   
676   if (index > 0) {
677     _prevWaypt = _routeMgr->wayptAtIndex(index - 1);
678     if (_prevWaypt->flag(WPT_DYNAMIC)) {
679       _wp0_position = _indicated_pos;
680     } else {
681       _wp0_position = _prevWaypt->position();
682     }
683   }
684   
685   _currentWaypt = _routeMgr->currentWaypt();
686
687   _desiredCourse = getLegMagCourse();
688   _desiredCourseNode->fireValueChanged();
689   wp1Changed();
690 }
691
692 void GPS::routeEdited()
693 {
694   if (_mode != "leg") {
695     return;
696   }
697   
698   SG_LOG(SG_INSTR, SG_INFO, "GPS route edited while in LEG mode, updating waypoints");
699   routeManagerSequenced();
700 }
701
702 void GPS::routeFinished()
703 {
704   if (_mode != "leg") {
705     return;
706   }
707   
708   SG_LOG(SG_INSTR, SG_INFO, "GPS route finished, reverting to OBS");
709   // select OBS mode, but keep current waypoint-as is
710   _mode = "obs";
711   wp1Changed();
712 }
713
714 void GPS::updateTurn()
715 {
716   bool printProgress = false;
717   
718   if (_computeTurnData) {
719     if (_last_speed_kts < 10) {
720       // need valid leg course and sensible ground speed to compute the turn
721       return;
722     }
723     
724     computeTurnData();
725     printProgress = true;
726   }
727   
728   if (!_anticipateTurn) {
729     updateOverflight();
730     return;
731   }
732
733   updateTurnData();
734   // find bearing to turn centre
735   double bearing, az2, distanceM;
736   SGGeodesy::inverse(_indicated_pos, _turnCentre, bearing, az2, distanceM);
737   double progress = computeTurnProgress(bearing);
738   
739   if (printProgress) {
740     SG_LOG(SG_INSTR, SG_INFO,"turn progress=" << progress);
741   }
742   
743   if (!_inTurn && (progress > 0.0)) {
744     beginTurn();
745   }
746   
747   if (_inTurn && !_turnSequenced && (progress > 0.5)) {
748     _turnSequenced = true;
749      SG_LOG(SG_INSTR, SG_INFO, "turn passed midpoint, sequencing");
750      _routeMgr->sequence();
751   }
752   
753   if (_inTurn && (progress >= 1.0)) {
754     endTurn();
755   }
756   
757   if (_inTurn) {
758     // drive deviation and desired course
759     double desiredCourse = bearing - copysign(90, _turnAngle);
760     SG_NORMALIZE_RANGE(desiredCourse, 0.0, 360.0);
761     double deviationNm = (distanceM * SG_METER_TO_NM) - _turnRadius;
762     double deviationDeg = desiredCourse - getMagTrack();
763     deviationNm = copysign(deviationNm, deviationDeg);
764     // FIXME
765     //_wp1_course_deviation_node->setDoubleValue(deviationDeg);
766     //_wp1_course_error_nm_node->setDoubleValue(deviationNm);
767     //_cdiDeflectionNode->setDoubleValue(deviationDeg);
768   }
769 }
770
771 void GPS::updateOverflight()
772 {
773   if (!_wayptController->isDone()) {
774     return;
775   }
776   
777   if (_mode == "dto") {
778     SG_LOG(SG_INSTR, SG_INFO, "GPS DTO reached destination point");
779     
780     // check for wp1 being on active route - resume leg mode
781     if (_routeMgr->isRouteActive()) {
782       int index = _routeMgr->flightPlan()->findWayptIndex(_currentWaypt->position());
783       if (index >= 0) {
784         SG_LOG(SG_INSTR, SG_INFO, "GPS DTO, resuming LEG mode at wp:" << index);
785         _mode = "leg";
786         _routeMgr->jumpToIndex(index);
787       }
788     }
789     
790     if (_mode == "dto") {
791       // if we didn't enter leg mode, drop back to OBS mode
792       // select OBS mode, but keep current waypoint-as is
793       _mode = "obs";
794       wp1Changed();
795     }
796   } else if (_mode == "leg") {
797     SG_LOG(SG_INSTR, SG_DEBUG, "GPS doing overflight sequencing");
798     _routeMgr->sequence();
799   } else if (_mode == "obs") {
800     // nothing to do here, TO/FROM will update but that's fine
801   }
802   
803   _computeTurnData = true;
804 }
805
806 void GPS::beginTurn()
807 {
808   _inTurn = true;
809   _turnSequenced = false;
810   SG_LOG(SG_INSTR, SG_INFO, "beginning turn");
811 }
812
813 void GPS::endTurn()
814 {
815   _inTurn = false;
816   SG_LOG(SG_INSTR, SG_INFO, "ending turn");
817   _computeTurnData = true;
818 }
819
820 double GPS::computeTurnProgress(double aBearing) const
821 {
822   double startBearing = _turnStartBearing + copysign(90, _turnAngle);
823   return (aBearing - startBearing) / _turnAngle;
824 }
825
826 void GPS::computeTurnData()
827 {
828   _computeTurnData = false;
829   int nextIndex = _routeMgr->currentIndex() + 1;
830   if ((_mode != "leg") || (nextIndex >= _routeMgr->numWaypts())) {
831     _anticipateTurn = false;
832     return;
833   }
834   
835   WayptRef next = _routeMgr->wayptAtIndex(nextIndex);
836   if (next->flag(WPT_DYNAMIC) || !_config.turnAnticipationEnabled()) {
837     _anticipateTurn = false;
838     return;
839   }
840   
841   _turnStartBearing = _desiredCourse;
842 // compute next leg course
843   double crs, dist;
844   boost::tie(crs, dist) = next->courseAndDistanceFrom(_currentWaypt->position());
845     
846
847 // compute offset bearing
848   _turnAngle = crs - _turnStartBearing;
849   SG_NORMALIZE_RANGE(_turnAngle, -180.0, 180.0);
850   double median = _turnStartBearing + (_turnAngle * 0.5);
851   double offsetBearing = median + copysign(90, _turnAngle);
852   SG_NORMALIZE_RANGE(offsetBearing, 0.0, 360.0);
853   
854   SG_LOG(SG_INSTR, SG_INFO, "GPS computeTurnData: in=" << _turnStartBearing <<
855     ", out=" << crs << "; turnAngle=" << _turnAngle << ", median=" << median 
856     << ", offset=" << offsetBearing);
857
858   SG_LOG(SG_INSTR, SG_INFO, "next leg is now:" << _currentWaypt->ident() << "->" << next->ident());
859
860   _turnPt = _currentWaypt->position();
861   _anticipateTurn = true;
862 }
863
864 void GPS::updateTurnData()
865 {
866   // depends on ground speed, so needs to be updated per-frame
867   _turnRadius = computeTurnRadiusNm(_last_speed_kts);
868   
869   // compute the turn centre, based on the turn radius.
870   // key thing is to understand that we're working a right-angle triangle,
871   // where the right-angle is the point we start the turn. From that point,
872   // one side is the inbound course to the turn pt, and the other is the
873   // perpendicular line, of length 'r', to the turn centre.
874   // the triangle's hypotenuse, which we need to find, is the distance from the
875   // turn pt to the turn center (in the direction of the offset bearing)
876   // note that d - _turnRadius tell us how much we're 'cutting' the corner.
877   
878   double halfTurnAngle = fabs(_turnAngle * 0.5) * SG_DEGREES_TO_RADIANS;
879   double d = _turnRadius / cos(halfTurnAngle);
880   
881  // SG_LOG(SG_INSTR, SG_INFO, "turnRadius=" << _turnRadius << ", d=" << d
882  //   << " (cut distance=" << d - _turnRadius << ")");
883   
884   double median = _turnStartBearing + (_turnAngle * 0.5);
885   double offsetBearing = median + copysign(90, _turnAngle);
886   SG_NORMALIZE_RANGE(offsetBearing, 0.0, 360.0);
887   
888   double az2;
889   SGGeodesy::direct(_turnPt, offsetBearing, d * SG_NM_TO_METER, _turnCentre, az2); 
890 }
891
892 double GPS::computeTurnRadiusNm(double aGroundSpeedKts) const
893 {
894         // turn time is seconds to execute a 360 turn. 
895   double turnTime = 360.0 / _config.turnRateDegSec();
896   
897   // c is ground distance covered in that time (circumference of the circle)
898         double c = turnTime * (aGroundSpeedKts / 3600.0); // convert knts to nm/sec
899   
900   // divide by 2PI to go from circumference -> radius
901         return c / (2 * M_PI);
902 }
903
904 void GPS::updateRouteData()
905 {
906   double totalDistance = _wayptController->distanceToWayptM() * SG_METER_TO_NM;
907   // walk all waypoints from wp2 to route end, and sum
908   for (int i=_routeMgr->currentIndex()+1; i<_routeMgr->numWaypts(); ++i) {
909     //totalDistance += _routeMgr->get_waypoint(i).get_distance();
910   }
911   
912   _routeDistanceNm->setDoubleValue(totalDistance * SG_METER_TO_NM);
913   if (_last_speed_kts > 1.0) {
914     double TTW = ((totalDistance * SG_METER_TO_NM) / _last_speed_kts) * 3600.0;
915     _routeETE->setStringValue(makeTTWString(TTW));    
916   }
917 }
918
919 void GPS::driveAutopilot()
920 {
921   if (!_config.driveAutopilot() || !_realismSimpleGps->getBoolValue()) {
922     _apDrivingFlag->setBoolValue(false);
923     return;
924   }
925  
926   // compatability feature - allow the route-manager / GPS to drive the
927   // generic autopilot heading hold *in leg mode only* 
928   
929   bool drive = _mode == "leg";
930   _apDrivingFlag->setBoolValue(drive);
931   
932   if (drive) {
933     // FIXME: we want to set desired track, not heading, here
934     _apTrueHeading->setDoubleValue(getWP1Bearing());
935   }
936 }
937
938 void GPS::wp1Changed()
939 {
940   if (!_currentWaypt)
941     return;
942   if (_mode == "leg") {
943     _wayptController.reset(WayptController::createForWaypt(this, _currentWaypt));
944   } else if (_mode == "obs") {
945     _wayptController.reset(new OBSController(this, _currentWaypt));
946   } else if (_mode == "dto") {
947     _wayptController.reset(new DirectToController(this, _currentWaypt, _wp0_position));
948   }
949
950   _wayptController->init();
951
952   if (_mode == "obs") {
953     _legDistanceNm = -1.0;
954   } else {
955     _wayptController->update();
956     _legDistanceNm = _wayptController->distanceToWayptM() * SG_METER_TO_NM;
957   }
958   
959   if (!_config.driveAutopilot()) {
960     return;
961   }
962   
963   RouteRestriction ar = _currentWaypt->altitudeRestriction();
964   double restrictAlt = _currentWaypt->altitudeFt();
965   double alt = _indicated_pos.getElevationFt();
966   if ((ar == RESTRICT_AT) ||
967        ((ar == RESTRICT_ABOVE) && (alt < restrictAlt)) ||
968        ((ar == RESTRICT_BELOW) && (alt > restrictAlt)))
969   {
970     SG_LOG(SG_AUTOPILOT, SG_INFO, "current waypt has altitude set, setting on AP");
971     _apTargetAltitudeFt->setDoubleValue(restrictAlt);
972   }
973 }
974
975 /////////////////////////////////////////////////////////////////////////////
976 // property getter/setters
977
978 void GPS::setSelectedCourse(double crs)
979 {
980   if (_selectedCourse == crs) {
981     return;
982   }
983   
984   _selectedCourse = crs;
985   if ((_mode == "obs") || _config.courseSelectable()) {
986     _desiredCourse = _selectedCourse;
987     _desiredCourseNode->fireValueChanged();
988   }
989 }
990
991 double GPS::getLegDistance() const
992 {
993   if (!_dataValid || (_mode == "obs")) {
994     return -1;
995   }
996   
997   return _legDistanceNm;
998 }
999
1000 double GPS::getLegCourse() const
1001 {
1002   if (!_dataValid || !_wayptController.get()) {
1003     return -9999.0;
1004   }
1005   
1006   return _wayptController->targetTrackDeg();
1007 }
1008
1009 double GPS::getLegMagCourse() const
1010 {
1011   if (!_dataValid) {
1012     return 0.0;
1013   }
1014   
1015   double m = getLegCourse() - _magvar_node->getDoubleValue();
1016   SG_NORMALIZE_RANGE(m, 0.0, 360.0);
1017   return m;
1018 }
1019
1020 double GPS::getMagTrack() const
1021 {
1022   if (!_dataValid) {
1023     return 0.0;
1024   }
1025   
1026   double m = getTrueTrack() - _magvar_node->getDoubleValue();
1027   SG_NORMALIZE_RANGE(m, 0.0, 360.0);
1028   return m;
1029 }
1030
1031 double GPS::getCDIDeflection() const
1032 {
1033   if (!_dataValid) {
1034     return 0.0;
1035   }
1036   
1037   double defl;
1038   if (_config.cdiDeflectionIsAngular()) {
1039     defl = getWP1CourseDeviation();
1040     SG_CLAMP_RANGE(defl, -10.0, 10.0); // as in navradio.cxx
1041   } else {
1042     double fullScale = _config.cdiDeflectionLinearPeg();
1043     double normError = getWP1CourseErrorNm() / fullScale;
1044     SG_CLAMP_RANGE(normError, -1.0, 1.0);
1045     defl = normError * 10.0; // re-scale to navradio limits, i.e [-10.0 .. 10.0]
1046   }
1047   
1048   return defl;
1049 }
1050
1051 const char* GPS::getWP0Ident() const
1052 {
1053   if (!_dataValid || (_mode != "leg") || (!_prevWaypt)) {
1054     return "";
1055   }
1056   
1057   return _prevWaypt->ident().c_str();
1058 }
1059
1060 const char* GPS::getWP0Name() const
1061 {
1062   return "";
1063 }
1064
1065 const char* GPS::getWP1Ident() const
1066 {
1067   if ((!_dataValid)||(!_currentWaypt)) {
1068     return "";
1069   }
1070   
1071   return _currentWaypt->ident().c_str();
1072 }
1073
1074 const char* GPS::getWP1Name() const
1075 {
1076   return "";
1077 }
1078
1079 double GPS::getWP1Distance() const
1080 {
1081   if (!_dataValid || !_wayptController.get()) {
1082     return -1.0;
1083   }
1084   
1085   return _wayptController->distanceToWayptM() * SG_METER_TO_NM;
1086 }
1087
1088 double GPS::getWP1TTW() const
1089 {
1090   if (!_dataValid || !_wayptController.get()) {
1091     return -1.0;
1092   }
1093   
1094   return _wayptController->timeToWaypt();
1095 }
1096
1097 const char* GPS::getWP1TTWString() const
1098 {
1099   double t = getWP1TTW();
1100   if (t <= 0.0) {
1101     return "";
1102   }
1103   
1104   return makeTTWString(t);
1105 }
1106
1107 double GPS::getWP1Bearing() const
1108 {
1109   if (!_dataValid || !_wayptController.get()) {
1110     return -9999.0;
1111   }
1112   
1113   return _wayptController->trueBearingDeg();
1114 }
1115
1116 double GPS::getWP1MagBearing() const
1117 {
1118   if (!_dataValid || !_wayptController.get()) {
1119     return -9999.0;
1120   }
1121
1122   double magBearing =  _wayptController->trueBearingDeg() - _magvar_node->getDoubleValue();
1123   SG_NORMALIZE_RANGE(magBearing, 0.0, 360.0);
1124   return magBearing;
1125 }
1126
1127 double GPS::getWP1CourseDeviation() const
1128 {
1129   if (!_dataValid || !_wayptController.get()) {
1130     return 0.0;
1131   }
1132
1133   return _wayptController->courseDeviationDeg();
1134 }
1135
1136 double GPS::getWP1CourseErrorNm() const
1137 {
1138   if (!_dataValid || !_wayptController.get()) {
1139     return 0.0;
1140   }
1141   
1142   return _wayptController->xtrackErrorNm();
1143 }
1144
1145 bool GPS::getWP1ToFlag() const
1146 {
1147   if (!_dataValid || !_wayptController.get()) {
1148     return false;
1149   }
1150   
1151   return _wayptController->toFlag();
1152 }
1153
1154 bool GPS::getWP1FromFlag() const
1155 {
1156   if (!_dataValid || !_wayptController.get()) {
1157     return false;
1158   }
1159   
1160   return !getWP1ToFlag();
1161 }
1162
1163 double GPS::getScratchDistance() const
1164 {
1165   if (!_scratchValid) {
1166     return 0.0;
1167   }
1168   
1169   return SGGeodesy::distanceNm(_indicated_pos, _scratchPos);
1170 }
1171
1172 double GPS::getScratchTrueBearing() const
1173 {
1174   if (!_scratchValid) {
1175     return 0.0;
1176   }
1177
1178   return SGGeodesy::courseDeg(_indicated_pos, _scratchPos);
1179 }
1180
1181 double GPS::getScratchMagBearing() const
1182 {
1183   if (!_scratchValid) {
1184     return 0.0;
1185   }
1186   
1187   double crs = getScratchTrueBearing() - _magvar_node->getDoubleValue();
1188   SG_NORMALIZE_RANGE(crs, 0.0, 360.0);
1189   return crs;
1190 }
1191
1192 /////////////////////////////////////////////////////////////////////////////
1193 // command / scratch / search system
1194
1195 void GPS::setCommand(const char* aCmd)
1196 {
1197   SG_LOG(SG_INSTR, SG_DEBUG, "GPS command:" << aCmd);
1198   
1199   if (!strcmp(aCmd, "direct")) {
1200     directTo();
1201   } else if (!strcmp(aCmd, "obs")) {
1202     selectOBSMode();
1203   } else if (!strcmp(aCmd, "leg")) {
1204     selectLegMode();
1205   } else if (!strcmp(aCmd, "load-route-wpt")) {
1206     loadRouteWaypoint();
1207   } else if (!strcmp(aCmd, "nearest")) {
1208     loadNearest();
1209   } else if (!strcmp(aCmd, "search")) {
1210     _searchNames = false;
1211     search();
1212   } else if (!strcmp(aCmd, "search-names")) {
1213     _searchNames = true;
1214     search();
1215   } else if (!strcmp(aCmd, "next")) {
1216     nextResult();
1217   } else if (!strcmp(aCmd, "previous")) {
1218     previousResult();
1219   } else if (!strcmp(aCmd, "define-user-wpt")) {
1220     defineWaypoint();
1221   } else if (!strcmp(aCmd, "route-insert-before")) {
1222     int index = _scratchNode->getIntValue("index");
1223     if (index < 0 || (_routeMgr->numWaypts() == 0)) {
1224       index = _routeMgr->numWaypts();
1225     } else if (index >= _routeMgr->numWaypts()) {
1226       SG_LOG(SG_INSTR, SG_WARN, "GPS:route-insert-before, bad index:" << index);
1227       return;
1228     }
1229     
1230     insertWaypointAtIndex(index);
1231   } else if (!strcmp(aCmd, "route-insert-after")) {
1232     int index = _scratchNode->getIntValue("index");
1233     if (index < 0 || (_routeMgr->numWaypts() == 0)) {
1234       index = _routeMgr->numWaypts();
1235     } else if (index >= _routeMgr->numWaypts()) {
1236       SG_LOG(SG_INSTR, SG_WARN, "GPS:route-insert-after, bad index:" << index);
1237       return;
1238     } else {
1239       ++index; 
1240     }
1241   
1242     insertWaypointAtIndex(index);
1243   } else if (!strcmp(aCmd, "route-delete")) {
1244     int index = _scratchNode->getIntValue("index");
1245     if (index < 0) {
1246       index = _routeMgr->numWaypts();
1247     } else if (index >= _routeMgr->numWaypts()) {
1248       SG_LOG(SG_INSTR, SG_WARN, "GPS:route-delete, bad index:" << index);
1249       return;
1250     }
1251     
1252     removeWaypointAtIndex(index);
1253   } else {
1254     SG_LOG(SG_INSTR, SG_WARN, "GPS:unrecognized command:" << aCmd);
1255   }
1256 }
1257
1258 void GPS::clearScratch()
1259 {
1260   _scratchPos = SGGeod::fromDegFt(-9999.0, -9999.0, -9999.0);
1261   _scratchValid = false;  
1262   _scratchNode->setStringValue("type", "");
1263   _scratchNode->setStringValue("query", "");
1264 }
1265
1266 bool GPS::isScratchPositionValid() const
1267 {
1268   if ((_scratchPos.getLongitudeDeg() < -9990.0) ||
1269       (_scratchPos.getLatitudeDeg() < -9990.0)) {
1270    return false;   
1271   }
1272   
1273   return true;
1274 }
1275
1276 void GPS::directTo()
1277 {  
1278   if (!isScratchPositionValid()) {
1279     return;
1280   }
1281   
1282   _prevWaypt = NULL;
1283   _wp0_position = _indicated_pos;
1284   _currentWaypt = new BasicWaypt(_scratchPos, _scratchNode->getStringValue("ident"), NULL);
1285   _mode = "dto";
1286
1287   clearScratch();
1288   wp1Changed();
1289 }
1290
1291 void GPS::loadRouteWaypoint()
1292 {
1293   _scratchValid = false;
1294 //  if (!_routeMgr->isRouteActive()) {
1295 //    SG_LOG(SG_INSTR, SG_WARN, "GPS:loadWaypoint: no active route");
1296 //    return;
1297 //  }
1298   
1299   int index = _scratchNode->getIntValue("index", -9999);
1300   clearScratch();
1301   
1302   if ((index < 0) || (index >= _routeMgr->numWaypts())) { // no index supplied, use current wp
1303     index = _routeMgr->currentIndex();
1304   }
1305   
1306   _searchIsRoute = true;
1307   setScratchFromRouteWaypoint(index);
1308 }
1309
1310 void GPS::setScratchFromRouteWaypoint(int aIndex)
1311 {
1312   assert(_searchIsRoute);
1313   if ((aIndex < 0) || (aIndex >= _routeMgr->numWaypts())) {
1314     SG_LOG(SG_INSTR, SG_WARN, "GPS:setScratchFromRouteWaypoint: route-index out of bounds");
1315     return;
1316   }
1317   
1318   _searchResultIndex = aIndex;
1319   WayptRef wp = _routeMgr->wayptAtIndex(aIndex);
1320   _scratchNode->setStringValue("ident", wp->ident());
1321   _scratchPos = wp->position();
1322   _scratchValid = true;
1323   _scratchNode->setIntValue("index", aIndex);
1324 }
1325
1326 void GPS::loadNearest()
1327 {
1328   string sty(_scratchNode->getStringValue("type"));
1329   FGPositioned::Type ty = FGPositioned::typeFromName(sty);
1330   if (ty == FGPositioned::INVALID) {
1331     SG_LOG(SG_INSTR, SG_WARN, "GPS:loadNearest: request type is invalid:" << sty);
1332     return;
1333   }
1334   
1335   auto_ptr<FGPositioned::Filter> f(createFilter(ty));
1336   int limitCount = _scratchNode->getIntValue("max-results", 1);
1337   double cutoffDistance = _scratchNode->getDoubleValue("cutoff-nm", 400.0);
1338   
1339   SGGeod searchPos = _indicated_pos;
1340   if (isScratchPositionValid()) {
1341     searchPos = _scratchPos;
1342   }
1343   
1344   clearScratch(); // clear now, regardless of whether we find a match or not
1345     
1346   _searchResults = 
1347     FGPositioned::findClosestN(searchPos, limitCount, cutoffDistance, f.get());
1348   _searchResultIndex = 0;
1349   _searchIsRoute = false;
1350   
1351   if (_searchResults.empty()) {
1352     return;
1353   }
1354   
1355   setScratchFromCachedSearchResult();
1356 }
1357
1358 bool GPS::SearchFilter::pass(FGPositioned* aPos) const
1359 {
1360   switch (aPos->type()) {
1361   case FGPositioned::AIRPORT:
1362   // heliport and seaport too?
1363   case FGPositioned::VOR:
1364   case FGPositioned::NDB:
1365   case FGPositioned::FIX:
1366   case FGPositioned::TACAN:
1367   case FGPositioned::WAYPOINT:
1368     return true;
1369   default:
1370     return false;
1371   }
1372 }
1373
1374 FGPositioned::Type GPS::SearchFilter::minType() const
1375 {
1376   return FGPositioned::AIRPORT;
1377 }
1378
1379 FGPositioned::Type GPS::SearchFilter::maxType() const
1380 {
1381   return FGPositioned::VOR;
1382 }
1383
1384 FGPositioned::Filter* GPS::createFilter(FGPositioned::Type aTy)
1385 {
1386   if (aTy == FGPositioned::AIRPORT) {
1387     return new FGAirport::HardSurfaceFilter();
1388   }
1389   
1390   // if we were passed INVALID, assume it means 'all types interesting to a GPS'
1391   if (aTy == FGPositioned::INVALID) {
1392     return new SearchFilter;
1393   }
1394   
1395   return new FGPositioned::TypeFilter(aTy);
1396 }
1397
1398 void GPS::search()
1399 {
1400   // parse search terms into local members, and exec the first search
1401   string sty(_scratchNode->getStringValue("type"));
1402   _searchType = FGPositioned::typeFromName(sty);
1403   _searchQuery = _scratchNode->getStringValue("query");
1404   if (_searchQuery.empty()) {
1405     SG_LOG(SG_INSTR, SG_WARN, "empty GPS search query");
1406     clearScratch();
1407     return;
1408   }
1409   
1410   _searchExact = _scratchNode->getBoolValue("exact", true);
1411   _searchResultIndex = 0;
1412   _searchIsRoute = false;
1413
1414   auto_ptr<FGPositioned::Filter> f(createFilter(_searchType));
1415   if (_searchNames) {
1416     _searchResults = FGPositioned::findAllWithName(_searchQuery, f.get(), _searchExact);
1417   } else {
1418     _searchResults = FGPositioned::findAllWithIdent(_searchQuery, f.get(), _searchExact);
1419   }
1420   
1421   bool orderByRange = _scratchNode->getBoolValue("order-by-distance", true);
1422   if (orderByRange) {
1423     FGPositioned::sortByRange(_searchResults, _indicated_pos);
1424   }
1425   
1426   if (_searchResults.empty()) {
1427     clearScratch();
1428     return;
1429   }
1430   
1431   setScratchFromCachedSearchResult();
1432 }
1433
1434 bool GPS::getScratchHasNext() const
1435 {
1436   int lastResult;
1437   if (_searchIsRoute) {
1438     lastResult = _routeMgr->numWaypts() - 1;
1439   } else {
1440     lastResult = (int) _searchResults.size() - 1;
1441   }
1442
1443   if (lastResult < 0) { // search array might be empty
1444     return false;
1445   }
1446   
1447   return (_searchResultIndex < lastResult);
1448 }
1449
1450 void GPS::setScratchFromCachedSearchResult()
1451 {
1452   int index = _searchResultIndex;
1453   
1454   if ((index < 0) || (index >= (int) _searchResults.size())) {
1455     SG_LOG(SG_INSTR, SG_WARN, "GPS:setScratchFromCachedSearchResult: index out of bounds:" << index);
1456     return;
1457   }
1458   
1459   setScratchFromPositioned(_searchResults[index], index);
1460 }
1461
1462 void GPS::setScratchFromPositioned(FGPositioned* aPos, int aIndex)
1463 {
1464   clearScratch();
1465   assert(aPos);
1466
1467   _scratchPos = aPos->geod();
1468   _scratchNode->setStringValue("name", aPos->name());
1469   _scratchNode->setStringValue("ident", aPos->ident());
1470   _scratchNode->setStringValue("type", FGPositioned::nameForType(aPos->type()));
1471     
1472   if (aIndex >= 0) {
1473     _scratchNode->setIntValue("index", aIndex);
1474   }
1475   
1476   _scratchValid = true;
1477   _scratchNode->setIntValue("result-count", _searchResults.size());
1478   
1479   switch (aPos->type()) {
1480   case FGPositioned::VOR:
1481     _scratchNode->setDoubleValue("frequency-mhz", static_cast<FGNavRecord*>(aPos)->get_freq() / 100.0);
1482     break;
1483   
1484   case FGPositioned::NDB:
1485     _scratchNode->setDoubleValue("frequency-khz", static_cast<FGNavRecord*>(aPos)->get_freq() / 100.0);
1486     break;
1487   
1488   case FGPositioned::AIRPORT:
1489     addAirportToScratch((FGAirport*)aPos);
1490     break;
1491   
1492   default:
1493       // no-op
1494       break;
1495   }
1496   
1497   // look for being on the route and set?
1498 }
1499
1500 void GPS::addAirportToScratch(FGAirport* aAirport)
1501 {
1502   for (unsigned int r=0; r<aAirport->numRunways(); ++r) {
1503     SGPropertyNode* rwyNd = _scratchNode->getChild("runways", r, true);
1504     FGRunway* rwy = aAirport->getRunwayByIndex(r);
1505     // TODO - filter out unsuitable runways in the future
1506     // based on config again
1507     
1508     rwyNd->setStringValue("id", rwy->ident().c_str());
1509     rwyNd->setIntValue("length-ft", rwy->lengthFt());
1510     rwyNd->setIntValue("width-ft", rwy->widthFt());
1511     rwyNd->setIntValue("heading-deg", rwy->headingDeg());
1512     // map surface code to a string
1513     // TODO - lighting information
1514     
1515     if (rwy->ILS()) {
1516       rwyNd->setDoubleValue("ils-frequency-mhz", rwy->ILS()->get_freq() / 100.0);
1517     }
1518   } // of runways iteration
1519 }
1520
1521
1522 void GPS::selectOBSMode()
1523 {
1524   if (!isScratchPositionValid()) {
1525     return;
1526   }
1527   
1528   _mode = "obs";
1529   _currentWaypt = new BasicWaypt(_scratchPos, _scratchNode->getStringValue("ident"), NULL);
1530   _wp0_position = _indicated_pos;
1531   wp1Changed();
1532 }
1533
1534 void GPS::selectLegMode()
1535 {
1536   if (_mode == "leg") {
1537     return;
1538   }
1539   
1540   if (!_routeMgr->isRouteActive()) {
1541     SG_LOG(SG_INSTR, SG_WARN, "GPS:selectLegMode: no active route");
1542     return;
1543   }
1544
1545   _mode = "leg";  
1546   // depending on the situation, this will either get over-written 
1547   // in routeManagerSequenced or not; either way it does no harm to
1548   // set it here.
1549   _wp0_position = _indicated_pos;
1550
1551   // not really sequenced, but does all the work of updating wp0/1
1552   routeManagerSequenced();
1553 }
1554
1555 void GPS::nextResult()
1556 {
1557   if (!getScratchHasNext()) {
1558     return;
1559   }
1560   
1561   clearScratch();
1562   if (_searchIsRoute) {
1563     setScratchFromRouteWaypoint(++_searchResultIndex);
1564   } else {
1565     ++_searchResultIndex;
1566     setScratchFromCachedSearchResult();
1567   }
1568 }
1569
1570 void GPS::previousResult()
1571 {
1572   if (_searchResultIndex <= 0) {
1573     return;
1574   }
1575   
1576   clearScratch();
1577   --_searchResultIndex;
1578   
1579   if (_searchIsRoute) {
1580     setScratchFromRouteWaypoint(_searchResultIndex);
1581   } else {
1582     setScratchFromCachedSearchResult();
1583   }
1584 }
1585
1586 void GPS::defineWaypoint()
1587 {
1588   if (!isScratchPositionValid()) {
1589     SG_LOG(SG_INSTR, SG_WARN, "GPS:defineWaypoint: invalid lat/lon");
1590     return;
1591   }
1592   
1593   string ident = _scratchNode->getStringValue("ident");
1594   if (ident.size() < 2) {
1595     SG_LOG(SG_INSTR, SG_WARN, "GPS:defineWaypoint: waypoint identifier must be at least two characters");
1596     return;
1597   }
1598     
1599 // check for duplicate idents
1600   FGPositioned::TypeFilter f(FGPositioned::WAYPOINT);
1601   FGPositionedList dups = FGPositioned::findAllWithIdent(ident, &f);
1602   if (!dups.empty()) {
1603     SG_LOG(SG_INSTR, SG_WARN, "GPS:defineWaypoint: non-unique waypoint identifier, ho-hum");
1604   }
1605   
1606   SG_LOG(SG_INSTR, SG_INFO, "GPS:defineWaypoint: creating waypoint:" << ident);
1607   FGPositionedRef wpt = FGPositioned::createUserWaypoint(ident, _scratchPos);
1608   _searchResults.clear();
1609   _searchResults.push_back(wpt);
1610   setScratchFromPositioned(wpt.get(), -1);
1611 }
1612
1613 void GPS::insertWaypointAtIndex(int aIndex)
1614 {
1615   // note we do allow index = routeMgr->size(), that's an append
1616   if ((aIndex < 0) || (aIndex > _routeMgr->numWaypts())) {
1617     throw sg_range_exception("GPS::insertWaypointAtIndex: index out of bounds");
1618   }
1619   
1620   if (!isScratchPositionValid()) {
1621     SG_LOG(SG_INSTR, SG_WARN, "GPS:insertWaypointAtIndex: invalid lat/lon");
1622     return;
1623   }
1624   
1625   string ident = _scratchNode->getStringValue("ident");
1626
1627   WayptRef wpt = new BasicWaypt(_scratchPos, ident, NULL);
1628   _routeMgr->flightPlan()->insertWayptAtIndex(wpt, aIndex);
1629 }
1630
1631 void GPS::removeWaypointAtIndex(int aIndex)
1632 {
1633   if ((aIndex < 0) || (aIndex >= _routeMgr->numWaypts())) {
1634     throw sg_range_exception("GPS::removeWaypointAtIndex: index out of bounds");
1635   }
1636   
1637   _routeMgr->removeLegAtIndex(aIndex);
1638 }
1639
1640 void GPS::tieSGGeod(SGPropertyNode* aNode, SGGeod& aRef, 
1641   const char* lonStr, const char* latStr, const char* altStr)
1642 {
1643   tie(aNode, lonStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getLongitudeDeg, &SGGeod::setLongitudeDeg));
1644   tie(aNode, latStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getLatitudeDeg, &SGGeod::setLatitudeDeg));
1645   
1646   if (altStr) {
1647     tie(aNode, altStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getElevationFt, &SGGeod::setElevationFt));
1648   }
1649 }
1650
1651 void GPS::tieSGGeodReadOnly(SGPropertyNode* aNode, SGGeod& aRef, 
1652   const char* lonStr, const char* latStr, const char* altStr)
1653 {
1654   tie(aNode, lonStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getLongitudeDeg, NULL));
1655   tie(aNode, latStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getLatitudeDeg, NULL));
1656   
1657   if (altStr) {
1658     tie(aNode, altStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getElevationFt, NULL));
1659   }
1660 }
1661
1662 // end of gps.cxx