FlightGear next
gps.cxx
Go to the documentation of this file.
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#include <config.h>
7
8#include "gps.hxx"
9
10#include <memory>
11#include <set>
12#include <cstring>
13#include <cstdio>
14
15#include "Main/fg_props.hxx"
16#include "Main/globals.hxx" // for get_subsystem
17#include "Main/util.hxx" // for fgLowPass
19#include <Navaids/waypoint.hxx>
20#include "Navaids/navrecord.hxx"
22#include <Navaids/routePath.hxx>
24
25#include "Airports/airport.hxx"
26#include "Airports/runways.hxx"
28
29#include <simgear/math/sg_random.hxx>
30#include <simgear/sg_inlines.h>
31#include <simgear/math/sg_geodesy.hxx>
32#include <simgear/structure/exception.hxx>
33
34using std::unique_ptr;
35using std::string;
36using namespace flightgear;
37
38
39static const char* makeTTWString(double TTW)
40{
41 if ((TTW <= 0.0) || (TTW >= 356400.5)) { // 99 hours
42 return "--:--:--";
43 }
44
45 unsigned TTW_seconds = (unsigned) (TTW + 0.5);
46 unsigned TTW_minutes = 0;
47 unsigned TTW_hours = 0;
48
49 TTW_hours = TTW_seconds / 3600;
50 TTW_minutes = (TTW_seconds / 60) % 60;
51 TTW_seconds = TTW_seconds % 60;
52
53 static char TTW_str[16];
54 snprintf(TTW_str, 16, "%02u:%02u:%02u",
55 TTW_hours, TTW_minutes, TTW_seconds);
56
57 return TTW_str;
58}
59
61// configuration helper object
62
63GPS::Config::Config() :
64 _enableTurnAnticipation(false),
65 _turnRate(3.0), // degrees-per-second, so 180 degree turn takes 60 seconds
66 _overflightDistance(0.02),
67 _overflightArmDistance(1.0),
68 _overflightArmAngle(90.0),
69 _waypointAlertTime(30.0),
70 _requireHardSurface(true),
71 _cdiMaxDeflectionNm(3.0), // linear mode, 3nm at the peg
72 _driveAutopilot(true),
73 _courseSelectable(false),
74 _followLegTrackToFix(true)
75{
76}
77
78void GPS::Config::bind(GPS* aOwner, SGPropertyNode* aCfg)
79{
80 aOwner->tie(aCfg, "turn-rate-deg-sec", SGRawValuePointer<double>(&_turnRate));
81 aOwner->tie(aCfg, "enable-fly-by", SGRawValuePointer<bool>(&_enableTurnAnticipation));
82 aOwner->tie(aCfg, "wpt-alert-time", SGRawValuePointer<double>(&_waypointAlertTime));
83 aOwner->tie(aCfg, "hard-surface-runways-only", SGRawValuePointer<bool>(&_requireHardSurface));
84 aOwner->tie(aCfg, "cdi-max-deflection-nm", SGRawValuePointer<double>(&_cdiMaxDeflectionNm));
85 aOwner->tie(aCfg, "drive-autopilot", SGRawValuePointer<bool>(&_driveAutopilot));
86 aOwner->tie(aCfg, "course-selectable", SGRawValuePointer<bool>(&_courseSelectable));
87 aOwner->tie(aCfg, "follow-leg-track-to-fix", SGRawValuePointer<bool>(&_followLegTrackToFix));
88 aOwner->tie(aCfg, "over-flight-distance-nm", SGRawValuePointer<double>(&_overflightDistance));
89 aOwner->tie(aCfg, "over-flight-arm-distance-nm", SGRawValuePointer<double>(&_overflightArmDistance));
90 aOwner->tie(aCfg, "over-flight-arm-angle-deg", SGRawValuePointer<double>(&_overflightArmAngle));
91 aOwner->tie(aCfg, "delegate-sequencing", SGRawValuePointer<bool>(&_delegateSequencing));
92 aOwner->tie(aCfg, "max-fly-by-turn-angle-deg", SGRawValueMethods<GPS, double>(*aOwner, &GPS::maxFlyByTurnAngleDeg, &GPS::setFlyByMaxTurnAngle));
93}
94
95void GPS::setFlyByMaxTurnAngle(double maxAngle)
96{
97 _config.setMaxFlyByTurnAngle(maxAngle);
98 // keep the FlightPlan in sync, so RoutePath matches
99 _route->setMaxFlyByTurnAngle(maxAngle);
100}
101
103
104GPS::GPS ( SGPropertyNode *node, bool defaultGPSMode) :
105 _selectedCourse(0.0),
106 _desiredCourse(0.0),
107 _dataValid(false),
108 _lastPosValid(false),
109 _defaultGPSMode(defaultGPSMode),
110 _mode("init"),
111 _name(node->getStringValue("name", "gps")),
112 _num(node->getIntValue("number", 0)),
113 _callbackFlightPlanChanged(SGPropertyChangeCallback<GPS>(this,&GPS::routeManagerFlightPlanChanged,
114 fgGetNode("/autopilot/route-manager/signals/flightplan-changed", true))),
115 _callbackRouteActivated(SGPropertyChangeCallback<GPS>(this,&GPS::routeActivated,
116 fgGetNode("/autopilot/route-manager/active", true)))
117{
118 string branch = "/instrumentation/" + _name;
119 _gpsNode = fgGetNode(branch, _num, true );
120 _scratchNode = _gpsNode->getChild("scratch", 0, true);
121
122 SGPropertyNode *wp_node = _gpsNode->getChild("wp", 0, true);
123 _currentWayptNode = wp_node->getChild("wp", 1, true);
124
125#if FG_210_COMPAT
126 _searchIsRoute = false;
127 _searchHasNext = false;
128 _searchType = FGPositioned::INVALID;
129#endif
130}
131
133{
134}
135
136void
138{
139 _magvar_node = fgGetNode("/environment/magnetic-variation-deg", true);
140 _serviceable_node = _gpsNode->getChild("serviceable", 0, true);
141 _serviceable_node->setBoolValue(true);
142 _electrical_node = fgGetNode("/systems/electrical/outputs/gps", true);
143
144// basic GPS outputs
145 _raim_node = _gpsNode->getChild("raim", 0, true);
146 _odometer_node = _gpsNode->getChild("odometer", 0, true);
147 _trip_odometer_node = _gpsNode->getChild("trip-odometer", 0, true);
148 _true_bug_error_node = _gpsNode->getChild("true-bug-error-deg", 0, true);
149 _magnetic_bug_error_node = _gpsNode->getChild("magnetic-bug-error-deg", 0, true);
150 _eastWestVelocity = _gpsNode->getChild("ew-velocity-msec", 0, true);
151 _northSouthVelocity = _gpsNode->getChild("ns-velocity-msec", 0, true);
152
153// waypoints
154 // for compatibility, alias selected course down to wp/wp[1]/desired-course-deg
155 SGPropertyNode* wp1Crs = _currentWayptNode->getChild("desired-course-deg", 0, true);
156 wp1Crs->alias(_gpsNode->getChild("desired-course-deg", 0, true), true);
157
158 _tracking_bug_node = _gpsNode->getChild("tracking-bug", 0, true);
159
160// route properties
161 // should these move to the route manager?
162 _routeDistanceNm = _gpsNode->getChild("route-distance-nm", 0, true);
163 _routeETE = _gpsNode->getChild("ETE", 0, true);
164
165
166// navradio slaving properties
167 SGPropertyNode* toFlag = _gpsNode->getChild("to-flag", 0, true);
168 toFlag->alias(_currentWayptNode->getChild("to-flag"), true);
169
170 SGPropertyNode* fromFlag = _gpsNode->getChild("from-flag", 0, true);
171 fromFlag->alias(_currentWayptNode->getChild("from-flag"), true);
172
173 // autopilot drive properties
174 _apDrivingFlag = fgGetNode("/autopilot/settings/gps-driving-true-heading", true);
175 _apTrueHeading = fgGetNode("/autopilot/settings/true-heading-deg",true);
176
177 clearScratch();
178 clearOutput();
179}
180
181void
183{
184 clearOutput();
185}
186
187void
189{
190 _config.bind(this, _gpsNode->getChild("config", 0, true));
191
192// basic GPS outputs
193 tie(_gpsNode, "selected-course-deg", SGRawValueMethods<GPS, double>
194 (*this, &GPS::getSelectedCourse, &GPS::setSelectedCourse));
195
196 tie(_gpsNode, "desired-course-deg", SGRawValueMethods<GPS, double>
197 (*this, &GPS::getDesiredCourse, NULL));
198 _desiredCourseNode = _gpsNode->getChild("desired-course-deg", 0, true);
199
200 tieSGGeodReadOnly(_gpsNode, _indicated_pos, "indicated-longitude-deg",
201 "indicated-latitude-deg", "indicated-altitude-ft");
202
203 tie(_gpsNode, "indicated-vertical-speed", SGRawValueMethods<GPS, double>
204 (*this, &GPS::getVerticalSpeed, NULL));
205 tie(_gpsNode, "indicated-track-true-deg", SGRawValueMethods<GPS, double>
206 (*this, &GPS::getTrueTrack, NULL));
207 tie(_gpsNode, "indicated-track-magnetic-deg", SGRawValueMethods<GPS, double>
208 (*this, &GPS::getMagTrack, NULL));
209 tie(_gpsNode, "indicated-ground-speed-kt", SGRawValueMethods<GPS, double>
210 (*this, &GPS::getGroundspeedKts, NULL));
211
212// command system
213 tie(_gpsNode, "mode", SGRawValueMethods<GPS, const char*>(*this, &GPS::getMode, NULL));
214 tie(_gpsNode, "command", SGRawValueMethods<GPS, const char*>(*this, &GPS::getCommand, &GPS::setCommand));
215 tieSGGeod(_scratchNode, _scratchPos, "longitude-deg", "latitude-deg", "altitude-ft");
216
217#if FG_210_COMPAT
218 tie(_scratchNode, "valid", SGRawValueMethods<GPS, bool>(*this, &GPS::getScratchValid, NULL));
219 tie(_scratchNode, "distance-nm", SGRawValueMethods<GPS, double>(*this, &GPS::getScratchDistance, NULL));
220 tie(_scratchNode, "true-bearing-deg", SGRawValueMethods<GPS, double>(*this, &GPS::getScratchTrueBearing, NULL));
221 tie(_scratchNode, "mag-bearing-deg", SGRawValueMethods<GPS, double>(*this, &GPS::getScratchMagBearing, NULL));
222 tie(_scratchNode, "has-next", SGRawValueMethods<GPS, bool>(*this, &GPS::getScratchHasNext, NULL));
223 _scratchValid = false;
224
225 _scratchNode->setStringValue("type", "");
226 _scratchNode->setStringValue("query", "");
227#endif
228
229 SGPropertyNode *wp_node = _gpsNode->getChild("wp", 0, true);
230 SGPropertyNode* wp0_node = wp_node->getChild("wp", 0, true);
231 tieSGGeodReadOnly(wp0_node, _wp0_position, "longitude-deg", "latitude-deg", "altitude-ft");
232 tie(wp0_node, "ID", SGRawValueMethods<GPS, const char*>
233 (*this, &GPS::getWP0Ident, NULL));
234 tie(wp0_node, "name", SGRawValueMethods<GPS, const char*>
235 (*this, &GPS::getWP0Name, NULL));
236
237 tie(_currentWayptNode, "valid", SGRawValueMethods<GPS, bool>
238 (*this, &GPS::getWP1IValid, NULL));
239
240 tie(_currentWayptNode, "ID", SGRawValueMethods<GPS, const char*>
241 (*this, &GPS::getWP1Ident, NULL));
242 tie(_currentWayptNode, "name", SGRawValueMethods<GPS, const char*>
243 (*this, &GPS::getWP1Name, NULL));
244
245 tie(_currentWayptNode, "distance-nm", SGRawValueMethods<GPS, double>
246 (*this, &GPS::getWP1Distance, NULL));
247 tie(_currentWayptNode, "bearing-true-deg", SGRawValueMethods<GPS, double>
248 (*this, &GPS::getWP1Bearing, NULL));
249 tie(_currentWayptNode, "bearing-mag-deg", SGRawValueMethods<GPS, double>
250 (*this, &GPS::getWP1MagBearing, NULL));
251 tie(_currentWayptNode, "TTW-sec", SGRawValueMethods<GPS, double>
252 (*this, &GPS::getWP1TTW, NULL));
253 tie(_currentWayptNode, "TTW", SGRawValueMethods<GPS, const char*>
254 (*this, &GPS::getWP1TTWString, NULL));
255
256 tie(_currentWayptNode, "course-deviation-deg", SGRawValueMethods<GPS, double>
257 (*this, &GPS::getWP1CourseDeviation, NULL));
258 tie(_currentWayptNode, "course-error-nm", SGRawValueMethods<GPS, double>
259 (*this, &GPS::getWP1CourseErrorNm, NULL));
260 tie(_currentWayptNode, "to-flag", SGRawValueMethods<GPS, bool>
261 (*this, &GPS::getWP1ToFlag, NULL));
262 tie(_currentWayptNode, "from-flag", SGRawValueMethods<GPS, bool>
263 (*this, &GPS::getWP1FromFlag, NULL));
264
265// leg properties (only valid in DTO/LEG modes, not OBS)
266 tie(wp_node, "leg-distance-nm", SGRawValueMethods<GPS, double>(*this, &GPS::getLegDistance, NULL));
267 tie(wp_node, "leg-true-course-deg", SGRawValueMethods<GPS, double>(*this, &GPS::getLegCourse, NULL));
268 tie(wp_node, "leg-mag-course-deg", SGRawValueMethods<GPS, double>(*this, &GPS::getLegMagCourse, NULL));
269
270// navradio slaving properties
271 tie(_gpsNode, "cdi-deflection", SGRawValueMethods<GPS,double>
272 (*this, &GPS::getCDIDeflection));
273
274 _currentWpLatNode = _currentWayptNode->getNode("latitude-deg", true);
275 _currentWpLonNode = _currentWayptNode->getNode("longitude-deg", true);
276 _currentWpAltNode = _currentWayptNode->getNode("altitude-ft", true);
277}
278
279void
281{
282 // avoid shutdown crash during Untie(), due to invoking the getters to capture the
283 // current value(s)
284 _currentWaypt = _prevWaypt = nullptr;
285
286 _tiedProperties.Untie();
287 _gpsNode.clear();
288 _currentWayptNode.clear();
289 _currentWpLatNode.clear();
290 _currentWpLonNode.clear();
291 _currentWpAltNode.clear();
292}
293
294void
295GPS::clearOutput()
296{
297 _dataValid = false;
298 _last_speed_kts = 0.0;
299 _last_pos = SGGeod();
300 _lastPosValid = false;
301 _indicated_pos = SGGeod();
302 _last_vertical_speed = 0.0;
303 _last_true_track = 0.0;
304 _lastEWVelocity = _lastNSVelocity = 0.0;
305 _currentWaypt = _prevWaypt = NULL;
306 _legDistanceNm = -1.0;
307
308 _raim_node->setDoubleValue(0.0);
309 _indicated_pos = SGGeod();
310 _odometer_node->setDoubleValue(0);
311 _trip_odometer_node->setDoubleValue(0);
312 _tracking_bug_node->setDoubleValue(0);
313 _true_bug_error_node->setDoubleValue(0);
314 _magnetic_bug_error_node->setDoubleValue(0);
315 _northSouthVelocity->setDoubleValue(0.0);
316 _eastWestVelocity->setDoubleValue(0.0);
317}
318
319void
320GPS::update (double delta_time_sec)
321{
322 if (!_defaultGPSMode) {
323 // If it's off, don't bother.
324 // check if value is defined, since many aircraft don't define an output
325 // for the GPS, but expect the default one to work.
326 bool elecOn = !_electrical_node->hasValue() || _electrical_node->getBoolValue();
327 if (!_serviceable_node->getBoolValue() || !elecOn) {
328 clearOutput();
329 return;
330 }
331 }
332
333 if (delta_time_sec <= 0.0) {
334 return; // paused, don't bother
335 }
336
337 _raim_node->setDoubleValue(1.0);
338 _indicated_pos = globals->get_aircraft_position();
339 updateBasicData(delta_time_sec);
340
341 if (_dataValid) {
342 if (_wayptController.get()) {
343 _wayptController->update(delta_time_sec);
344 updateCurrentWpNode(_wayptController->position());
345 _desiredCourse = getLegMagCourse();
346
347 _gpsNode->setStringValue("rnav-controller-status", _wayptController->status());
348
349 if (_wayptController->isDone()) {
350 doSequence();
351 }
352 updateRouteData();
353 }
354
355
356 updateTrackingBug();
357 driveAutopilot();
358 }
359
360 if (_dataValid && (_mode == "init")) {
361 // will select LEG mode if the route is active
362 routeManagerFlightPlanChanged(nullptr);
363 auto routeMgr = globals->get_subsystem<FGRouteMgr>();
364
365 if (!routeMgr || !routeMgr->isRouteActive()) {
366 // initialise in OBS mode, with waypt set to the nearest airport.
367 // keep in mind at this point, _dataValid is not set
369 FGPositionedRef apt = FGPositioned::findClosest(_indicated_pos, 20.0, &f);
370 if (apt) {
371 selectOBSMode(new flightgear::NavaidWaypoint(apt, nullptr));
372 } else {
373 selectOBSMode(nullptr);
374 }
375 }
376
377 if (_mode != "init")
378 {
379 // allow a realistic delay in the future, here
380 }
381 } // of init mode check
382
383 _last_pos = _indicated_pos;
384 _lastPosValid = !(_last_pos == SGGeod());
385}
386
388{
389 if (_route) {
390 _route->removeDelegate(this);
391 _route = nullptr;
392 }
393 _wayptController.reset();
394}
395
396void GPS::routeManagerFlightPlanChanged(SGPropertyNode*)
397{
398 if (_route) {
399 _route->removeDelegate(this);
400 }
401
402 SG_LOG(SG_INSTR, SG_DEBUG, "GPS saw route-manager flight-plan replaced.");
403 auto routeMgr = globals->get_subsystem<FGRouteMgr>();
404 if (!routeMgr) {
405 return;
406 }
407
408 _route = routeMgr->flightPlan();
409 if (_route) {
410 _route->addDelegate(this);
411 }
412
413 if (routeMgr->isRouteActive()) {
414 selectLegMode();
415 } else {
416 selectOBSMode(_currentWaypt); // revert to OBS on current waypoint
417 }
418}
419
420void GPS::routeActivated(SGPropertyNode* aNode)
421{
422 // if the delegate is handling this, don't do anything else ourselves
423 if (_config.delegateDoesSequencing()) {
424 return;
425 }
426
427 bool isActive = aNode->getBoolValue();
428 if (isActive) {
429 SG_LOG(SG_INSTR, SG_INFO, "GPS::route activated, switching to LEG mode");
430 selectLegMode();
431
432 // if we've already passed the current waypoint, sequence.
433 if (_dataValid && getWP1FromFlag()) {
434 SG_LOG(SG_INSTR, SG_INFO, "GPS::route activated, FROM wp1, sequencing");
435 sequence();
436 }
437 } else if (_mode == "leg") {
438 SG_LOG(SG_INSTR, SG_INFO, "GPS::route deactivated, switching to OBS mode");
439 selectOBSMode(_currentWaypt);
440 }
441}
442
443
445// implement the RNAV interface
447{
448 if (!_dataValid) {
449 return SGGeod();
450 }
451
452 return _indicated_pos;
453}
454
456{
457 return _last_true_track;
458}
459
461{
462 return _last_speed_kts;
463}
464
466{
467 return _last_vertical_speed;
468}
469
471{
472 return _magvar_node->getDoubleValue();
473}
474
476{
477 return _config.overflightDistanceNm() * SG_NM_TO_METER;
478}
479
481{
482 return _config.overflightArmDistanceNm() * SG_NM_TO_METER;
483}
484
486{
487 return _config.overflightArmAngleDeg();
488}
489
491{
492 return _selectedCourse;
493}
494
496{
497 return _config.maxFlyByTurnAngleDeg();
498}
499
500std::optional<double> GPS::nextLegTrack()
501{
502 auto next = _route->nextLeg();
503 if (!next)
504 return {};
505
506 return next->courseDeg();
507}
508
509std::optional<RNAV::LegData> GPS::previousLegData()
510{
511 // if the previous controller computed valid data,
512 // use that. This ensures fly-by turns work out, especially
513 if (_wp0Data.has_value())
514 return _wp0Data;
515
516 // if we did not get data from the previous controller (eg, waypoint
517 // jumped or first waypoint), just compute the position
518 FlightPlan::Leg* leg = _route->previousLeg();
519 if (!leg) {
520 return {}; // no data
521 }
522
523 LegData legData;
524 Waypt* waypt = leg->waypoint();
525 legData.position = waypt->position();
526
527 // ensure computations use runway end, not threshold
528 if (waypt->type() == "runway") {
529 RunwayWaypt* rwpt = static_cast<RunwayWaypt*>(waypt);
530 legData.position = rwpt->runway()->end();
531 }
532
533 return legData;
534}
535
536bool GPS::canFlyBy() const
537{
538 return _config.turnAnticipationEnabled();
539}
540
542
543void
544GPS::updateBasicData(double dt)
545{
546 if (!_lastPosValid) {
547 return;
548 }
549
550 double distance_m;
551 double track2_deg;
552 SGGeodesy::inverse(_last_pos, _indicated_pos, _last_true_track, track2_deg, distance_m );
553
554// detect repositions
555// setting this value high enough hypersonic aircraft but not spaceships
556 if ((distance_m / dt) > 100000.0) {
557 SG_LOG(SG_INSTR, SG_DEBUG, "GPS detected reposition, resetting data");
558 _dataValid = false;
559 return;
560 }
561
562 double speed_kt = ((distance_m * SG_METER_TO_NM) * ((1 / dt) * 3600.0));
563 double vertical_speed_mpm = ((_indicated_pos.getElevationM() - _last_pos.getElevationM()) * 60 / dt);
564 _last_vertical_speed = vertical_speed_mpm * SG_METER_TO_FEET;
565
566 speed_kt = fgGetLowPass(_last_speed_kts, speed_kt, dt/5.0);
567 _last_speed_kts = speed_kt;
568
569 SGGeod g = _indicated_pos;
570 g.setLongitudeDeg(_last_pos.getLongitudeDeg());
571 double northSouthM = dist(SGVec3d::fromGeod(_last_pos), SGVec3d::fromGeod(g));
572 northSouthM = copysign(northSouthM, _indicated_pos.getLatitudeDeg() - _last_pos.getLatitudeDeg());
573
574 double nsMSec = fgGetLowPass(_lastNSVelocity, northSouthM / dt, dt/2.0);
575 _lastNSVelocity = nsMSec;
576 _northSouthVelocity->setDoubleValue(nsMSec);
577
578 g = _indicated_pos;
579 g.setLatitudeDeg(_last_pos.getLatitudeDeg());
580 double eastWestM = dist(SGVec3d::fromGeod(_last_pos), SGVec3d::fromGeod(g));
581 eastWestM = copysign(eastWestM, _indicated_pos.getLongitudeDeg() - _last_pos.getLongitudeDeg());
582
583 double ewMSec = fgGetLowPass(_lastEWVelocity, eastWestM / dt, dt/2.0);
584 _lastEWVelocity = ewMSec;
585 _eastWestVelocity->setDoubleValue(ewMSec);
586
587 double odometer = _odometer_node->getDoubleValue();
588 _odometer_node->setDoubleValue(odometer + distance_m * SG_METER_TO_NM);
589 odometer = _trip_odometer_node->getDoubleValue();
590 _trip_odometer_node->setDoubleValue(odometer + distance_m * SG_METER_TO_NM);
591
592 if (!_dataValid) {
593 _dataValid = true;
594 }
595}
596
597void
598GPS::updateTrackingBug()
599{
600 double tracking_bug = _tracking_bug_node->getDoubleValue();
601 double true_bug_error = tracking_bug - getTrueTrack();
602 double magnetic_bug_error = tracking_bug - getMagTrack();
603
604 // Get the errors into the (-180,180) range.
605 SG_NORMALIZE_RANGE(true_bug_error, -180.0, 180.0);
606 SG_NORMALIZE_RANGE(magnetic_bug_error, -180.0, 180.0);
607
608 _true_bug_error_node->setDoubleValue(true_bug_error);
609 _magnetic_bug_error_node->setDoubleValue(magnetic_bug_error);
610}
611
612void GPS::currentWaypointChanged()
613{
614 _wp0Data.reset();
615 if (!_route) {
616 return;
617 }
618
619 int index = _route->currentIndex(),
620 count = _route->numLegs();
621 if ((index < 0) || (index >= count)) {
622 _currentWaypt=nullptr;
623 _prevWaypt=nullptr;
624 // no active leg on the route
625 return;
626 }
627
628 if (index > 0) {
629 _prevWaypt = _route->previousLeg()->waypoint();
630 if (_prevWaypt->flag(WPT_DYNAMIC)) {
631 _wp0_position = _indicated_pos;
632 } else {
633 _wp0_position = _prevWaypt->position();
634 }
635 }
636
637 _currentWaypt = _route->currentLeg()->waypoint();
638 if (_wayptController && (_wayptController->waypoint() == _prevWaypt)) {
639 // capture leg data form the controller, before we destroy it
640 _wp0Data = _wayptController->legData();
641 }
642
643 wp1Changed(); // rebuild the active controller
644 _desiredCourse = getLegMagCourse();
645 _desiredCourseNode->fireValueChanged();
646}
647
648
649void GPS::waypointsChanged()
650{
651 if (_mode != "leg") {
652 return;
653 }
654
655 SG_LOG(SG_INSTR, SG_DEBUG, "GPS route edited while in LEG mode, updating waypoints");
656 currentWaypointChanged();
657}
658
659void GPS::doSequence()
660{
661 if (!_route) {
662 return;
663 }
664
665 if (_config.delegateDoesSequencing()) {
666 // new style behaviour : let the delegate handle it
667 _route->sequence();
668 } else {
669 // backwards compatible behaviour : we do it ourselves
670 if (_mode == "dto") {
671 SG_LOG(SG_INSTR, SG_INFO, "GPS DTO reached destination point");
672 if (_route && _route->isActive()) {
673 // check if our destination point is on the active route,
674 // in which case resume leg mode
675 const int index = _route->findWayptIndex(_currentWaypt->position());
676 if (index >= 0) {
677 SG_LOG(SG_INSTR, SG_INFO, "GPS DTO resuming LEG mode at " << _route->legAtIndex(index)->waypoint()->ident());
678 _mode = "leg";
679 _route->setCurrentIndex(index);
680 return;
681 }
682 }
683
684 // if we didn't enter LEG mode, drop back to OBS
685 selectOBSMode(_currentWaypt);
686 } else if (_mode == "leg") {
687 const int nextIndex = _route->currentIndex() + 1;
688 if (nextIndex >= _route->numLegs()) {
689 SG_LOG(SG_INSTR, SG_INFO, "GPS built-in sequencing, reached end of route,");
690 _route->finish();
691 selectOBSMode(_currentWaypt);
692 } else {
693 // sequence ourselves
694 _route->setCurrentIndex(nextIndex);
695 }
696 }
697 }
698}
699
700void GPS::cleared()
701{
702 // if the aircraft delegates handle sequencing, don't do
703 // anything here
704 if (_config.delegateDoesSequencing()) {
705 return;
706 }
707
708 // backwards compatability : select OBS mode
709 if (_mode == "leg") {
710 selectOBSMode(_currentWaypt);
711 }
712}
713
714void GPS::endOfFlightPlan()
715{
716 // backwards compatability - same logic as 'cleared', revert to OBS mode
717 // if we're in leg mode
718 cleared();
719}
720
722{
723 return computeTurnRadiusNm(groundSpeedKts);
724}
725
726double GPS::computeTurnRadiusNm(double aGroundSpeedKts) const
727{
728 // turn time is seconds to execute a 360 turn.
729 double turnTime = 360.0 / _config.turnRateDegSec();
730
731 // c is ground distance covered in that time (circumference of the circle)
732 double c = turnTime * (aGroundSpeedKts / 3600.0); // convert knts to nm/sec
733
734 // divide by 2PI to go from circumference -> radius
735 return c / (2 * M_PI);
736}
737
738void GPS::updateRouteData()
739{
740 double totalDistance = 0.0;
741 // waypt controller might be null: Sentry-Id: FLIGHTGEAR-FFX
742 if (_wayptController) {
743 totalDistance = _wayptController->distanceToWayptM() * SG_METER_TO_NM;
744 }
745
746 if (_route) {
747 // walk all waypoints from wp2 to route end, and sum
748 for (int i=_route->currentIndex()+1; i<_route->numLegs(); ++i) {
749 auto leg = _route->legAtIndex(i);
750 // omit missed-approach waypoints in distance calculation
751 if (leg->waypoint()->flag(WPT_MISS))
752 continue;
753
754 totalDistance += leg->distanceNm();
755 }
756 }
757
758 _routeDistanceNm->setDoubleValue(totalDistance * SG_METER_TO_NM);
759 if (_last_speed_kts > 1.0) {
760 double TTW = ((totalDistance * SG_METER_TO_NM) / _last_speed_kts) * 3600.0;
761 _routeETE->setStringValue(makeTTWString(TTW));
762 }
763}
764
765void GPS::driveAutopilot()
766{
767 if (!_config.driveAutopilot() || !_defaultGPSMode) {
768 _apDrivingFlag->setBoolValue(false);
769 return;
770 }
771
772 // compatibility feature - allow the route-manager / GPS to drive the
773 // generic autopilot heading hold *in leg mode only*
774
775 bool drive = _mode == "leg";
776 _apDrivingFlag->setBoolValue(drive);
777
778 if (drive) {
779 // FIXME: we want to set desired track, not heading, here
780 _apTrueHeading->setDoubleValue(getWP1Bearing());
781 }
782}
783
784void GPS::wp1Changed()
785{
786 if (!_currentWaypt)
787 return;
788
789 if (_mode == "leg") {
790 _wayptController.reset(WayptController::createForWaypt(this, _currentWaypt));
791 if (_currentWaypt->type() == "hold") {
792 // pass the hold count through
793 auto leg = _route->currentLeg();
794 auto holdCtl = static_cast<flightgear::HoldCtl*>(_wayptController.get());
795 holdCtl->setHoldCount(leg->holdCount());
796 }
797 } else if (_mode == "obs") {
798 _wayptController.reset(new OBSController(this, _currentWaypt));
799 } else if (_mode == "dto") {
800 _wayptController.reset(new DirectToController(this, _currentWaypt, _wp0_position));
801 }
802
803 const bool ok = _wayptController && _wayptController->init();
804 if (!ok) {
805 SG_LOG(SG_AUTOPILOT, SG_WARN, "GPS failed to init RNAV controller for waypoint " << _currentWaypt->ident());
806 _wayptController.reset();
807 _mode.clear();
808 _gpsNode->setStringValue("rnav-controller-status", "");
809 return;
810 }
811
812 _gpsNode->setStringValue("rnav-controller-status", _wayptController->status());
813
814 if (_mode == "obs") {
815 _legDistanceNm = -1.0;
816 } else {
817 _wayptController->update(0.0);
818 _gpsNode->setStringValue("rnav-controller-status", _wayptController->status());
819
820 _legDistanceNm = _wayptController->distanceToWayptM() * SG_METER_TO_NM;
821
822 // synchronise these properties immediately
823 updateCurrentWpNode(_wayptController->position());
824
825 _desiredCourse = getLegMagCourse();
826 }
827}
828
829void GPS::updateCurrentWpNode(const SGGeod& p)
830{
831 _currentWpLatNode->setDoubleValue(p.getLatitudeDeg());
832 _currentWpLonNode->setDoubleValue(p.getLongitudeDeg());
833 _currentWpAltNode->setDoubleValue(p.getElevationFt());
834}
835
837// property getter/setters
838
839void GPS::setSelectedCourse(double crs)
840{
841 if (_selectedCourse == crs) {
842 return;
843 }
844
845 _selectedCourse = crs;
846 if ((_mode == "obs") || _config.courseSelectable()) {
847 _desiredCourse = _selectedCourse;
848 _desiredCourseNode->fireValueChanged();
849 }
850}
851
852double GPS::getLegDistance() const
853{
854 if (!_dataValid || (_mode == "obs")) {
855 return -1;
856 }
857
858 return _legDistanceNm;
859}
860
861double GPS::getLegCourse() const
862{
863 if (!_dataValid || !_wayptController.get()) {
864 return -9999.0;
865 }
866
867 return _wayptController->targetTrackDeg();
868}
869
870double GPS::getLegMagCourse() const
871{
872 if (!_dataValid) {
873 return 0.0;
874 }
875
876 double m = getLegCourse() - _magvar_node->getDoubleValue();
877 SG_NORMALIZE_RANGE(m, 0.0, 360.0);
878 return m;
879}
880
881double GPS::getMagTrack() const
882{
883 if (!_dataValid) {
884 return 0.0;
885 }
886
887 double m = getTrueTrack() - _magvar_node->getDoubleValue();
888 SG_NORMALIZE_RANGE(m, 0.0, 360.0);
889 return m;
890}
891
892double GPS::getCDIDeflection() const
893{
894 if (!_dataValid) {
895 return 0.0;
896 }
897
898 double defl;
899 if (_config.cdiDeflectionIsAngular()) {
900 defl = getWP1CourseDeviation();
901 SG_CLAMP_RANGE(defl, -10.0, 10.0); // as in navradio.cxx
902 } else {
903 double fullScale = _config.cdiDeflectionLinearPeg();
904 double normError = getWP1CourseErrorNm() / fullScale;
905 SG_CLAMP_RANGE(normError, -1.0, 1.0);
906 defl = normError * 10.0; // re-scale to navradio limits, i.e [-10.0 .. 10.0]
907 }
908
909 return defl;
910}
911
912const char* GPS::getWP0Ident() const
913{
914 if (!_dataValid || (_mode != "leg") || !_prevWaypt) {
915 return "";
916 }
917
918// work around std::string::c_str() storage lifetime with libc++
919// real fix is to allow tie-ing with std::string instead of char*
920 static char identBuf[16];
921 strncpy(identBuf, _prevWaypt->ident().c_str(), 15);
922
923 return identBuf;
924}
925
926const char* GPS::getWP0Name() const
927{
928 if (!_dataValid || !_prevWaypt || !_prevWaypt->source()) {
929 return "";
930 }
931
932 return _prevWaypt->source()->name().c_str();
933}
934
935bool GPS::getWP1IValid() const
936{
937 return _dataValid && _currentWaypt.get();
938}
939
940const char* GPS::getWP1Ident() const
941{
942 if (!_dataValid || !_currentWaypt) {
943 return "";
944 }
945
946// work around std::string::c_str() storage lifetime with libc++
947// real fix is to allow tie-ing with std::string instead of char*
948 static char identBuf[16];
949 strncpy(identBuf, _currentWaypt->ident().c_str(), 15);
950
951 return identBuf;
952}
953
954const char* GPS::getWP1Name() const
955{
956 if (!_dataValid || !_currentWaypt || !_currentWaypt->source()) {
957 return "";
958 }
959
960 return _currentWaypt->source()->name().c_str();
961}
962
963double GPS::getWP1Distance() const
964{
965 if (!_dataValid || !_wayptController.get()) {
966 return -1.0;
967 }
968
969 return _wayptController->distanceToWayptM() * SG_METER_TO_NM;
970}
971
972double GPS::getWP1TTW() const
973{
974 if (!_dataValid || !_wayptController.get()) {
975 return -1.0;
976 }
977
978 return _wayptController->timeToWaypt();
979}
980
981const char* GPS::getWP1TTWString() const
982{
983 double t = getWP1TTW();
984 if (t <= 0.0) {
985 return "";
986 }
987
988 return makeTTWString(t);
989}
990
991double GPS::getWP1Bearing() const
992{
993 if (!_dataValid || !_wayptController.get()) {
994 return -9999.0;
995 }
996
997 return _wayptController->trueBearingDeg();
998}
999
1000double GPS::getWP1MagBearing() const
1001{
1002 if (!_dataValid || !_wayptController.get()) {
1003 return -9999.0;
1004 }
1005
1006 double magBearing = _wayptController->trueBearingDeg() - _magvar_node->getDoubleValue();
1007 SG_NORMALIZE_RANGE(magBearing, 0.0, 360.0);
1008 return magBearing;
1009}
1010
1011double GPS::getWP1CourseDeviation() const
1012{
1013 if (!_dataValid || !_wayptController.get()) {
1014 return 0.0;
1015 }
1016
1017 return _wayptController->courseDeviationDeg();
1018}
1019
1020double GPS::getWP1CourseErrorNm() const
1021{
1022 if (!_dataValid || !_wayptController.get()) {
1023 return 0.0;
1024 }
1025
1026 return _wayptController->xtrackErrorNm();
1027}
1028
1029bool GPS::getWP1ToFlag() const
1030{
1031 if (!_dataValid || !_wayptController.get()) {
1032 return false;
1033 }
1034
1035 return _wayptController->toFlag();
1036}
1037
1038bool GPS::getWP1FromFlag() const
1039{
1040 if (!_dataValid || !_wayptController.get()) {
1041 return false;
1042 }
1043
1044 return !getWP1ToFlag();
1045}
1046
1047#if FG_210_COMPAT
1048double GPS::getScratchDistance() const
1049{
1050 if (!_scratchValid) {
1051 return 0.0;
1052 }
1053
1054 return SGGeodesy::distanceNm(_indicated_pos, _scratchPos);
1055}
1056
1057double GPS::getScratchTrueBearing() const
1058{
1059 if (!_scratchValid) {
1060 return 0.0;
1061 }
1062
1063 return SGGeodesy::courseDeg(_indicated_pos, _scratchPos);
1064}
1065
1066double GPS::getScratchMagBearing() const
1067{
1068 if (!_scratchValid) {
1069 return 0.0;
1070 }
1071
1072 double crs = getScratchTrueBearing() - _magvar_node->getDoubleValue();
1073 SG_NORMALIZE_RANGE(crs, 0.0, 360.0);
1074 return crs;
1075}
1076
1077#endif
1078
1080// scratch system
1081
1082void GPS::setCommand(const char* aCmd)
1083{
1084 SG_LOG(SG_INSTR, SG_DEBUG, "GPS command:" << aCmd);
1085
1086 if (!strcmp(aCmd, "direct")) {
1087 directTo();
1088 } else if (!strcmp(aCmd, "obs")) {
1089 // valid scratch data is always used, if it's not valid we will
1090 // use the current waypoint if one exists
1091 selectOBSMode(isScratchPositionValid() ? nullptr : _currentWaypt);
1092 } else if (!strcmp(aCmd, "leg")) {
1093 selectLegMode();
1094 } else if (!strcmp(aCmd, "exit-hold")) {
1095 commandExitHold();
1096#if FG_210_COMPAT
1097 } else if (!strcmp(aCmd, "load-route-wpt")) {
1098 loadRouteWaypoint();
1099 } else if (!strcmp(aCmd, "nearest")) {
1100 loadNearest();
1101 } else if (!strcmp(aCmd, "search")) {
1102 _searchNames = false;
1103 search();
1104 } else if (!strcmp(aCmd, "search-names")) {
1105 _searchNames = true;
1106 search();
1107 } else if (!strcmp(aCmd, "next")) {
1108 nextResult();
1109 } else if (!strcmp(aCmd, "previous")) {
1110 previousResult();
1111 } else if (!strcmp(aCmd, "define-user-wpt")) {
1112 defineWaypoint();
1113 } else if (!strcmp(aCmd, "route-insert-before")) {
1114 int index = _scratchNode->getIntValue("index");
1115 if (index < 0 || (_route->numLegs() == 0)) {
1116 index = _route->numLegs();
1117 } else if (index >= _route->numLegs()) {
1118 SG_LOG(SG_INSTR, SG_WARN, "GPS:route-insert-before, bad index:" << index);
1119 return;
1120 }
1121
1122 insertWaypointAtIndex(index);
1123 } else if (!strcmp(aCmd, "route-insert-after")) {
1124 int index = _scratchNode->getIntValue("index");
1125 if (index < 0 || (_route->numLegs() == 0)) {
1126 index = _route->numLegs();
1127 } else if (index >= _route->numLegs()) {
1128 SG_LOG(SG_INSTR, SG_WARN, "GPS:route-insert-after, bad index:" << index);
1129 return;
1130 } else {
1131 ++index;
1132 }
1133
1134 insertWaypointAtIndex(index);
1135 } else if (!strcmp(aCmd, "route-delete")) {
1136 int index = _scratchNode->getIntValue("index");
1137 if (index < 0) {
1138 index = _route->numLegs();
1139 } else if (index >= _route->numLegs()) {
1140 SG_LOG(SG_INSTR, SG_WARN, "GPS:route-delete, bad index:" << index);
1141 return;
1142 }
1143
1144 removeWaypointAtIndex(index);
1145#endif
1146 } else {
1147 SG_LOG(SG_INSTR, SG_WARN, "GPS:unrecognized command:" << aCmd);
1148 }
1149}
1150
1151void GPS::clearScratch()
1152{
1153 _scratchPos = SGGeod::fromDegFt(-9999.0, -9999.0, -9999.0);
1154 _scratchNode->setBoolValue("valid", false);
1155#if FG_210_COMPAT
1156 _scratchNode->setStringValue("type", "");
1157 _scratchNode->setStringValue("query", "");
1158#endif
1159}
1160
1161bool GPS::isScratchPositionValid() const
1162{
1163 if ((_scratchPos.getLongitudeDeg() < -9990.0) ||
1164 (_scratchPos.getLatitudeDeg() < -9990.0)) {
1165 return false;
1166 }
1167
1168 return true;
1169}
1170
1171FGPositionedRef GPS::positionedFromScratch() const
1172{
1173 if (!isScratchPositionValid()) {
1174 return NULL;
1175 }
1176
1177 std::string ident(_scratchNode->getStringValue("ident"));
1178 return FGPositioned::findClosestWithIdent(ident, _scratchPos);
1179}
1180
1181void GPS::directTo()
1182{
1183 if (!isScratchPositionValid()) {
1184 return;
1185 }
1186
1187 _prevWaypt = NULL;
1188 _wp0_position = _indicated_pos;
1189
1190 FGPositionedRef pos = positionedFromScratch();
1191 if (pos) {
1192 _currentWaypt = new NavaidWaypoint(pos, NULL);
1193 } else {
1194 _currentWaypt = new BasicWaypt(_scratchPos, _scratchNode->getStringValue("ident"), NULL);
1195 }
1196
1197 _mode = "dto";
1198 wp1Changed();
1199}
1200
1201void GPS::selectOBSMode(flightgear::Waypt* waypt)
1202{
1203 if (!waypt && isScratchPositionValid()) {
1204 FGPositionedRef pos = positionedFromScratch();
1205 if (pos) {
1206 waypt = new NavaidWaypoint(pos, NULL);
1207 } else {
1208 waypt = new BasicWaypt(_scratchPos, _scratchNode->getStringValue("ident"), NULL);
1209 }
1210 }
1211
1212 _mode = "obs";
1213 _prevWaypt = NULL;
1214 _wp0_position = _indicated_pos;
1215 _currentWaypt = waypt;
1216 wp1Changed();
1217}
1218
1219void GPS::selectLegMode()
1220{
1221 if (_mode == "leg") {
1222 return;
1223 }
1224
1225 if (!_route) {
1226 SG_LOG(SG_INSTR, SG_WARN, "GPS:selectLegMode: no active route");
1227 return;
1228 }
1229
1230 _mode = "leg";
1231
1232// clear any previous leg data which might be hanging around
1233// note this means you can mess up fly-by by toggling into and out LEG
1234// mode, but this seems reasonable
1235 _wp0Data.reset();
1236
1237 // depending on the situation, this will either get over-written
1238 // in routeManagerSequenced or not; either way it does no harm to
1239 // set it here.
1240 _wp0_position = _indicated_pos;
1241
1242 // not really sequenced, but does all the work of updating wp0/1
1243 currentWaypointChanged();
1244}
1245
1246#if FG_210_COMPAT
1247
1248void GPS::loadRouteWaypoint()
1249{
1250 _scratchValid = false;
1251 int index = _scratchNode->getIntValue("index", -9999);
1252 clearScratch();
1253
1254 if ((index < 0) || (index >= _route->numLegs())) { // no index supplied, use current wp
1255 index = _route->currentIndex();
1256 }
1257
1258 _searchIsRoute = true;
1259 setScratchFromRouteWaypoint(index);
1260}
1261
1262void GPS::setScratchFromRouteWaypoint(int aIndex)
1263{
1264 assert(_searchIsRoute);
1265 if ((aIndex < 0) || (aIndex >= _route->numLegs())) {
1266 SG_LOG(SG_INSTR, SG_WARN, "GPS:setScratchFromRouteWaypoint: route-index out of bounds");
1267 return;
1268 }
1269
1270 _searchResultIndex = aIndex;
1271 WayptRef wp = _route->legAtIndex(aIndex)->waypoint();
1272 _scratchNode->setStringValue("ident", wp->ident());
1273 _scratchPos = wp->position();
1274 _scratchValid = true;
1275 _scratchNode->setIntValue("index", aIndex);
1276}
1277
1278void GPS::loadNearest()
1279{
1280 string sty(_scratchNode->getStringValue("type"));
1282 if (ty == FGPositioned::INVALID) {
1283 SG_LOG(SG_INSTR, SG_WARN, "GPS:loadNearest: request type is invalid:" << sty);
1284 return;
1285 }
1286
1287 unique_ptr<FGPositioned::Filter> f(createFilter(ty));
1288 int limitCount = _scratchNode->getIntValue("max-results", 1);
1289 double cutoffDistance = _scratchNode->getDoubleValue("cutoff-nm", 400.0);
1290
1291 SGGeod searchPos = _indicated_pos;
1292 if (isScratchPositionValid()) {
1293 searchPos = _scratchPos;
1294 }
1295
1296 clearScratch(); // clear now, regardless of whether we find a match or not
1297
1298 _searchResults =
1299 FGPositioned::findClosestN(searchPos, limitCount, cutoffDistance, f.get());
1300 _searchResultIndex = 0;
1301 _searchIsRoute = false;
1302
1303 if (_searchResults.empty()) {
1304 return;
1305 }
1306
1307 setScratchFromCachedSearchResult();
1308}
1309
1310bool GPS::SearchFilter::pass(FGPositioned* aPos) const
1311{
1312 switch (aPos->type()) {
1314 // heliport and seaport too?
1315 case FGPositioned::VOR:
1316 case FGPositioned::NDB:
1317 case FGPositioned::FIX:
1320 return true;
1321 default:
1322 return false;
1323 }
1324}
1325
1326FGPositioned::Type GPS::SearchFilter::minType() const
1327{
1328 return FGPositioned::AIRPORT;
1329}
1330
1331FGPositioned::Type GPS::SearchFilter::maxType() const
1332{
1333 return FGPositioned::VOR;
1334}
1335
1336FGPositioned::Filter* GPS::createFilter(FGPositioned::Type aTy)
1337{
1338 if (aTy == FGPositioned::AIRPORT) {
1339 return new FGAirport::HardSurfaceFilter();
1340 }
1341
1342 // if we were passed INVALID, assume it means 'all types interesting to a GPS'
1343 if (aTy == FGPositioned::INVALID) {
1344 return new SearchFilter;
1345 }
1346
1347 return new FGPositioned::TypeFilter(aTy);
1348}
1349
1350void GPS::search()
1351{
1352 // parse search terms into local members, and exec the first search
1353 string sty(_scratchNode->getStringValue("type"));
1354 _searchType = FGPositioned::typeFromName(sty);
1355 _searchQuery = _scratchNode->getStringValue("query");
1356 if (_searchQuery.empty()) {
1357 SG_LOG(SG_INSTR, SG_WARN, "empty GPS search query");
1358 clearScratch();
1359 return;
1360 }
1361
1362 _searchExact = _scratchNode->getBoolValue("exact", true);
1363 _searchResultIndex = 0;
1364 _searchIsRoute = false;
1365
1366 unique_ptr<FGPositioned::Filter> f(createFilter(_searchType));
1367 if (_searchNames) {
1368 _searchResults = FGPositioned::findAllWithName(_searchQuery, f.get(), _searchExact);
1369 } else {
1370 _searchResults = FGPositioned::findAllWithIdent(_searchQuery, f.get(), _searchExact);
1371 }
1372
1373 bool orderByRange = _scratchNode->getBoolValue("order-by-distance", true);
1374 if (orderByRange) {
1375 FGPositioned::sortByRange(_searchResults, _indicated_pos);
1376 }
1377
1378 if (_searchResults.empty()) {
1379 clearScratch();
1380 return;
1381 }
1382
1383 setScratchFromCachedSearchResult();
1384}
1385
1386bool GPS::getScratchHasNext() const
1387{
1388 int lastResult;
1389 if (_searchIsRoute) {
1390 lastResult = _route->numLegs() - 1;
1391 } else {
1392 lastResult = (int) _searchResults.size() - 1;
1393 }
1394
1395 if (lastResult < 0) { // search array might be empty
1396 return false;
1397 }
1398
1399 return (_searchResultIndex < lastResult);
1400}
1401
1402void GPS::setScratchFromCachedSearchResult()
1403{
1404 int index = _searchResultIndex;
1405
1406 if ((index < 0) || (index >= (int) _searchResults.size())) {
1407 SG_LOG(SG_INSTR, SG_WARN, "GPS:setScratchFromCachedSearchResult: index out of bounds:" << index);
1408 return;
1409 }
1410
1411 setScratchFromPositioned(_searchResults[index], index);
1412}
1413
1414void GPS::setScratchFromPositioned(FGPositioned* aPos, int aIndex)
1415{
1416 clearScratch();
1417 assert(aPos);
1418
1419 _scratchPos = aPos->geod();
1420 _scratchNode->setStringValue("name", aPos->name());
1421 _scratchNode->setStringValue("ident", aPos->ident());
1422 _scratchNode->setStringValue("type", FGPositioned::nameForType(aPos->type()));
1423
1424 if (aIndex >= 0) {
1425 _scratchNode->setIntValue("index", aIndex);
1426 }
1427
1428 _scratchValid = true;
1429 _scratchNode->setIntValue("result-count", _searchResults.size());
1430
1431 switch (aPos->type()) {
1432 case FGPositioned::VOR:
1433 _scratchNode->setDoubleValue("frequency-mhz", static_cast<FGNavRecord*>(aPos)->get_freq() / 100.0);
1434 break;
1435
1436 case FGPositioned::NDB:
1437 _scratchNode->setDoubleValue("frequency-khz", static_cast<FGNavRecord*>(aPos)->get_freq() / 100.0);
1438 break;
1439
1441 addAirportToScratch((FGAirport*)aPos);
1442 break;
1443
1444 default:
1445 // no-op
1446 break;
1447 }
1448
1449 // look for being on the route and set?
1450}
1451
1452void GPS::addAirportToScratch(FGAirport* aAirport)
1453{
1454 for (unsigned int r=0; r<aAirport->numRunways(); ++r) {
1455 SGPropertyNode* rwyNd = _scratchNode->getChild("runways", r, true);
1456 FGRunway* rwy = aAirport->getRunwayByIndex(r);
1457 // TODO: filter out unsuitable runways in the future
1458 // based on config again
1459
1460 rwyNd->setStringValue("id", rwy->ident().c_str());
1461 rwyNd->setIntValue("length-ft", rwy->lengthFt());
1462 rwyNd->setIntValue("width-ft", rwy->widthFt());
1463 rwyNd->setIntValue("heading-deg", rwy->headingDeg());
1464 // map surface code to a string
1465 // TODO: lighting information
1466
1467 if (rwy->ILS()) {
1468 rwyNd->setDoubleValue("ils-frequency-mhz", rwy->ILS()->get_freq() / 100.0);
1469 }
1470 } // of runways iteration
1471}
1472
1473void GPS::nextResult()
1474{
1475 if (!getScratchHasNext()) {
1476 return;
1477 }
1478
1479 clearScratch();
1480 if (_searchIsRoute) {
1481 setScratchFromRouteWaypoint(++_searchResultIndex);
1482 } else {
1483 ++_searchResultIndex;
1484 setScratchFromCachedSearchResult();
1485 }
1486}
1487
1488void GPS::previousResult()
1489{
1490 if (_searchResultIndex <= 0) {
1491 return;
1492 }
1493
1494 clearScratch();
1495 --_searchResultIndex;
1496
1497 if (_searchIsRoute) {
1498 setScratchFromRouteWaypoint(_searchResultIndex);
1499 } else {
1500 setScratchFromCachedSearchResult();
1501 }
1502}
1503
1504void GPS::defineWaypoint()
1505{
1506 if (!isScratchPositionValid()) {
1507 SG_LOG(SG_INSTR, SG_WARN, "GPS:defineWaypoint: invalid lat/lon");
1508 return;
1509 }
1510
1511 string ident = _scratchNode->getStringValue("ident");
1512 if (ident.size() < 2) {
1513 SG_LOG(SG_INSTR, SG_WARN, "GPS:defineWaypoint: waypoint identifier must be at least two characters");
1514 return;
1515 }
1516
1517 // check for duplicate idents
1518 FGPositioned::TypeFilter f(FGPositioned::WAYPOINT);
1520 if (!dups.empty()) {
1521 SG_LOG(SG_INSTR, SG_WARN, "GPS:defineWaypoint: non-unique waypoint identifier, ho-hum");
1522 }
1523
1524 SG_LOG(SG_INSTR, SG_INFO, "GPS:defineWaypoint: creating waypoint:" << ident);
1526 _searchResults.clear();
1527 _searchResults.push_back(wpt);
1528 setScratchFromPositioned(wpt.get(), -1);
1529}
1530
1531void GPS::insertWaypointAtIndex(int aIndex)
1532{
1533 // note we do allow index = routeMgr->size(), that's an append
1534 if ((aIndex < 0) || (aIndex > _route->numLegs())) {
1535 throw sg_range_exception("GPS::insertWaypointAtIndex: index out of bounds");
1536 }
1537
1538 if (!isScratchPositionValid()) {
1539 SG_LOG(SG_INSTR, SG_WARN, "GPS:insertWaypointAtIndex: invalid lat/lon");
1540 return;
1541 }
1542
1543 string ident = _scratchNode->getStringValue("ident");
1544
1545 WayptRef wpt = new BasicWaypt(_scratchPos, ident, NULL);
1546 _route->insertWayptAtIndex(wpt, aIndex);
1547}
1548
1549void GPS::removeWaypointAtIndex(int aIndex)
1550{
1551 if ((aIndex < 0) || (aIndex >= _route->numLegs())) {
1552 throw sg_range_exception("GPS::removeWaypointAtIndex: index out of bounds");
1553 }
1554
1555 _route->deleteIndex(aIndex);
1556}
1557
1558
1559#endif // of FG_210_COMPAT
1560
1561void GPS::tieSGGeod(SGPropertyNode* aNode, SGGeod& aRef,
1562 const char* lonStr, const char* latStr, const char* altStr)
1563{
1564 tie(aNode, lonStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getLongitudeDeg, &SGGeod::setLongitudeDeg));
1565 tie(aNode, latStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getLatitudeDeg, &SGGeod::setLatitudeDeg));
1566
1567 if (altStr) {
1568 tie(aNode, altStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getElevationFt, &SGGeod::setElevationFt));
1569 }
1570}
1571
1572void GPS::tieSGGeodReadOnly(SGPropertyNode* aNode, SGGeod& aRef,
1573 const char* lonStr, const char* latStr, const char* altStr)
1574{
1575 tie(aNode, lonStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getLongitudeDeg, NULL));
1576 tie(aNode, latStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getLatitudeDeg, NULL));
1577
1578 if (altStr) {
1579 tie(aNode, altStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getElevationFt, NULL));
1580 }
1581}
1582
1583void GPS::commandExitHold()
1584{
1585 if (_currentWaypt && (_currentWaypt->type() == "hold")) {
1586 auto holdCtl = static_cast<flightgear::HoldCtl*>(_wayptController.get());
1587 holdCtl->exitHold();
1588 } else {
1589 SG_LOG(SG_INSTR, SG_WARN, "GPS:exit hold requested, but not currently in a hold");
1590 }
1591}
1592
1593
1594// Register the subsystem.
1595#if 0
1596SGSubsystemMgr::InstancedRegistrant<GPS> registrantGPS(
1597 SGSubsystemMgr::FDM,
1598 {{"instrumentation", SGSubsystemMgr::Dependency::HARD}});
1599#endif
1600
1601// end of gps.cxx
#define p(x)
#define M_PI
Definition FGJSBBase.h:50
#define i(x)
unsigned int numRunways() const
Definition airport.cxx:102
FGRunwayRef getRunwayByIndex(unsigned int aIndex) const
Definition airport.cxx:116
int get_freq() const
Definition navrecord.hxx:76
static Type typeFromName(const std::string &aName)
Map a candidate type string to a real type.
static FGPositionedList findAllWithName(const std::string &aName, Filter *aFilter=NULL, bool aExact=true)
As above, but searches names instead of idents.
static FGPositionedRef createWaypoint(FGPositioned::Type aType, const std::string &aIdent, const SGGeod &aPos, bool isTemporary=false, const std::string &aName={})
virtual const std::string & name() const
Return the name of this positioned.
static FGPositionedRef findClosestWithIdent(const std::string &aIdent, const SGGeod &aPos, Filter *aFilter=NULL)
virtual const SGGeod & geod() const
static void sortByRange(FGPositionedList &, const SGGeod &aPos)
Sort an FGPositionedList by distance from a position.
static FGPositionedList findAllWithIdent(const std::string &aIdent, Filter *aFilter=NULL, bool aExact=true)
Find all items with the specified ident.
static const char * nameForType(Type aTy)
Map a type to a human-readable string.
static FGPositionedList findClosestN(const SGGeod &aPos, unsigned int aN, double aCutoffNm, Filter *aFilter=NULL)
Find the closest N items to a position, which pass the specified filter A cutoff range in NM must be ...
Type type() const
const std::string & ident() const
static FGPositionedRef findClosest(const SGGeod &aPos, double aCutoffNm, Filter *aFilter=NULL)
Find the closest item to a position, which pass the specified filter A cutoff range in NM must be spe...
Top level route manager class.
Definition route_mgr.hxx:27
double headingDeg() const
Runway heading in degrees.
double lengthFt() const
double widthFt() const
SGGeod end() const
Get the 'far' end - this is equivalent to calling pointOnCenterline(lengthFt());.
Definition runways.cxx:94
FGNavRecord * ILS() const
Definition runways.cxx:120
Model a GPS radio.
Definition gps.hxx:63
void unbind() override
Definition gps.cxx:280
std::optional< double > nextLegTrack() override
Definition gps.cxx:500
double magvarDeg() override
Magnetic variation at current position.
Definition gps.cxx:470
double maxFlyByTurnAngleDeg() const override
maximum angle in degrees where flyBy is permitted.
Definition gps.cxx:495
double trackDeg() override
True track in degrees.
Definition gps.cxx:455
double vspeedFPM() override
Vertical speed in ft/minute.
Definition gps.cxx:465
double selectedMagCourse() override
device selected course (eg, from autopilot / MCP / OBS) in degrees
Definition gps.cxx:490
double groundSpeedKts() override
Ground speed (along the track) in knots.
Definition gps.cxx:460
std::optional< LegData > previousLegData() override
device leg previous waypoint position(eg, from route manager)
Definition gps.cxx:509
void shutdown() override
Definition gps.cxx:387
SGGeod position() override
Definition gps.cxx:446
void init() override
Definition gps.cxx:137
bool canFlyBy() const override
Definition gps.cxx:536
void reinit() override
Definition gps.cxx:182
void bind() override
Definition gps.cxx:188
double overflightArmAngleDeg() override
angle for overflight sequencing.
Definition gps.cxx:485
void update(double delta_time_sec) override
Definition gps.cxx:320
double overflightArmDistanceM() override
minimum distance to a waypoint for overflight sequencing.
Definition gps.cxx:480
GPS(SGPropertyNode *node, bool defaultGPSMode=false)
Definition gps.cxx:104
double overflightDistanceM() override
minimum distance to switch next waypoint.
Definition gps.cxx:475
virtual ~GPS()
Definition gps.cxx:132
virtual void sequence()
Invoked when the C++ code determines the active leg is done / next leg should be sequenced.
flight-plan leg encapsulation
Waypoint based upon a navaid.
Definition waypoint.hxx:63
double turnRadiusNm()
compute turn radius based on current ground-speed
Waypoint based upon a runway.
Definition waypoint.hxx:117
FGRunway * runway() const
Definition waypoint.hxx:129
static WayptController * createForWaypt(RNAV *rnav, const WayptRef &aWpt)
Static factory method, given a waypoint, return a controller bound to it, of the appropriate type.
Abstract base class for waypoints (and things that are treated similarly by navigation systems).
Definition route.hxx:105
virtual std::string type() const =0
virtual SGGeod position() const =0
FGGlobals * globals
Definition globals.cxx:142
static const char * makeTTWString(double TTW)
Definition gps.cxx:39
const double g(9.80665)
FlightPlan.hxx - defines a full flight-plan object, including departure, cruise, arrival information ...
Definition Addon.cxx:53
SGSharedPtr< FGPositioned > FGPositionedRef
Definition airways.cxx:49
SGSharedPtr< Waypt > WayptRef
@ WPT_DYNAMIC
waypoint position is dynamic, i.e moves based on other criteria, such as altitude,...
Definition route.hxx:49
@ WPT_MISS
segment is part of missed approach
Definition route.hxx:46
std::vector< FGPositionedRef > FGPositionedList
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
Definition proptest.cpp:27
double fgGetLowPass(double current, double target, double timeratio)
Move a value towards a target.
Definition util.cxx:46