1 // route_mgr.cxx - manage a route (i.e. a collection of waypoints)
3 // Written by Curtis Olson, started January 2004.
7 // Copyright (C) 2004 Curtis L. Olson - http://www.flightgear.org/~curt
9 // This program is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU General Public License as
11 // published by the Free Software Foundation; either version 2 of the
12 // License, or (at your option) any later version.
14 // This program is distributed in the hope that it will be useful, but
15 // WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 // General Public License for more details.
19 // You should have received a copy of the GNU General Public License
20 // along with this program; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
34 #include <simgear/compiler.h>
36 #include "route_mgr.hxx"
38 #include <simgear/misc/strutils.hxx>
39 #include <simgear/structure/exception.hxx>
41 #include <simgear/props/props_io.hxx>
42 #include <simgear/misc/sg_path.hxx>
43 #include <simgear/route/route.hxx>
44 #include <simgear/sg_inlines.h>
46 #include "Main/fg_props.hxx"
47 #include "Navaids/positioned.hxx"
48 #include "Airports/simple.hxx"
49 #include "Airports/runways.hxx"
51 #include "FDM/flight.hxx" // for getting ground speed
53 #define RM "/autopilot/route-manager/"
55 static double get_ground_speed() {
56 // starts in ft/s so we convert to kts
57 static const SGPropertyNode * speedup_node = fgGetNode("/sim/speed-up");
59 double ft_s = cur_fdm_state->get_V_ground_speed()
60 * speedup_node->getIntValue();
61 double kts = ft_s * SG_FEET_TO_METER * 3600 * SG_METER_TO_NM;
65 FGRouteMgr::FGRouteMgr() :
66 _route( new SGRoute ),
68 _driveAutopilot(true),
69 input(fgGetNode( RM "input", true )),
70 mirror(fgGetNode( RM "route", true )),
73 listener = new InputListener(this);
74 input->setStringValue("");
75 input->addChangeListener(listener);
79 FGRouteMgr::~FGRouteMgr() {
80 input->removeChangeListener(listener);
87 void FGRouteMgr::init() {
88 SGPropertyNode_ptr rm(fgGetNode(RM));
90 lon = fgGetNode( "/position/longitude-deg", true );
91 lat = fgGetNode( "/position/latitude-deg", true );
92 alt = fgGetNode( "/position/altitude-ft", true );
93 magvar = fgGetNode("/environment/magnetic-variation-deg", true);
95 // autopilot drive properties
96 _apTargetAltitudeFt = fgGetNode( "/autopilot/settings/target-altitude-ft", true );
97 _apAltitudeLock = fgGetNode( "/autopilot/locks/altitude", true );
98 _apTrueHeading = fgGetNode( "/autopilot/settings/true-heading-deg", true );
99 rm->tie("drive-autopilot", SGRawValuePointer<bool>(&_driveAutopilot));
100 _driveAutopilot = false;
102 departure = fgGetNode(RM "departure", true);
103 // init departure information from current location
104 SGGeod pos = SGGeod::fromDegFt(lon->getDoubleValue(), lat->getDoubleValue(), alt->getDoubleValue());
105 FGAirport* apt = FGAirport::findClosest(pos, 20.0);
107 departure->setStringValue("airport", apt->ident().c_str());
108 FGRunway* active = apt->getActiveRunwayForUsage();
109 departure->setStringValue("runway", active->ident().c_str());
111 departure->setStringValue("airport", "");
112 departure->setStringValue("runway", "");
115 departure->getChild("etd", 0, true);
116 departure->getChild("takeoff-time", 0, true);
118 destination = fgGetNode(RM "destination", true);
119 destination->getChild("airport", 0, true);
120 destination->getChild("runway", 0, true);
121 destination->getChild("eta", 0, true);
122 destination->getChild("touchdown-time", 0, true);
124 alternate = fgGetNode(RM "alternate", true);
125 alternate->getChild("airport", 0, true);
126 alternate->getChild("runway", 0, true);
128 cruise = fgGetNode(RM "cruise", true);
129 cruise->getChild("altitude", 0, true);
130 cruise->getChild("flight-level", 0, true);
131 cruise->getChild("speed", 0, true);
133 totalDistance = fgGetNode(RM "total-distance", true);
134 totalDistance->setDoubleValue(0.0);
136 ete = fgGetNode(RM "ete", true);
137 ete->setDoubleValue(0.0);
139 elapsedFlightTime = fgGetNode(RM "flight-time", true);
140 elapsedFlightTime->setDoubleValue(0.0);
142 active = fgGetNode(RM "active", true);
143 active->setBoolValue(false);
145 airborne = fgGetNode(RM "airborne", true);
146 airborne->setBoolValue(false);
148 rm->tie("auto-sequence", SGRawValuePointer<bool>(&_autoSequence));
150 currentWp = fgGetNode(RM "current-wp", true);
151 currentWp->setIntValue(_route->current_index());
153 // temporary distance / eta calculations, for backward-compatability
154 wp0 = fgGetNode(RM "wp", 0, true);
155 wp0->getChild("id", 0, true);
156 wp0->getChild("dist", 0, true);
157 wp0->getChild("eta", 0, true);
158 wp0->getChild("bearing-deg", 0, true);
160 wp1 = fgGetNode(RM "wp", 1, true);
161 wp1->getChild("id", 0, true);
162 wp1->getChild("dist", 0, true);
163 wp1->getChild("eta", 0, true);
165 wpn = fgGetNode(RM "wp-last", 0, true);
166 wpn->getChild("dist", 0, true);
167 wpn->getChild("eta", 0, true);
172 _pathNode = fgGetNode(RM "file-path", 0, true);
176 void FGRouteMgr::postinit() {
177 string_list *waypoints = globals->get_initial_waypoints();
179 vector<string>::iterator it;
180 for (it = waypoints->begin(); it != waypoints->end(); ++it)
184 weightOnWheels = fgGetNode("/gear/gear[0]/wow", false);
185 // check airbone flag agrees with presets
190 void FGRouteMgr::bind() { }
191 void FGRouteMgr::unbind() { }
193 void FGRouteMgr::updateTargetAltitude() {
194 if (_route->current_index() == _route->size()) {
195 altitude_set = false;
199 SGWayPoint wp = _route->get_current();
200 if (wp.get_target().getElevationM() < -9990.0) {
201 altitude_set = false;
207 if (!_driveAutopilot) {
211 _apTargetAltitudeFt->setDoubleValue(wp.get_target().getElevationFt());
213 if ( !near_ground() ) {
214 _apAltitudeLock->setStringValue( "altitude-hold" );
218 bool FGRouteMgr::isRouteActive() const
220 return active->getBoolValue();
223 void FGRouteMgr::update( double dt ) {
225 // paused, nothing to do here
229 if (!active->getBoolValue()) {
233 double groundSpeed = get_ground_speed();
234 if (airborne->getBoolValue()) {
235 time_t now = time(NULL);
236 elapsedFlightTime->setDoubleValue(difftime(now, _takeoffTime));
237 } else { // not airborne
238 if (weightOnWheels->getBoolValue() || (groundSpeed < 40)) {
242 airborne->setBoolValue(true);
243 _takeoffTime = time(NULL); // start the clock
244 departure->setIntValue("takeoff-time", _takeoffTime);
247 // basic course/distance information
248 double inboundCourse, dummy, wp_course, wp_distance;
249 SGWayPoint wp = _route->get_current();
251 wp.CourseAndDistance(_route->get_waypoint(_route->current_index() - 1),
252 &inboundCourse, &dummy);
254 wp.CourseAndDistance( lon->getDoubleValue(), lat->getDoubleValue(),
255 alt->getDoubleValue(), &wp_course, &wp_distance );
257 if (_autoSequence && (wp_distance < 2000.0)) {
258 double courseDeviation = inboundCourse - wp_course;
259 SG_NORMALIZE_RANGE(courseDeviation, -180.0, 180.0);
260 if (fabs(courseDeviation) < 90.0) {
261 SG_LOG( SG_AUTOPILOT, SG_INFO, "route manager doing sequencing");
266 if (_driveAutopilot) {
267 _apTrueHeading->setDoubleValue(wp_course);
270 // update wp0 / wp1 / wp-last for legacy users
271 wp0->setDoubleValue("dist", wp_distance * SG_METER_TO_NM);
272 wp_course -= magvar->getDoubleValue(); // expose magnetic bearing
273 wp0->setDoubleValue("bearing-deg", wp_course);
274 setETAPropertyFromDistance(wp0->getChild("eta"), wp_distance);
276 if ((_route->current_index() + 1) < _route->size()) {
277 wp = _route->get_waypoint(_route->current_index() + 1);
278 double wp1_course, wp1_distance;
279 wp.CourseAndDistance(lon->getDoubleValue(), lat->getDoubleValue(),
280 alt->getDoubleValue(), &wp1_course, &wp1_distance);
282 wp1->setDoubleValue("dist", wp1_distance * SG_METER_TO_NM);
283 setETAPropertyFromDistance(wp1->getChild("eta"), wp1_distance);
286 double totalDistanceRemaining = wp_distance; // distance to current waypoint
287 for (int i=_route->current_index() + 1; i<_route->size(); ++i) {
288 totalDistanceRemaining += _route->get_waypoint(i).get_distance();
291 wpn->setDoubleValue("dist", totalDistanceRemaining * SG_METER_TO_NM);
292 ete->setDoubleValue(totalDistanceRemaining * SG_METER_TO_NM / groundSpeed * 3600.0);
293 setETAPropertyFromDistance(wpn->getChild("eta"), totalDistanceRemaining);
295 // get time now at destination tz as tm struct
297 // convert to string ... and stash in property
298 //destination->setDoubleValue("eta", eta);
302 void FGRouteMgr::setETAPropertyFromDistance(SGPropertyNode_ptr aProp, double aDistance) {
303 double speed = get_ground_speed();
305 aProp->setStringValue("--:--");
310 double eta = aDistance * SG_METER_TO_NM / get_ground_speed();
311 if ( eta >= 100.0 ) {
312 eta = 99.999; // clamp
315 if ( eta < (1.0/6.0) ) {
316 eta *= 60.0; // within 10 minutes, bump up to min/secs
319 int major = (int)eta,
320 minor = (int)((eta - (int)eta) * 60.0);
321 snprintf( eta_str, 64, "%d:%02d", major, minor );
322 aProp->setStringValue( eta_str );
325 void FGRouteMgr::add_waypoint( const SGWayPoint& wp, int n ) {
326 _route->add_waypoint( wp, n );
328 if ((n==0) || (_route->size() == 1)) {
329 updateTargetAltitude();
334 SGWayPoint FGRouteMgr::pop_waypoint( int n ) {
337 if ( _route->size() > 0 ) {
339 n = _route->size() - 1;
340 wp = _route->get_waypoint(n);
341 _route->delete_waypoint(n);
344 updateTargetAltitude();
350 bool FGRouteMgr::build() {
355 void FGRouteMgr::new_waypoint( const string& target, int n ) {
356 SGWayPoint* wp = make_waypoint( target );
361 add_waypoint( *wp, n );
364 if ( !near_ground() ) {
365 fgSetString( "/autopilot/locks/heading", "true-heading-hold" );
370 SGWayPoint* FGRouteMgr::make_waypoint(const string& tgt ) {
374 for (unsigned int i = 0; i < target.size(); i++) {
375 target[i] = toupper(target[i]);
379 double alt = -9999.0;
380 size_t pos = target.find( '@' );
381 if ( pos != string::npos ) {
382 alt = atof( target.c_str() + pos + 1 );
383 target = target.substr( 0, pos );
384 if ( !strcmp(fgGetString("/sim/startup/units"), "feet") )
385 alt *= SG_FEET_TO_METER;
389 pos = target.find( ',' );
390 if ( pos != string::npos ) {
391 double lon = atof( target.substr(0, pos).c_str());
392 double lat = atof( target.c_str() + pos + 1);
393 return new SGWayPoint( lon, lat, alt, SGWayPoint::WGS84, target );
397 if (_route->size() > 0) {
398 SGWayPoint wp = get_waypoint(_route->size()-1);
399 basePosition = wp.get_target();
401 // route is empty, use current position
402 basePosition = SGGeod::fromDeg(
403 fgGetNode("/position/longitude-deg")->getDoubleValue(),
404 fgGetNode("/position/latitude-deg")->getDoubleValue());
407 vector<string> pieces(simgear::strutils::split(target, "/"));
410 FGPositionedRef p = FGPositioned::findClosestWithIdent(pieces.front(), basePosition);
412 SG_LOG( SG_AUTOPILOT, SG_INFO, "Unable to find FGPositioned with ident:" << pieces.front());
416 if (pieces.size() == 1) {
418 return new SGWayPoint(p->geod(), target, p->name());
421 if (pieces.size() == 3) {
422 // navaid/radial/distance-nm notation
423 double radial = atof(pieces[1].c_str()),
424 distanceNm = atof(pieces[2].c_str()),
426 radial += magvar->getDoubleValue(); // convert to true bearing
428 SGGeodesy::direct(p->geod(), radial, distanceNm * SG_NM_TO_METER, offsetPos, az2);
430 SG_LOG(SG_AUTOPILOT, SG_INFO, "final offset radial is " << radial);
433 offsetPos.setElevationM(alt);
434 } // otherwise use the elevation of navid
435 return new SGWayPoint(offsetPos, p->ident() + pieces[2], target);
438 if (pieces.size() == 2) {
439 FGAirport* apt = dynamic_cast<FGAirport*>(p.ptr());
441 SG_LOG(SG_AUTOPILOT, SG_INFO, "Waypoint is not an airport:" << pieces.front());
445 if (!apt->hasRunwayWithIdent(pieces[1])) {
446 SG_LOG(SG_AUTOPILOT, SG_INFO, "No runway: " << pieces[1] << " at " << pieces[0]);
450 FGRunway* runway = apt->getRunwayByIdent(pieces[1]);
451 SGGeod t = runway->threshold();
452 return new SGWayPoint(t.getLongitudeDeg(), t.getLatitudeDeg(), alt, SGWayPoint::WGS84, pieces[1]);
455 SG_LOG(SG_AUTOPILOT, SG_INFO, "Unable to parse waypoint:" << target);
460 // mirror internal route to the property system for inspection by other subsystems
461 void FGRouteMgr::update_mirror() {
462 mirror->removeChildren("wp");
463 for (int i = 0; i < _route->size(); i++) {
464 SGWayPoint wp = _route->get_waypoint(i);
465 SGPropertyNode *prop = mirror->getChild("wp", i, 1);
467 const SGGeod& pos(wp.get_target());
468 prop->setStringValue("id", wp.get_id().c_str());
469 prop->setStringValue("name", wp.get_name().c_str());
470 prop->setDoubleValue("longitude-deg", pos.getLongitudeDeg());
471 prop->setDoubleValue("latitude-deg",pos.getLatitudeDeg());
472 prop->setDoubleValue("altitude-m", pos.getElevationM());
473 prop->setDoubleValue("altitude-ft", pos.getElevationFt());
475 // set number as listener attachment point
476 mirror->setIntValue("num", _route->size());
480 bool FGRouteMgr::near_ground() {
481 SGPropertyNode *gear = fgGetNode( "/gear/gear/wow", false );
482 if ( !gear || gear->getType() == simgear::props::NONE )
483 return fgGetBool( "/sim/presets/onground", true );
485 if ( fgGetDouble("/position/altitude-agl-ft", 300.0)
486 < fgGetDouble("/autopilot/route-manager/min-lock-altitude-agl-ft") )
489 return gear->getBoolValue();
493 // command interface /autopilot/route-manager/input:
495 // @CLEAR ... clear route
496 // @POP ... remove first entry
497 // @DELETE3 ... delete 4th entry
498 // @INSERT2:KSFO@900 ... insert "KSFO@900" as 3rd entry
499 // KSFO@900 ... append "KSFO@900"
501 void FGRouteMgr::InputListener::valueChanged(SGPropertyNode *prop)
503 const char *s = prop->getStringValue();
504 if (!strcmp(s, "@CLEAR"))
506 else if (!strcmp(s, "@ACTIVATE"))
508 else if (!strcmp(s, "@LOAD")) {
510 } else if (!strcmp(s, "@SAVE")) {
512 } else if (!strcmp(s, "@POP"))
513 mgr->pop_waypoint(0);
514 else if (!strncmp(s, "@DELETE", 7))
515 mgr->pop_waypoint(atoi(s + 7));
516 else if (!strncmp(s, "@INSERT", 7)) {
518 int pos = strtol(s + 7, &r, 10);
524 mgr->new_waypoint(r, pos);
526 mgr->new_waypoint(s);
529 // SGWayPoint( const double lon = 0.0, const double lat = 0.0,
530 // const double alt = 0.0, const modetype m = WGS84,
531 // const string& s = "", const string& n = "" );
533 bool FGRouteMgr::activate()
535 const FGAirport* depApt = fgFindAirportID(departure->getStringValue("airport"));
537 SG_LOG(SG_AUTOPILOT, SG_ALERT,
538 "unable to activate route: departure airport is invalid:"
539 << departure->getStringValue("airport") );
543 string runwayId(departure->getStringValue("runway"));
544 FGRunway* runway = NULL;
545 if (depApt->hasRunwayWithIdent(runwayId)) {
546 runway = depApt->getRunwayByIdent(runwayId);
548 SG_LOG(SG_AUTOPILOT, SG_INFO,
549 "route-manager, departure runway not found:" << runwayId);
550 runway = depApt->getActiveRunwayForUsage();
553 SGWayPoint swp(runway->threshold(),
554 depApt->ident() + "-" + runway->ident(), runway->name());
555 add_waypoint(swp, 0);
557 const FGAirport* destApt = fgFindAirportID(destination->getStringValue("airport"));
559 SG_LOG(SG_AUTOPILOT, SG_ALERT,
560 "unable to activate route: destination airport is invalid:"
561 << destination->getStringValue("airport") );
565 runwayId = (destination->getStringValue("runway"));
566 if (destApt->hasRunwayWithIdent(runwayId)) {
567 FGRunway* runway = depApt->getRunwayByIdent(runwayId);
568 SGWayPoint swp(runway->end(),
569 destApt->ident() + "-" + runway->ident(), runway->name());
572 // quite likely, since destination runway may not be known until enroute
573 // probably want a listener on the 'destination' node to allow an enroute
575 add_waypoint(SGWayPoint(destApt->geod(), destApt->ident(), destApt->name()));
578 _route->set_current(0);
580 double routeDistanceNm = _route->total_distance() * SG_METER_TO_NM;
581 totalDistance->setDoubleValue(routeDistanceNm);
582 double cruiseSpeedKts = cruise->getDoubleValue("speed", 0.0);
583 if (cruiseSpeedKts > 1.0) {
584 // very very crude approximation, doesn't allow for climb / descent
585 // performance or anything else at all
586 ete->setDoubleValue(routeDistanceNm / cruiseSpeedKts * (60.0 * 60.0));
589 active->setBoolValue(true);
590 sequence(); // sequence will sync up wp0, wp1 and current-wp
591 SG_LOG(SG_AUTOPILOT, SG_INFO, "route-manager, activate route ok");
596 void FGRouteMgr::sequence()
598 if (!active->getBoolValue()) {
599 SG_LOG(SG_AUTOPILOT, SG_ALERT, "trying to sequence waypoints with no active route");
603 if (_route->current_index() == _route->size()) {
604 SG_LOG(SG_AUTOPILOT, SG_INFO, "reached end of active route");
606 active->setBoolValue(false);
610 _route->increment_current();
611 currentWaypointChanged();
614 void FGRouteMgr::jumpToIndex(int index)
616 if (!active->getBoolValue()) {
617 SG_LOG(SG_AUTOPILOT, SG_ALERT, "trying to sequence waypoints with no active route");
621 if ((index < 0) || (index >= _route->size())) {
622 SG_LOG(SG_AUTOPILOT, SG_ALERT, "passed invalid index (" <<
623 index << ") to FGRouteMgr::jumpToIndex");
627 if (_route->current_index() == index) {
631 _route->set_current(index);
632 currentWaypointChanged();
635 void FGRouteMgr::currentWaypointChanged()
637 SGWayPoint previous = _route->get_previous();
638 SGWayPoint cur = _route->get_current();
640 wp0->getChild("id")->setStringValue(cur.get_id());
641 if ((_route->current_index() + 1) < _route->size()) {
642 wp1->getChild("id")->setStringValue(_route->get_next().get_id());
644 wp1->getChild("id")->setStringValue("");
647 currentWp->setIntValue(_route->current_index());
648 updateTargetAltitude();
649 SG_LOG(SG_AUTOPILOT, SG_INFO, "route manager, current-wp is now " << _route->current_index());
652 int FGRouteMgr::findWaypoint(const SGGeod& aPos) const
654 for (int i=0; i<_route->size(); ++i) {
655 double d = SGGeodesy::distanceM(aPos, _route->get_waypoint(i).get_target());
656 if (d < 200.0) { // 200 metres seems close enough
664 SGWayPoint FGRouteMgr::get_waypoint( int i ) const
666 return _route->get_waypoint(i);
669 int FGRouteMgr::size() const
671 return _route->size();
674 int FGRouteMgr::currentWaypoint() const
676 return _route->current_index();
679 void FGRouteMgr::saveRoute()
681 SGPath path(_pathNode->getStringValue());
682 SG_LOG(SG_IO, SG_INFO, "Saving route to " << path.str());
684 writeProperties(path.str(), mirror, false, SGPropertyNode::ARCHIVE);
685 } catch (const sg_exception &e) {
686 SG_LOG(SG_IO, SG_WARN, "Error saving route:" << e.getMessage());
687 //guiErrorMessage("Error writing autosave.xml: ", e);
691 void FGRouteMgr::loadRoute()
694 // deactivate route first
695 active->setBoolValue(false);
697 SGPropertyNode_ptr routeData(new SGPropertyNode);
698 SGPath path(_pathNode->getStringValue());
700 SG_LOG(SG_IO, SG_INFO, "going to read flight-plan from:" << path.str());
701 readProperties(path.str(), routeData);
704 SGPropertyNode* dep = routeData->getChild("departure");
706 throw sg_io_exception("malformed route file, no departure node");
709 string depIdent = dep->getStringValue("airport");
710 const FGAirport* depApt = fgFindAirportID(depIdent);
712 throw sg_io_exception("bad route file, unknown airport:" + depIdent);
715 departure->setStringValue("runway", dep->getStringValue("runway"));
718 SGPropertyNode* dst = routeData->getChild("destination");
720 throw sg_io_exception("malformed route file, no destination node");
723 destination->setStringValue("airport", dst->getStringValue("airport"));
724 destination->setStringValue("runay", dst->getStringValue("runway"));
727 SGPropertyNode* alt = routeData->getChild("alternate");
729 alternate->setStringValue(alt->getStringValue("airport"));
730 } // of cruise data loading
733 SGPropertyNode* crs = routeData->getChild("cruise");
735 cruise->setDoubleValue(crs->getDoubleValue("speed"));
736 } // of cruise data loading
740 SGPropertyNode_ptr _route = routeData->getChild("route", 0);
741 SGGeod lastPos(depApt->geod());
743 for (int i=0; i<_route->nChildren(); ++i) {
744 SGPropertyNode_ptr wp = _route->getChild("wp", i);
745 parseRouteWaypoint(wp);
746 } // of route iteration
747 } catch (sg_exception& e) {
748 SG_LOG(SG_IO, SG_WARN, "failed to load flight-plan (from '" << e.getOrigin()
749 << "'):" << e.getMessage());
753 void FGRouteMgr::parseRouteWaypoint(SGPropertyNode* aWP)
756 if (_route->size() > 0) {
757 lastPos = get_waypoint(_route->size()-1).get_target();
759 // route is empty, use departure airport position
760 const FGAirport* apt = fgFindAirportID(departure->getStringValue("airport"));
761 assert(apt); // shouldn't have got this far with an invalid airport
762 lastPos = apt->geod();
765 SGPropertyNode_ptr altProp = aWP->getChild("altitude-ft");
766 double alt = -9999.0;
768 alt = altProp->getDoubleValue();
771 string ident(aWP->getStringValue("ident"));
772 if (aWP->hasChild("longitude-deg")) {
773 // explicit longitude/latitude
775 alt = 0.0; // don't export wyapoints with invalid altitude
778 SGWayPoint swp(aWP->getDoubleValue("longitude-deg"),
779 aWP->getDoubleValue("latitude-deg"), alt,
780 SGWayPoint::WGS84, ident, aWP->getStringValue("name"));
782 } else if (aWP->hasChild("navid")) {
783 // lookup by navid (possibly with offset)
784 string nid(aWP->getStringValue("navid"));
785 FGPositionedRef p = FGPositioned::findClosestWithIdent(nid, lastPos);
787 throw sg_io_exception("bad route file, unknown navid:" + nid);
790 SGGeod pos(p->geod());
791 if (aWP->hasChild("offset-nm") && aWP->hasChild("offset-radial")) {
792 double radialDeg = aWP->getDoubleValue("offset-radial");
793 // convert magnetic radial to a true radial!
794 radialDeg += magvar->getDoubleValue();
795 double offsetNm = aWP->getDoubleValue("offset-nm");
797 SGGeodesy::direct(p->geod(), radialDeg, offsetNm * SG_NM_TO_METER, pos, az2);
801 alt = p->elevation();
804 SGWayPoint swp(pos.getLongitudeDeg(), pos.getLatitudeDeg(), alt,
805 SGWayPoint::WGS84, ident, "");
808 // lookup by ident (symbolic waypoint)
809 FGPositionedRef p = FGPositioned::findClosestWithIdent(ident, lastPos);
811 throw sg_io_exception("bad route file, unknown waypoint:" + ident);
815 alt = p->elevation();
818 SGWayPoint swp(p->longitude(), p->latitude(), alt,
819 SGWayPoint::WGS84, p->ident(), p->name());