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 <boost/algorithm/string/case_conv.hpp>
40 #include <simgear/misc/strutils.hxx>
41 #include <simgear/structure/exception.hxx>
42 #include <simgear/misc/sgstream.hxx>
44 #include <simgear/props/props_io.hxx>
45 #include <simgear/misc/sg_path.hxx>
46 #include <simgear/route/route.hxx>
47 #include <simgear/sg_inlines.h>
49 #include "Main/fg_props.hxx"
50 #include "Navaids/positioned.hxx"
51 #include "Airports/simple.hxx"
52 #include "Airports/runways.hxx"
54 #include "FDM/flight.hxx" // for getting ground speed
56 #define RM "/autopilot/route-manager/"
58 static double get_ground_speed() {
59 // starts in ft/s so we convert to kts
60 static const SGPropertyNode * speedup_node = fgGetNode("/sim/speed-up");
62 double ft_s = cur_fdm_state->get_V_ground_speed()
63 * speedup_node->getIntValue();
64 double kts = ft_s * SG_FEET_TO_METER * 3600 * SG_METER_TO_NM;
68 FGRouteMgr::FGRouteMgr() :
69 _route( new SGRoute ),
70 input(fgGetNode( RM "input", true )),
71 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 departure = fgGetNode(RM "departure", true);
96 departure->tie("airport", SGRawValueMethods<FGRouteMgr, const char*>(*this,
97 &FGRouteMgr::getDepartureICAO, &FGRouteMgr::setDepartureICAO));
98 departure->tie("name", SGRawValueMethods<FGRouteMgr, const char*>(*this,
99 &FGRouteMgr::getDepartureName, NULL));
101 // init departure information from current location
102 SGGeod pos = SGGeod::fromDegFt(lon->getDoubleValue(), lat->getDoubleValue(), alt->getDoubleValue());
103 _departure = FGAirport::findClosest(pos, 20.0);
105 FGRunway* active = _departure->getActiveRunwayForUsage();
106 departure->setStringValue("runway", active->ident().c_str());
108 departure->setStringValue("runway", "");
111 departure->getChild("etd", 0, true);
112 departure->getChild("takeoff-time", 0, true);
114 destination = fgGetNode(RM "destination", true);
115 destination->getChild("airport", 0, true);
117 destination->tie("airport", SGRawValueMethods<FGRouteMgr, const char*>(*this,
118 &FGRouteMgr::getDestinationICAO, &FGRouteMgr::setDestinationICAO));
119 destination->tie("name", SGRawValueMethods<FGRouteMgr, const char*>(*this,
120 &FGRouteMgr::getDestinationName, NULL));
122 destination->getChild("runway", 0, true);
123 destination->getChild("eta", 0, true);
124 destination->getChild("touchdown-time", 0, true);
126 alternate = fgGetNode(RM "alternate", true);
127 alternate->getChild("airport", 0, true);
128 alternate->getChild("runway", 0, true);
130 cruise = fgGetNode(RM "cruise", true);
131 cruise->getChild("altitude-ft", 0, true);
132 cruise->setDoubleValue("altitude-ft", 10000.0);
133 cruise->getChild("flight-level", 0, true);
134 cruise->getChild("speed-kts", 0, true);
135 cruise->setDoubleValue("speed-kts", 160.0);
137 totalDistance = fgGetNode(RM "total-distance", true);
138 totalDistance->setDoubleValue(0.0);
140 ete = fgGetNode(RM "ete", true);
141 ete->setDoubleValue(0.0);
143 elapsedFlightTime = fgGetNode(RM "flight-time", true);
144 elapsedFlightTime->setDoubleValue(0.0);
146 active = fgGetNode(RM "active", true);
147 active->setBoolValue(false);
149 airborne = fgGetNode(RM "airborne", true);
150 airborne->setBoolValue(false);
152 _edited = fgGetNode(RM "signals/edited", true);
153 _finished = fgGetNode(RM "signals/finished", true);
155 _currentWpt = fgGetNode(RM "current-wp", true);
156 _currentWpt->tie(SGRawValueMethods<FGRouteMgr, int>
157 (*this, &FGRouteMgr::currentWaypoint, &FGRouteMgr::jumpToIndex));
159 // temporary distance / eta calculations, for backward-compatability
160 wp0 = fgGetNode(RM "wp", 0, true);
161 wp0->getChild("id", 0, true);
162 wp0->getChild("dist", 0, true);
163 wp0->getChild("eta", 0, true);
164 wp0->getChild("bearing-deg", 0, true);
166 wp1 = fgGetNode(RM "wp", 1, true);
167 wp1->getChild("id", 0, true);
168 wp1->getChild("dist", 0, true);
169 wp1->getChild("eta", 0, true);
171 wpn = fgGetNode(RM "wp-last", 0, true);
172 wpn->getChild("dist", 0, true);
173 wpn->getChild("eta", 0, true);
176 _route->set_current(0);
179 _pathNode = fgGetNode(RM "file-path", 0, true);
183 void FGRouteMgr::postinit() {
184 string_list *waypoints = globals->get_initial_waypoints();
186 vector<string>::iterator it;
187 for (it = waypoints->begin(); it != waypoints->end(); ++it)
191 weightOnWheels = fgGetNode("/gear/gear[0]/wow", false);
192 // check airbone flag agrees with presets
197 void FGRouteMgr::bind() { }
198 void FGRouteMgr::unbind() { }
200 bool FGRouteMgr::isRouteActive() const
202 return active->getBoolValue();
205 void FGRouteMgr::update( double dt ) {
207 // paused, nothing to do here
211 if (!active->getBoolValue()) {
215 double groundSpeed = get_ground_speed();
216 if (airborne->getBoolValue()) {
217 time_t now = time(NULL);
218 elapsedFlightTime->setDoubleValue(difftime(now, _takeoffTime));
219 } else { // not airborne
220 if (weightOnWheels->getBoolValue() || (groundSpeed < 40)) {
224 airborne->setBoolValue(true);
225 _takeoffTime = time(NULL); // start the clock
226 departure->setIntValue("takeoff-time", _takeoffTime);
229 // basic course/distance information
230 double wp_course, wp_distance;
231 SGWayPoint wp = _route->get_current();
232 wp.CourseAndDistance( lon->getDoubleValue(), lat->getDoubleValue(),
233 alt->getDoubleValue(), &wp_course, &wp_distance );
235 // update wp0 / wp1 / wp-last for legacy users
236 wp0->setDoubleValue("dist", wp_distance * SG_METER_TO_NM);
237 wp_course -= magvar->getDoubleValue(); // expose magnetic bearing
238 wp0->setDoubleValue("bearing-deg", wp_course);
239 setETAPropertyFromDistance(wp0->getChild("eta"), wp_distance);
241 if ((_route->current_index() + 1) < _route->size()) {
242 wp = _route->get_waypoint(_route->current_index() + 1);
243 double wp1_course, wp1_distance;
244 wp.CourseAndDistance(lon->getDoubleValue(), lat->getDoubleValue(),
245 alt->getDoubleValue(), &wp1_course, &wp1_distance);
247 wp1->setDoubleValue("dist", wp1_distance * SG_METER_TO_NM);
248 setETAPropertyFromDistance(wp1->getChild("eta"), wp1_distance);
251 double totalDistanceRemaining = wp_distance; // distance to current waypoint
252 for (int i=_route->current_index() + 1; i<_route->size(); ++i) {
253 totalDistanceRemaining += _route->get_waypoint(i).get_distance();
256 wpn->setDoubleValue("dist", totalDistanceRemaining * SG_METER_TO_NM);
257 ete->setDoubleValue(totalDistanceRemaining * SG_METER_TO_NM / groundSpeed * 3600.0);
258 setETAPropertyFromDistance(wpn->getChild("eta"), totalDistanceRemaining);
260 // get time now at destination tz as tm struct
262 // convert to string ... and stash in property
263 //destination->setDoubleValue("eta", eta);
267 void FGRouteMgr::setETAPropertyFromDistance(SGPropertyNode_ptr aProp, double aDistance) {
268 double speed = get_ground_speed();
270 aProp->setStringValue("--:--");
275 double eta = aDistance * SG_METER_TO_NM / get_ground_speed();
276 if ( eta >= 100.0 ) {
277 eta = 99.999; // clamp
280 if ( eta < (1.0/6.0) ) {
281 eta *= 60.0; // within 10 minutes, bump up to min/secs
284 int major = (int)eta,
285 minor = (int)((eta - (int)eta) * 60.0);
286 snprintf( eta_str, 64, "%d:%02d", major, minor );
287 aProp->setStringValue( eta_str );
290 void FGRouteMgr::add_waypoint( const SGWayPoint& wp, int n )
292 _route->add_waypoint( wp, n );
294 if ((n >= 0) && (_route->current_index() > n)) {
295 _route->set_current(_route->current_index() + 1);
301 void FGRouteMgr::waypointsChanged()
303 double routeDistanceNm = _route->total_distance() * SG_METER_TO_NM;
304 totalDistance->setDoubleValue(routeDistanceNm);
305 double cruiseSpeedKts = cruise->getDoubleValue("speed", 0.0);
306 if (cruiseSpeedKts > 1.0) {
307 // very very crude approximation, doesn't allow for climb / descent
308 // performance or anything else at all
309 ete->setDoubleValue(routeDistanceNm / cruiseSpeedKts * (60.0 * 60.0));
313 _edited->fireValueChanged();
317 SGWayPoint FGRouteMgr::pop_waypoint( int n ) {
318 if ( _route->size() <= 0 ) {
323 n = _route->size() - 1;
326 if (_route->current_index() > n) {
327 _route->set_current(_route->current_index() - 1);
330 SGWayPoint wp = _route->get_waypoint(n);
331 _route->delete_waypoint(n);
338 bool FGRouteMgr::build() {
343 void FGRouteMgr::new_waypoint( const string& target, int n ) {
344 SGWayPoint* wp = make_waypoint( target );
349 add_waypoint( *wp, n );
354 SGWayPoint* FGRouteMgr::make_waypoint(const string& tgt ) {
355 string target(boost::to_upper_copy(tgt));
358 double alt = -9999.0;
360 size_t pos = target.find( '@' );
361 if ( pos != string::npos ) {
362 alt = atof( target.c_str() + pos + 1 );
363 target = target.substr( 0, pos );
364 if ( !strcmp(fgGetString("/sim/startup/units"), "feet") )
365 alt *= SG_FEET_TO_METER;
369 pos = target.find( ',' );
370 if ( pos != string::npos ) {
371 double lon = atof( target.substr(0, pos).c_str());
372 double lat = atof( target.c_str() + pos + 1);
374 char ew = (lon < 0.0) ? 'W' : 'E';
375 char ns = (lat < 0.0) ? 'S' : 'N';
376 snprintf(buf, 32, "%c%03d%c%03d", ew, (int) fabs(lon), ns, (int)fabs(lat));
377 return new SGWayPoint( lon, lat, alt, SGWayPoint::WGS84, buf);
381 if (_route->size() > 0) {
382 SGWayPoint wp = get_waypoint(_route->size()-1);
383 basePosition = wp.get_target();
385 // route is empty, use current position
386 basePosition = SGGeod::fromDeg(
387 fgGetNode("/position/longitude-deg")->getDoubleValue(),
388 fgGetNode("/position/latitude-deg")->getDoubleValue());
391 vector<string> pieces(simgear::strutils::split(target, "/"));
394 FGPositionedRef p = FGPositioned::findClosestWithIdent(pieces.front(), basePosition);
396 SG_LOG( SG_AUTOPILOT, SG_INFO, "Unable to find FGPositioned with ident:" << pieces.front());
400 SGGeod geod = SGGeod::fromGeodM(p->geod(), alt);
401 if (pieces.size() == 1) {
403 return new SGWayPoint(geod, target, p->name());
406 if (pieces.size() == 3) {
407 // navaid/radial/distance-nm notation
408 double radial = atof(pieces[1].c_str()),
409 distanceNm = atof(pieces[2].c_str()),
411 radial += magvar->getDoubleValue(); // convert to true bearing
413 SGGeodesy::direct(geod, radial, distanceNm * SG_NM_TO_METER, offsetPos, az2);
414 offsetPos.setElevationM(alt);
416 SG_LOG(SG_AUTOPILOT, SG_INFO, "final offset radial is " << radial);
417 return new SGWayPoint(offsetPos, p->ident() + pieces[2], target);
420 if (pieces.size() == 2) {
421 FGAirport* apt = dynamic_cast<FGAirport*>(p.ptr());
423 SG_LOG(SG_AUTOPILOT, SG_INFO, "Waypoint is not an airport:" << pieces.front());
427 if (!apt->hasRunwayWithIdent(pieces[1])) {
428 SG_LOG(SG_AUTOPILOT, SG_INFO, "No runway: " << pieces[1] << " at " << pieces[0]);
432 FGRunway* runway = apt->getRunwayByIdent(pieces[1]);
433 SGGeod t = runway->threshold();
434 return new SGWayPoint(t.getLongitudeDeg(), t.getLatitudeDeg(), alt, SGWayPoint::WGS84, pieces[1]);
437 SG_LOG(SG_AUTOPILOT, SG_INFO, "Unable to parse waypoint:" << target);
442 // mirror internal route to the property system for inspection by other subsystems
443 void FGRouteMgr::update_mirror() {
444 mirror->removeChildren("wp");
445 for (int i = 0; i < _route->size(); i++) {
446 SGWayPoint wp = _route->get_waypoint(i);
447 SGPropertyNode *prop = mirror->getChild("wp", i, 1);
449 const SGGeod& pos(wp.get_target());
450 prop->setStringValue("id", wp.get_id().c_str());
451 prop->setStringValue("name", wp.get_name().c_str());
452 prop->setDoubleValue("longitude-deg", pos.getLongitudeDeg());
453 prop->setDoubleValue("latitude-deg",pos.getLatitudeDeg());
454 prop->setDoubleValue("altitude-m", pos.getElevationM());
455 prop->setDoubleValue("altitude-ft", pos.getElevationFt());
457 // set number as listener attachment point
458 mirror->setIntValue("num", _route->size());
461 // command interface /autopilot/route-manager/input:
463 // @CLEAR ... clear route
464 // @POP ... remove first entry
465 // @DELETE3 ... delete 4th entry
466 // @INSERT2:KSFO@900 ... insert "KSFO@900" as 3rd entry
467 // KSFO@900 ... append "KSFO@900"
469 void FGRouteMgr::InputListener::valueChanged(SGPropertyNode *prop)
471 const char *s = prop->getStringValue();
472 if (strlen(s) == 0) {
476 if (!strcmp(s, "@CLEAR"))
478 else if (!strcmp(s, "@ACTIVATE"))
480 else if (!strcmp(s, "@LOAD")) {
482 } else if (!strcmp(s, "@SAVE")) {
484 } else if (!strcmp(s, "@POP")) {
485 SG_LOG(SG_AUTOPILOT, SG_WARN, "route-manager @POP command is deprecated");
486 } else if (!strcmp(s, "@NEXT")) {
487 mgr->jumpToIndex(mgr->currentWaypoint() + 1);
488 } else if (!strcmp(s, "@PREVIOUS")) {
489 mgr->jumpToIndex(mgr->currentWaypoint() - 1);
490 } else if (!strncmp(s, "@JUMP", 5)) {
491 mgr->jumpToIndex(atoi(s + 5));
492 } else if (!strncmp(s, "@DELETE", 7))
493 mgr->pop_waypoint(atoi(s + 7));
494 else if (!strncmp(s, "@INSERT", 7)) {
496 int pos = strtol(s + 7, &r, 10);
502 mgr->new_waypoint(r, pos);
504 mgr->new_waypoint(s);
507 // SGWayPoint( const double lon = 0.0, const double lat = 0.0,
508 // const double alt = 0.0, const modetype m = WGS84,
509 // const string& s = "", const string& n = "" );
511 bool FGRouteMgr::activate()
513 if (isRouteActive()) {
514 SG_LOG(SG_AUTOPILOT, SG_WARN, "duplicate route-activation, no-op");
518 // only add departure waypoint if we're not airborne, so that
519 // in-air route activation doesn't confuse matters.
520 if (weightOnWheels->getBoolValue() && _departure) {
521 string runwayId(departure->getStringValue("runway"));
522 FGRunway* runway = NULL;
523 if (_departure->hasRunwayWithIdent(runwayId)) {
524 runway = _departure->getRunwayByIdent(runwayId);
526 SG_LOG(SG_AUTOPILOT, SG_INFO,
527 "route-manager, departure runway not found:" << runwayId);
528 runway = _departure->getActiveRunwayForUsage();
531 SGWayPoint swp(runway->threshold(),
532 _departure->ident() + "-" + runway->ident(), runway->name());
533 add_waypoint(swp, 0);
537 string runwayId = (destination->getStringValue("runway"));
538 if (_destination->hasRunwayWithIdent(runwayId)) {
539 FGRunway* runway = _destination->getRunwayByIdent(runwayId);
540 SGWayPoint swp(runway->end(),
541 _destination->ident() + "-" + runway->ident(), runway->name());
544 // quite likely, since destination runway may not be known until enroute
545 // probably want a listener on the 'destination' node to allow an enroute
547 add_waypoint(SGWayPoint(_destination->geod(), _destination->ident(), _destination->name()));
551 _route->set_current(0);
552 active->setBoolValue(true);
553 SG_LOG(SG_AUTOPILOT, SG_INFO, "route-manager, activate route ok");
558 void FGRouteMgr::sequence()
560 if (!active->getBoolValue()) {
561 SG_LOG(SG_AUTOPILOT, SG_ALERT, "trying to sequence waypoints with no active route");
565 if (checkFinished()) {
569 _route->increment_current();
570 currentWaypointChanged();
571 _currentWpt->fireValueChanged();
574 bool FGRouteMgr::checkFinished()
576 int lastWayptIndex = _route->size() - 1;
577 if (_route->current_index() < lastWayptIndex) {
581 SG_LOG(SG_AUTOPILOT, SG_INFO, "reached end of active route");
582 _finished->fireValueChanged();
583 active->setBoolValue(false);
587 void FGRouteMgr::jumpToIndex(int index)
589 if ((index < 0) || (index >= _route->size())) {
590 SG_LOG(SG_AUTOPILOT, SG_ALERT, "passed invalid index (" <<
591 index << ") to FGRouteMgr::jumpToIndex");
595 if (_route->current_index() == index) {
599 _route->set_current(index);
600 currentWaypointChanged();
601 _currentWpt->fireValueChanged();
604 void FGRouteMgr::currentWaypointChanged()
606 SGWayPoint previous = _route->get_previous();
607 SGWayPoint cur = _route->get_current();
609 wp0->getChild("id")->setStringValue(cur.get_id());
610 if ((_route->current_index() + 1) < _route->size()) {
611 wp1->getChild("id")->setStringValue(_route->get_next().get_id());
613 wp1->getChild("id")->setStringValue("");
616 SG_LOG(SG_AUTOPILOT, SG_INFO, "route manager, current-wp is now " << _route->current_index());
619 int FGRouteMgr::findWaypoint(const SGGeod& aPos) const
621 for (int i=0; i<_route->size(); ++i) {
622 double d = SGGeodesy::distanceM(aPos, _route->get_waypoint(i).get_target());
623 if (d < 200.0) { // 200 metres seems close enough
631 SGWayPoint FGRouteMgr::get_waypoint( int i ) const
633 return _route->get_waypoint(i);
636 int FGRouteMgr::size() const
638 return _route->size();
641 int FGRouteMgr::currentWaypoint() const
643 return _route->current_index();
646 void FGRouteMgr::setWaypointTargetAltitudeFt(unsigned int index, int altFt)
648 SGWayPoint wp = _route->get_waypoint(index);
649 wp.setTargetAltFt(altFt);
650 // simplest way to update a waypoint is to remove and re-add it
651 _route->delete_waypoint(index);
652 _route->add_waypoint(wp, index);
656 void FGRouteMgr::saveRoute()
658 SGPath path(_pathNode->getStringValue());
659 SG_LOG(SG_IO, SG_INFO, "Saving route to " << path.str());
661 SGPropertyNode_ptr d(new SGPropertyNode);
662 SGPath path(_pathNode->getStringValue());
663 d->setIntValue("version", 1);
666 d->setStringValue("departure/airport", _departure->ident());
667 d->setStringValue("departure/sid", departure->getStringValue("sid"));
668 d->setStringValue("departure/runway", departure->getStringValue("runway"));
672 d->setStringValue("destination/airport", _destination->ident());
673 d->setStringValue("destination/star", destination->getStringValue("star"));
674 d->setStringValue("destination/transition", destination->getStringValue("transition"));
675 d->setStringValue("destination/runway", destination->getStringValue("runway"));
679 SGPropertyNode* routeNode = d->getChild("route", 0, true);
680 for (int i=0; i<_route->size(); ++i) {
681 SGPropertyNode* wpNode = routeNode->getChild("wp",i, true);
682 SGWayPoint wp(_route->get_waypoint(i));
684 wpNode->setStringValue("ident", wp.get_id());
685 wpNode->setStringValue("name", wp.get_name());
686 SGGeod geod(wp.get_target());
688 wpNode->setDoubleValue("longitude-deg", geod.getLongitudeDeg());
689 wpNode->setDoubleValue("latitude-deg", geod.getLatitudeDeg());
691 if (geod.getElevationFt() > -9990.0) {
692 wpNode->setDoubleValue("altitude-ft", geod.getElevationFt());
694 } // of waypoint iteration
696 writeProperties(path.str(), d, true /* write-all */);
697 } catch (const sg_exception &e) {
698 SG_LOG(SG_IO, SG_WARN, "Error saving route:" << e.getMessage());
702 void FGRouteMgr::loadRoute()
704 // deactivate route first
705 active->setBoolValue(false);
707 SGPropertyNode_ptr routeData(new SGPropertyNode);
708 SGPath path(_pathNode->getStringValue());
710 SG_LOG(SG_IO, SG_INFO, "going to read flight-plan from:" << path.str());
713 readProperties(path.str(), routeData);
714 } catch (sg_exception& e) {
715 // if XML parsing fails, the file might be simple textual list of waypoints
716 loadPlainTextRoute(path);
722 SGPropertyNode* dep = routeData->getChild("departure");
724 throw sg_io_exception("malformed route file, no departure node");
727 string depIdent = dep->getStringValue("airport");
728 _departure = (FGAirport*) fgFindAirportID(depIdent);
732 SGPropertyNode* dst = routeData->getChild("destination");
734 throw sg_io_exception("malformed route file, no destination node");
737 _destination = (FGAirport*) fgFindAirportID(dst->getStringValue("airport"));
738 destination->setStringValue("runway", dst->getStringValue("runway"));
741 SGPropertyNode* alt = routeData->getChild("alternate");
743 alternate->setStringValue(alt->getStringValue("airport"));
744 } // of cruise data loading
747 SGPropertyNode* crs = routeData->getChild("cruise");
749 cruise->setDoubleValue("speed-kts", crs->getDoubleValue("speed-kts"));
750 cruise->setDoubleValue("mach", crs->getDoubleValue("mach"));
751 cruise->setDoubleValue("altitude-ft", crs->getDoubleValue("altitude-ft"));
752 } // of cruise data loading
756 SGPropertyNode_ptr _route = routeData->getChild("route", 0);
757 SGGeod lastPos = (_departure ? _departure->geod() : SGGeod());
759 for (int i=0; i<_route->nChildren(); ++i) {
760 SGPropertyNode_ptr wp = _route->getChild("wp", i);
761 parseRouteWaypoint(wp);
762 } // of route iteration
763 } catch (sg_exception& e) {
764 SG_LOG(SG_IO, SG_WARN, "failed to load flight-plan (from '" << e.getOrigin()
765 << "'):" << e.getMessage());
769 void FGRouteMgr::parseRouteWaypoint(SGPropertyNode* aWP)
772 if (_route->size() > 0) {
773 lastPos = get_waypoint(_route->size()-1).get_target();
775 // route is empty, use departure airport position
776 const FGAirport* apt = fgFindAirportID(departure->getStringValue("airport"));
777 assert(apt); // shouldn't have got this far with an invalid airport
778 lastPos = apt->geod();
781 SGPropertyNode_ptr altProp = aWP->getChild("altitude-ft");
782 double altM = -9999.0;
784 altM = altProp->getDoubleValue() * SG_FEET_TO_METER;
787 string ident(aWP->getStringValue("ident"));
788 if (aWP->hasChild("longitude-deg")) {
789 // explicit longitude/latitude
790 SGWayPoint swp(aWP->getDoubleValue("longitude-deg"),
791 aWP->getDoubleValue("latitude-deg"), altM,
792 SGWayPoint::WGS84, ident, aWP->getStringValue("name"));
794 } else if (aWP->hasChild("navid")) {
795 // lookup by navid (possibly with offset)
796 string nid(aWP->getStringValue("navid"));
797 FGPositionedRef p = FGPositioned::findClosestWithIdent(nid, lastPos);
799 throw sg_io_exception("bad route file, unknown navid:" + nid);
802 SGGeod pos(p->geod());
803 if (aWP->hasChild("offset-nm") && aWP->hasChild("offset-radial")) {
804 double radialDeg = aWP->getDoubleValue("offset-radial");
805 // convert magnetic radial to a true radial!
806 radialDeg += magvar->getDoubleValue();
807 double offsetNm = aWP->getDoubleValue("offset-nm");
809 SGGeodesy::direct(p->geod(), radialDeg, offsetNm * SG_NM_TO_METER, pos, az2);
812 SGWayPoint swp(pos.getLongitudeDeg(), pos.getLatitudeDeg(), altM,
813 SGWayPoint::WGS84, ident, "");
816 // lookup by ident (symbolic waypoint)
817 FGPositionedRef p = FGPositioned::findClosestWithIdent(ident, lastPos);
819 throw sg_io_exception("bad route file, unknown waypoint:" + ident);
822 SGWayPoint swp(p->longitude(), p->latitude(), altM,
823 SGWayPoint::WGS84, p->ident(), p->name());
828 void FGRouteMgr::loadPlainTextRoute(const SGPath& path)
830 sg_gzifstream in(path.str().c_str());
838 getline(in, line, '\n');
839 // trim CR from end of line, if found
840 if (line[line.size() - 1] == '\r') {
841 line.erase(line.size() - 1, 1);
844 new_waypoint(line, -1);
845 } // of line iteration
848 const char* FGRouteMgr::getDepartureICAO() const
854 return _departure->ident().c_str();
857 const char* FGRouteMgr::getDepartureName() const
863 return _departure->name().c_str();
866 void FGRouteMgr::setDepartureICAO(const char* aIdent)
868 if ((aIdent == NULL) || (strlen(aIdent) < 4)) {
871 _departure = FGAirport::findByIdent(aIdent);
875 const char* FGRouteMgr::getDestinationICAO() const
881 return _destination->ident().c_str();
884 const char* FGRouteMgr::getDestinationName() const
890 return _destination->name().c_str();
893 void FGRouteMgr::setDestinationICAO(const char* aIdent)
895 if ((aIdent == NULL) || (strlen(aIdent) < 4)) {
898 _destination = FGAirport::findByIdent(aIdent);