1 // FlightPlan.cxx - flight plan object
3 // Written by James Turner, started 2012.
5 // Copyright (C) 2012 Curtis L. Olson
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 // General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25 #include "FlightPlan.hxx"
32 #include <boost/algorithm/string/case_conv.hpp>
33 #include <boost/algorithm/string.hpp>
34 #include <boost/foreach.hpp>
37 #include <simgear/structure/exception.hxx>
38 #include <simgear/misc/sg_path.hxx>
39 #include <simgear/magvar/magvar.hxx>
40 #include <simgear/timing/sg_time.hxx>
41 #include <simgear/misc/sgstream.hxx>
42 #include <simgear/misc/strutils.hxx>
43 #include <simgear/props/props_io.hxx>
46 #include <Main/globals.hxx>
47 #include "Main/fg_props.hxx"
48 #include <Navaids/procedure.hxx>
49 #include <Navaids/waypoint.hxx>
56 namespace flightgear {
58 FlightPlan::FlightPlan() :
60 _departureRunway(NULL),
61 _destinationRunway(NULL),
70 FlightPlan::~FlightPlan()
75 FlightPlan* FlightPlan::clone(const string& newIdent) const
77 FlightPlan* c = new FlightPlan();
78 c->_ident = newIdent.empty() ? _ident : newIdent;
80 // copy destination / departure data.
81 c->setDeparture(_departure);
82 c->setDeparture(_departureRunway);
85 c->setApproach(_approach);
86 } else if (_destinationRunway) {
87 c->setDestination(_destinationRunway);
88 } else if (_destination) {
89 c->setDestination(_destination);
96 for (int l=0; l < numLegs(); ++l) {
97 c->_legs.push_back(_legs[l]->cloneFor(c));
103 void FlightPlan::setIdent(const string& s)
108 string FlightPlan::ident() const
113 FlightPlan::Leg* FlightPlan::insertWayptAtIndex(Waypt* aWpt, int aIndex)
123 if ((aIndex == -1) || (aIndex > (int) _legs.size())) {
124 index = _legs.size();
127 insertWayptsAtIndex(wps, index);
128 return legAtIndex(aIndex);
131 void FlightPlan::insertWayptsAtIndex(const WayptVec& wps, int aIndex)
138 if ((aIndex == -1) || (aIndex > (int) _legs.size())) {
139 index = _legs.size();
142 LegVec::iterator it = _legs.begin();
145 int endIndex = index + wps.size() - 1;
146 if (_currentIndex >= endIndex) {
147 _currentIndex += wps.size();
151 BOOST_FOREACH(WayptRef wp, wps) {
152 newLegs.push_back(new Leg(this, wp));
155 _legs.insert(it, newLegs.begin(), newLegs.end());
159 _delegate->runWaypointsChanged();
163 void FlightPlan::deleteIndex(int aIndex)
166 if (aIndex < 0) { // negative indices count the the end
167 index = _legs.size() + index;
170 if ((index < 0) || (index >= numLegs())) {
171 SG_LOG(SG_AUTOPILOT, SG_WARN, "removeAtIndex with invalid index:" << aIndex);
174 LegVec::iterator it = _legs.begin();
180 bool curChanged = false;
181 if (_currentIndex == index) {
182 // current waypoint was removed
184 } else if (_currentIndex > index) {
185 --_currentIndex; // shift current index down if necessary
190 _delegate->runWaypointsChanged();
192 _delegate->runCurrentWaypointChanged();
197 void FlightPlan::clear()
200 BOOST_FOREACH(Leg* l, _legs) {
206 _delegate->runDepartureChanged();
207 _delegate->runArrivalChanged();
208 _delegate->runWaypointsChanged();
209 _delegate->runCurrentWaypointChanged();
213 int FlightPlan::clearWayptsWithFlag(WayptFlag flag)
216 for (unsigned int i=0; i<_legs.size(); ++i) {
218 if (!l->waypoint()->flag(flag)) {
222 // okay, we're going to clear this leg
224 if (_currentIndex > (int) i) {
229 LegVec::iterator it = _legs.begin();
235 return 0; // nothing was cleared, don't fire the delegate
240 _delegate->runWaypointsChanged();
241 _delegate->runCurrentWaypointChanged();
247 void FlightPlan::setCurrentIndex(int index)
249 if ((index < -1) || (index >= numLegs())) {
250 throw sg_range_exception("invalid leg index", "FlightPlan::setCurrentIndex");
253 if (index == _currentIndex) {
257 _currentIndex = index;
259 _delegate->runCurrentWaypointChanged();
263 int FlightPlan::findWayptIndex(const SGGeod& aPos) const
265 for (int i=0; i<numLegs(); ++i) {
266 if (_legs[i]->waypoint()->matches(aPos)) {
274 FlightPlan::Leg* FlightPlan::currentLeg() const
276 if ((_currentIndex < 0) || (_currentIndex >= numLegs()))
278 return legAtIndex(_currentIndex);
281 FlightPlan::Leg* FlightPlan::previousLeg() const
283 if (_currentIndex == 0) {
287 return legAtIndex(_currentIndex - 1);
290 FlightPlan::Leg* FlightPlan::nextLeg() const
292 if ((_currentIndex < 0) || ((_currentIndex + 1) >= numLegs())) {
296 return legAtIndex(_currentIndex + 1);
299 FlightPlan::Leg* FlightPlan::legAtIndex(int index) const
301 if ((index < 0) || (index >= numLegs())) {
302 throw sg_range_exception("index out of range", "FlightPlan::legAtIndex");
308 int FlightPlan::findLegIndex(const Leg *l) const
310 for (unsigned int i=0; i<_legs.size(); ++i) {
319 void FlightPlan::setDeparture(FGAirport* apt)
321 if (apt == _departure) {
326 _departureRunway = NULL;
330 _delegate->runDepartureChanged();
334 void FlightPlan::setDeparture(FGRunway* rwy)
336 if (_departureRunway == rwy) {
340 _departureRunway = rwy;
341 if (rwy->airport() != _departure) {
342 _departure = rwy->airport();
347 _delegate->runDepartureChanged();
351 void FlightPlan::setSID(SID* sid, const std::string& transition)
358 _sidTransition = transition;
361 _delegate->runDepartureChanged();
365 void FlightPlan::setSID(Transition* trans)
372 if (trans->parent()->type() != PROCEDURE_SID)
373 throw sg_exception("FlightPlan::setSID: transition does not belong to a SID");
375 setSID((SID*) trans->parent(), trans->ident());
378 Transition* FlightPlan::sidTransition() const
380 if (!_sid || _sidTransition.empty()) {
384 return _sid->findTransitionByName(_sidTransition);
387 void FlightPlan::setDestination(FGAirport* apt)
389 if (apt == _destination) {
394 _destinationRunway = NULL;
395 setSTAR((STAR*)NULL);
398 _delegate->runArrivalChanged();
402 void FlightPlan::setDestination(FGRunway* rwy)
404 if (_destinationRunway == rwy) {
408 _destinationRunway = rwy;
409 if (_destination != rwy->airport()) {
410 _destination = rwy->airport();
411 setSTAR((STAR*)NULL);
415 _delegate->runArrivalChanged();
419 void FlightPlan::setSTAR(STAR* star, const std::string& transition)
426 _starTransition = transition;
429 _delegate->runArrivalChanged();
433 void FlightPlan::setSTAR(Transition* trans)
436 setSTAR((STAR*) NULL);
440 if (trans->parent()->type() != PROCEDURE_STAR)
441 throw sg_exception("FlightPlan::setSTAR: transition does not belong to a STAR");
443 setSTAR((STAR*) trans->parent(), trans->ident());
446 Transition* FlightPlan::starTransition() const
448 if (!_star || _starTransition.empty()) {
452 return _star->findTransitionByName(_starTransition);
455 void FlightPlan::setApproach(flightgear::Approach *app)
457 if (_approach == app) {
463 // keep runway + airport in sync
464 if (_destinationRunway != _approach->runway()) {
465 _destinationRunway = _approach->runway();
468 if (_destination != _destinationRunway->airport()) {
469 _destination = _destinationRunway->airport();
474 _delegate->runArrivalChanged();
478 bool FlightPlan::save(const SGPath& path)
480 SG_LOG(SG_IO, SG_INFO, "Saving route to " << path.str());
482 SGPropertyNode_ptr d(new SGPropertyNode);
483 d->setIntValue("version", 2);
486 d->setStringValue("departure/airport", _departure->ident());
488 d->setStringValue("departure/sid", _sid->ident());
491 if (_departureRunway) {
492 d->setStringValue("departure/runway", _departureRunway->ident());
497 d->setStringValue("destination/airport", _destination->ident());
499 d->setStringValue("destination/star", _star->ident());
503 d->setStringValue("destination/approach", _approach->ident());
506 //d->setStringValue("destination/transition", destination->getStringValue("transition"));
508 if (_destinationRunway) {
509 d->setStringValue("destination/runway", _destinationRunway->ident());
514 SGPropertyNode* routeNode = d->getChild("route", 0, true);
515 for (unsigned int i=0; i<_legs.size(); ++i) {
516 Waypt* wpt = _legs[i]->waypoint();
517 wpt->saveAsNode(routeNode->getChild("wp", i, true));
518 } // of waypoint iteration
519 writeProperties(path.str(), d, true /* write-all */);
521 } catch (sg_exception& e) {
522 SG_LOG(SG_IO, SG_ALERT, "Failed to save flight-plan '" << path.str() << "'. " << e.getMessage());
527 bool FlightPlan::load(const SGPath& path)
531 SG_LOG(SG_IO, SG_ALERT, "Failed to load flight-plan '" << path.str()
532 << "'. The file does not exist.");
536 SGPropertyNode_ptr routeData(new SGPropertyNode);
537 SG_LOG(SG_IO, SG_INFO, "going to read flight-plan from:" << path.str());
541 readProperties(path.str(), routeData);
542 } catch (sg_exception& ) {
543 // if XML parsing fails, the file might be simple textual list of waypoints
544 Status = loadPlainTextRoute(path);
548 if (routeData.valid())
551 int version = routeData->getIntValue("version", 1);
553 loadVersion1XMLRoute(routeData);
554 } else if (version == 2) {
555 loadVersion2XMLRoute(routeData);
557 throw sg_io_exception("unsupported XML route version");
560 } catch (sg_exception& e) {
561 SG_LOG(SG_IO, SG_ALERT, "Failed to load flight-plan '" << e.getOrigin()
562 << "'. " << e.getMessage());
569 _delegate->runWaypointsChanged();
575 void FlightPlan::loadXMLRouteHeader(SGPropertyNode_ptr routeData)
578 SGPropertyNode* dep = routeData->getChild("departure");
580 string depIdent = dep->getStringValue("airport");
581 setDeparture((FGAirport*) fgFindAirportID(depIdent));
583 if (dep->hasChild("runway")) {
584 setDeparture(_departure->getRunwayByIdent(dep->getStringValue("runway")));
587 if (dep->hasChild("sid")) {
588 setSID(_departure->findSIDWithIdent(dep->getStringValue("sid")));
590 // departure->setStringValue("transition", dep->getStringValue("transition"));
595 SGPropertyNode* dst = routeData->getChild("destination");
597 setDestination((FGAirport*) fgFindAirportID(dst->getStringValue("airport")));
599 if (dst->hasChild("runway")) {
600 setDestination(_destination->getRunwayByIdent(dst->getStringValue("runway")));
603 if (dst->hasChild("star")) {
604 setSTAR(_destination->findSTARWithIdent(dst->getStringValue("star")));
607 if (dst->hasChild("approach")) {
608 setApproach(_destination->findApproachWithIdent(dst->getStringValue("approach")));
612 // destination->setStringValue("transition", dst->getStringValue("transition"));
616 SGPropertyNode* alt = routeData->getChild("alternate");
618 //alternate->setStringValue(alt->getStringValue("airport"));
622 SGPropertyNode* crs = routeData->getChild("cruise");
624 // cruise->setDoubleValue("speed-kts", crs->getDoubleValue("speed-kts"));
625 // cruise->setDoubleValue("mach", crs->getDoubleValue("mach"));
626 // cruise->setDoubleValue("altitude-ft", crs->getDoubleValue("altitude-ft"));
627 } // of cruise data loading
631 void FlightPlan::loadVersion2XMLRoute(SGPropertyNode_ptr routeData)
633 loadXMLRouteHeader(routeData);
637 SGPropertyNode_ptr routeNode = routeData->getChild("route", 0);
638 for (int i=0; i<routeNode->nChildren(); ++i) {
639 SGPropertyNode_ptr wpNode = routeNode->getChild("wp", i);
640 Leg* l = new Leg(this, Waypt::createFromProperties(NULL, wpNode));
642 } // of route iteration
645 void FlightPlan::loadVersion1XMLRoute(SGPropertyNode_ptr routeData)
647 loadXMLRouteHeader(routeData);
651 SGPropertyNode_ptr routeNode = routeData->getChild("route", 0);
652 for (int i=0; i<routeNode->nChildren(); ++i) {
653 SGPropertyNode_ptr wpNode = routeNode->getChild("wp", i);
654 Leg* l = new Leg(this, parseVersion1XMLWaypt(wpNode));
656 } // of route iteration
660 WayptRef FlightPlan::parseVersion1XMLWaypt(SGPropertyNode* aWP)
663 if (!_legs.empty()) {
664 lastPos = _legs.back()->waypoint()->position();
665 } else if (_departure) {
666 lastPos = _departure->geod();
670 string ident(aWP->getStringValue("ident"));
671 if (aWP->hasChild("longitude-deg")) {
672 // explicit longitude/latitude
673 w = new BasicWaypt(SGGeod::fromDeg(aWP->getDoubleValue("longitude-deg"),
674 aWP->getDoubleValue("latitude-deg")), ident, NULL);
677 string nid = aWP->getStringValue("navid", ident.c_str());
678 FGPositionedRef p = FGPositioned::findClosestWithIdent(nid, lastPos);
680 throw sg_io_exception("bad route file, unknown navid:" + nid);
683 SGGeod pos(p->geod());
684 if (aWP->hasChild("offset-nm") && aWP->hasChild("offset-radial")) {
685 double radialDeg = aWP->getDoubleValue("offset-radial");
686 // convert magnetic radial to a true radial!
687 radialDeg += magvarDegAt(pos);
688 double offsetNm = aWP->getDoubleValue("offset-nm");
690 SGGeodesy::direct(p->geod(), radialDeg, offsetNm * SG_NM_TO_METER, pos, az2);
693 w = new BasicWaypt(pos, ident, NULL);
696 double altFt = aWP->getDoubleValue("altitude-ft", -9999.9);
697 if (altFt > -9990.0) {
698 w->setAltitude(altFt, RESTRICT_AT);
704 bool FlightPlan::loadPlainTextRoute(const SGPath& path)
707 sg_gzifstream in(path.str().c_str());
709 throw sg_io_exception("Cannot open file for reading.");
715 getline(in, line, '\n');
716 // trim CR from end of line, if found
717 if (line[line.size() - 1] == '\r') {
718 line.erase(line.size() - 1, 1);
721 line = simgear::strutils::strip(line);
722 if (line.empty() || (line[0] == '#')) {
723 continue; // ignore empty/comment lines
726 WayptRef w = waypointFromString(line);
728 throw sg_io_exception("Failed to create waypoint from line '" + line + "'.");
731 _legs.push_back(new Leg(this, w));
732 } // of line iteration
733 } catch (sg_exception& e) {
734 SG_LOG(SG_IO, SG_ALERT, "Failed to load route from: '" << path.str() << "'. " << e.getMessage());
742 double FlightPlan::magvarDegAt(const SGGeod& pos) const
744 double jd = globals->get_time_params()->getJD();
745 return sgGetMagVar(pos, jd) * SG_RADIANS_TO_DEGREES;
748 WayptRef FlightPlan::waypointFromString(const string& tgt )
750 string target(boost::to_upper_copy(tgt));
755 RouteRestriction altSetting = RESTRICT_NONE;
757 size_t pos = target.find( '@' );
758 if ( pos != string::npos ) {
759 altFt = atof( target.c_str() + pos + 1 );
760 target = target.substr( 0, pos );
761 if ( !strcmp(fgGetString("/sim/startup/units"), "meter") )
762 altFt *= SG_METER_TO_FEET;
763 altSetting = RESTRICT_AT;
767 pos = target.find( ',' );
768 if ( pos != string::npos ) {
769 double lon = atof( target.substr(0, pos).c_str());
770 double lat = atof( target.c_str() + pos + 1);
772 char ew = (lon < 0.0) ? 'W' : 'E';
773 char ns = (lat < 0.0) ? 'S' : 'N';
774 snprintf(buf, 32, "%c%03d%c%03d", ew, (int) fabs(lon), ns, (int)fabs(lat));
776 wpt = new BasicWaypt(SGGeod::fromDeg(lon, lat), buf, NULL);
777 if (altSetting != RESTRICT_NONE) {
778 wpt->setAltitude(altFt, altSetting);
785 // route is empty, use current position
786 basePosition = globals->get_aircraft_position();
788 basePosition = _legs.back()->waypoint()->position();
791 string_list pieces(simgear::strutils::split(target, "/"));
792 FGPositionedRef p = FGPositioned::findClosestWithIdent(pieces.front(), basePosition);
794 SG_LOG( SG_AUTOPILOT, SG_INFO, "Unable to find FGPositioned with ident:" << pieces.front());
798 double magvar = magvarDegAt(basePosition);
800 if (pieces.size() == 1) {
801 wpt = new NavaidWaypoint(p, NULL);
802 } else if (pieces.size() == 3) {
803 // navaid/radial/distance-nm notation
804 double radial = atof(pieces[1].c_str()),
805 distanceNm = atof(pieces[2].c_str());
807 wpt = new OffsetNavaidWaypoint(p, NULL, radial, distanceNm);
808 } else if (pieces.size() == 2) {
809 FGAirport* apt = dynamic_cast<FGAirport*>(p.ptr());
811 SG_LOG(SG_AUTOPILOT, SG_INFO, "Waypoint is not an airport:" << pieces.front());
815 if (!apt->hasRunwayWithIdent(pieces[1])) {
816 SG_LOG(SG_AUTOPILOT, SG_INFO, "No runway: " << pieces[1] << " at " << pieces[0]);
820 FGRunway* runway = apt->getRunwayByIdent(pieces[1]);
821 wpt = new NavaidWaypoint(runway, NULL);
822 } else if (pieces.size() == 4) {
823 // navid/radial/navid/radial notation
824 FGPositionedRef p2 = FGPositioned::findClosestWithIdent(pieces[2], basePosition);
826 SG_LOG( SG_AUTOPILOT, SG_INFO, "Unable to find FGPositioned with ident:" << pieces[2]);
830 double r1 = atof(pieces[1].c_str()),
831 r2 = atof(pieces[3].c_str());
836 bool ok = SGGeodesy::radialIntersection(p->geod(), r1, p2->geod(), r2, intersection);
838 SG_LOG(SG_AUTOPILOT, SG_INFO, "no valid intersection for:" << target);
842 std::string name = p->ident() + "-" + p2->ident();
843 wpt = new BasicWaypt(intersection, name, NULL);
847 SG_LOG(SG_AUTOPILOT, SG_INFO, "Unable to parse waypoint:" << target);
851 if (altSetting != RESTRICT_NONE) {
852 wpt->setAltitude(altFt, altSetting);
857 FlightPlan::Leg::Leg(FlightPlan* owner, WayptRef wpt) :
859 _speedRestrict(RESTRICT_NONE),
860 _altRestrict(RESTRICT_NONE),
864 throw sg_exception("can't create FlightPlan::Leg without underlying waypoint");
866 _speed = _altitudeFt = 0;
869 FlightPlan::Leg* FlightPlan::Leg::cloneFor(FlightPlan* owner) const
871 Leg* c = new Leg(owner, _waypt);
874 c->_speedRestrict = _speedRestrict;
875 c->_altitudeFt = _altitudeFt;
876 c->_altRestrict = _altRestrict;
881 FlightPlan::Leg* FlightPlan::Leg::nextLeg() const
883 return _parent->legAtIndex(index() + 1);
886 unsigned int FlightPlan::Leg::index() const
888 return _parent->findLegIndex(this);
891 int FlightPlan::Leg::altitudeFt() const
893 if (_altRestrict != RESTRICT_NONE) {
897 return _waypt->altitudeFt();
900 int FlightPlan::Leg::speed() const
902 if (_speedRestrict != RESTRICT_NONE) {
906 return _waypt->speed();
909 int FlightPlan::Leg::speedKts() const
914 double FlightPlan::Leg::speedMach() const
916 if (!isMachRestrict(_speedRestrict)) {
920 return -(_speed / 100.0);
923 RouteRestriction FlightPlan::Leg::altitudeRestriction() const
925 if (_altRestrict != RESTRICT_NONE) {
929 return _waypt->altitudeRestriction();
932 RouteRestriction FlightPlan::Leg::speedRestriction() const
934 if (_speedRestrict != RESTRICT_NONE) {
935 return _speedRestrict;
938 return _waypt->speedRestriction();
941 void FlightPlan::Leg::setSpeed(RouteRestriction ty, double speed)
944 if (isMachRestrict(ty)) {
945 _speed = (speed * -100);
951 void FlightPlan::Leg::setAltitude(RouteRestriction ty, int altFt)
957 double FlightPlan::Leg::courseDeg() const
962 double FlightPlan::Leg::distanceNm() const
964 return _pathDistance;
967 double FlightPlan::Leg::distanceAlongRoute() const
969 return _distanceAlongPath;
972 void FlightPlan::rebuildLegData()
974 _totalDistance = 0.0;
975 int lastLeg = static_cast<int>(_legs.size()) - 1;
976 for (int l=0; l<lastLeg; ++l) {
978 Leg* next = _legs[l + 1];
980 std::pair<double, double> crsDist =
981 next->waypoint()->courseAndDistanceFrom(cur->waypoint()->position());
982 _legs[l]->_courseDeg = crsDist.first;
983 _legs[l]->_pathDistance = crsDist.second * SG_METER_TO_NM;
984 _legs[l]->_distanceAlongPath = _totalDistance;
985 _totalDistance += crsDist.second * SG_METER_TO_NM;
986 } // of legs iteration
989 void FlightPlan::setDelegate(Delegate* d)
991 // wrap any existing delegate(s) in the new one
992 d->_inner = _delegate;
996 void FlightPlan::removeDelegate(Delegate* d)
998 if (d == _delegate) {
999 _delegate = _delegate->_inner;
1000 } else if (_delegate) {
1001 _delegate->removeInner(d);
1005 FlightPlan::Delegate::Delegate() :
1011 FlightPlan::Delegate::~Delegate()
1016 void FlightPlan::Delegate::removeInner(Delegate* d)
1023 // replace with grand-child
1025 } else { // recurse downwards
1026 _inner->removeInner(d);
1030 void FlightPlan::Delegate::runDepartureChanged()
1032 if (_inner) _inner->runDepartureChanged();
1036 void FlightPlan::Delegate::runArrivalChanged()
1038 if (_inner) _inner->runArrivalChanged();
1042 void FlightPlan::Delegate::runWaypointsChanged()
1044 if (_inner) _inner->runWaypointsChanged();
1048 void FlightPlan::Delegate::runCurrentWaypointChanged()
1050 if (_inner) _inner->runCurrentWaypointChanged();
1051 currentWaypointChanged();
1054 } // of namespace flightgear