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"
33 #include <boost/algorithm/string/case_conv.hpp>
34 #include <boost/algorithm/string.hpp>
35 #include <boost/foreach.hpp>
38 #include <simgear/structure/exception.hxx>
39 #include <simgear/misc/sg_path.hxx>
40 #include <simgear/magvar/magvar.hxx>
41 #include <simgear/timing/sg_time.hxx>
42 #include <simgear/misc/sgstream.hxx>
43 #include <simgear/misc/strutils.hxx>
44 #include <simgear/props/props_io.hxx>
45 #include <simgear/xml/easyxml.hxx>
48 #include <Main/globals.hxx>
49 #include "Main/fg_props.hxx"
50 #include <Navaids/procedure.hxx>
51 #include <Navaids/waypoint.hxx>
58 namespace flightgear {
60 typedef std::vector<FlightPlan::DelegateFactory*> FPDelegateFactoryVec;
61 static FPDelegateFactoryVec static_delegateFactories;
63 FlightPlan::FlightPlan() :
66 _departureRunway(NULL),
67 _destinationRunway(NULL),
74 _departureChanged = _arrivalChanged = _waypointsChanged = _currentWaypointChanged = false;
76 BOOST_FOREACH(DelegateFactory* factory, static_delegateFactories) {
77 Delegate* d = factory->createFlightPlanDelegate(this);
78 if (d) { // factory might not always create a delegate
79 d->_deleteWithPlan = true;
85 FlightPlan::~FlightPlan()
87 // delete all delegates which we own.
88 Delegate* d = _delegate;
92 if (cur->_deleteWithPlan) {
98 BOOST_FOREACH(Leg* l, _legs) {
103 FlightPlan* FlightPlan::clone(const string& newIdent) const
105 FlightPlan* c = new FlightPlan();
106 c->_ident = newIdent.empty() ? _ident : newIdent;
109 // copy destination / departure data.
110 c->setDeparture(_departure);
111 c->setDeparture(_departureRunway);
114 c->setApproach(_approach);
115 } else if (_destinationRunway) {
116 c->setDestination(_destinationRunway);
117 } else if (_destination) {
118 c->setDestination(_destination);
125 c->_waypointsChanged = true;
126 for (int l=0; l < numLegs(); ++l) {
127 c->_legs.push_back(_legs[l]->cloneFor(c));
133 void FlightPlan::setIdent(const string& s)
138 string FlightPlan::ident() const
143 FlightPlan::Leg* FlightPlan::insertWayptAtIndex(Waypt* aWpt, int aIndex)
153 if ((aIndex == -1) || (aIndex > (int) _legs.size())) {
154 index = _legs.size();
157 insertWayptsAtIndex(wps, index);
158 return legAtIndex(index);
161 void FlightPlan::insertWayptsAtIndex(const WayptVec& wps, int aIndex)
168 if ((aIndex == -1) || (aIndex > (int) _legs.size())) {
169 index = _legs.size();
172 LegVec::iterator it = _legs.begin();
175 int endIndex = index + wps.size() - 1;
176 if (_currentIndex >= endIndex) {
177 _currentIndex += wps.size();
181 BOOST_FOREACH(WayptRef wp, wps) {
182 newLegs.push_back(new Leg(this, wp));
186 _waypointsChanged = true;
187 _legs.insert(it, newLegs.begin(), newLegs.end());
191 void FlightPlan::deleteIndex(int aIndex)
194 if (aIndex < 0) { // negative indices count the the end
195 index = _legs.size() + index;
198 if ((index < 0) || (index >= numLegs())) {
199 SG_LOG(SG_NAVAID, SG_WARN, "removeAtIndex with invalid index:" << aIndex);
204 _waypointsChanged = true;
206 LegVec::iterator it = _legs.begin();
212 if (_currentIndex == index) {
213 // current waypoint was removed
214 _currentWaypointChanged = true;
215 } else if (_currentIndex > index) {
216 --_currentIndex; // shift current index down if necessary
222 void FlightPlan::clear()
225 _waypointsChanged = true;
226 _currentWaypointChanged = true;
227 _arrivalChanged = true;
228 _departureChanged = true;
231 BOOST_FOREACH(Leg* l, _legs) {
237 _delegate->runCleared();
245 RemoveWithFlag(WayptFlag f) : flag(f), delCount(0) { }
247 int numDeleted() const { return delCount; }
249 bool operator()(FlightPlan::Leg* leg) const
251 if (leg->waypoint()->flag(flag)) {
261 mutable int delCount;
264 int FlightPlan::clearWayptsWithFlag(WayptFlag flag)
267 // first pass, fix up currentIndex
268 for (int i=0; i<_currentIndex; ++i) {
270 if (l->waypoint()->flag(flag)) {
275 // test if the current leg will be removed
276 bool currentIsBeingCleared = false;
277 Leg* curLeg = currentLeg();
279 currentIsBeingCleared = curLeg->waypoint()->flag(flag);
282 _currentIndex -= count;
284 // if we're clearing the current waypoint, what shall we do with the
285 // index? there's various options, but safest is to select no waypoint
286 // and let the use re-activate.
287 // http://code.google.com/p/flightgear-bugs/issues/detail?id=1134
288 if (currentIsBeingCleared) {
289 SG_LOG(SG_GENERAL, SG_INFO, "currentIsBeingCleared:" << currentIsBeingCleared);
293 // now delete and remove
294 RemoveWithFlag rf(flag);
295 LegVec::iterator it = std::remove_if(_legs.begin(), _legs.end(), rf);
296 if (it == _legs.end()) {
297 return 0; // nothing was cleared, don't fire the delegate
301 _waypointsChanged = true;
302 if ((count > 0) || currentIsBeingCleared) {
303 _currentWaypointChanged = true;
306 _legs.erase(it, _legs.end());
308 if (_legs.empty()) { // maybe all legs were deleted
310 _delegate->runCleared();
315 return rf.numDeleted();
318 void FlightPlan::setCurrentIndex(int index)
320 if ((index < -1) || (index >= numLegs())) {
321 throw sg_range_exception("invalid leg index", "FlightPlan::setCurrentIndex");
324 if (index == _currentIndex) {
329 _currentIndex = index;
330 _currentWaypointChanged = true;
334 void FlightPlan::finish()
336 if (_currentIndex == -1) {
342 _currentWaypointChanged = true;
345 _delegate->runFinished();
351 int FlightPlan::findWayptIndex(const SGGeod& aPos) const
353 for (int i=0; i<numLegs(); ++i) {
354 if (_legs[i]->waypoint()->matches(aPos)) {
362 FlightPlan::Leg* FlightPlan::currentLeg() const
364 if ((_currentIndex < 0) || (_currentIndex >= numLegs()))
366 return legAtIndex(_currentIndex);
369 FlightPlan::Leg* FlightPlan::previousLeg() const
371 if (_currentIndex <= 0) {
375 return legAtIndex(_currentIndex - 1);
378 FlightPlan::Leg* FlightPlan::nextLeg() const
380 if ((_currentIndex < 0) || ((_currentIndex + 1) >= numLegs())) {
384 return legAtIndex(_currentIndex + 1);
387 FlightPlan::Leg* FlightPlan::legAtIndex(int index) const
389 if ((index < 0) || (index >= numLegs())) {
390 throw sg_range_exception("index out of range", "FlightPlan::legAtIndex");
396 int FlightPlan::findLegIndex(const Leg *l) const
398 for (unsigned int i=0; i<_legs.size(); ++i) {
407 void FlightPlan::setDeparture(FGAirport* apt)
409 if (apt == _departure) {
414 _departureChanged = true;
416 _departureRunway = NULL;
421 void FlightPlan::setDeparture(FGRunway* rwy)
423 if (_departureRunway == rwy) {
428 _departureChanged = true;
430 _departureRunway = rwy;
431 if (rwy->airport() != _departure) {
432 _departure = rwy->airport();
438 void FlightPlan::setSID(SID* sid, const std::string& transition)
445 _departureChanged = true;
447 _sidTransition = transition;
451 void FlightPlan::setSID(Transition* trans)
458 if (trans->parent()->type() != PROCEDURE_SID)
459 throw sg_exception("FlightPlan::setSID: transition does not belong to a SID");
461 setSID((SID*) trans->parent(), trans->ident());
464 Transition* FlightPlan::sidTransition() const
466 if (!_sid || _sidTransition.empty()) {
470 return _sid->findTransitionByName(_sidTransition);
473 void FlightPlan::setDestination(FGAirport* apt)
475 if (apt == _destination) {
480 _arrivalChanged = true;
482 _destinationRunway = NULL;
483 setSTAR((STAR*)NULL);
488 void FlightPlan::setDestination(FGRunway* rwy)
490 if (_destinationRunway == rwy) {
495 _arrivalChanged = true;
496 _destinationRunway = rwy;
497 if (_destination != rwy->airport()) {
498 _destination = rwy->airport();
499 setSTAR((STAR*)NULL);
505 void FlightPlan::setSTAR(STAR* star, const std::string& transition)
512 _arrivalChanged = true;
514 _starTransition = transition;
518 void FlightPlan::setSTAR(Transition* trans)
521 setSTAR((STAR*) NULL);
525 if (trans->parent()->type() != PROCEDURE_STAR)
526 throw sg_exception("FlightPlan::setSTAR: transition does not belong to a STAR");
528 setSTAR((STAR*) trans->parent(), trans->ident());
531 Transition* FlightPlan::starTransition() const
533 if (!_star || _starTransition.empty()) {
537 return _star->findTransitionByName(_starTransition);
540 void FlightPlan::setApproach(flightgear::Approach *app)
542 if (_approach == app) {
547 _arrivalChanged = true;
550 // keep runway + airport in sync
551 if (_destinationRunway != _approach->runway()) {
552 _destinationRunway = _approach->runway();
555 if (_destination != _destinationRunway->airport()) {
556 _destination = _destinationRunway->airport();
562 bool FlightPlan::save(const SGPath& path)
564 SG_LOG(SG_NAVAID, SG_INFO, "Saving route to " << path.str());
566 SGPropertyNode_ptr d(new SGPropertyNode);
567 d->setIntValue("version", 2);
570 d->setStringValue("departure/airport", _departure->ident());
572 d->setStringValue("departure/sid", _sid->ident());
575 if (_departureRunway) {
576 d->setStringValue("departure/runway", _departureRunway->ident());
581 d->setStringValue("destination/airport", _destination->ident());
583 d->setStringValue("destination/star", _star->ident());
587 d->setStringValue("destination/approach", _approach->ident());
590 //d->setStringValue("destination/transition", destination->getStringValue("transition"));
592 if (_destinationRunway) {
593 d->setStringValue("destination/runway", _destinationRunway->ident());
598 SGPropertyNode* routeNode = d->getChild("route", 0, true);
599 for (unsigned int i=0; i<_legs.size(); ++i) {
600 Waypt* wpt = _legs[i]->waypoint();
601 wpt->saveAsNode(routeNode->getChild("wp", i, true));
602 } // of waypoint iteration
603 writeProperties(path.str(), d, true /* write-all */);
605 } catch (sg_exception& e) {
606 SG_LOG(SG_NAVAID, SG_ALERT, "Failed to save flight-plan '" << path.str() << "'. " << e.getMessage());
611 bool FlightPlan::load(const SGPath& path)
615 SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load flight-plan '" << path.str()
616 << "'. The file does not exist.");
620 SG_LOG(SG_NAVAID, SG_INFO, "going to read flight-plan from:" << path.str());
625 // try different file formats
626 if (loadGpxFormat(path)) // GPX format
629 if (loadXmlFormat(path)) // XML property data
632 if (loadPlainTextFormat(path)) // simple textual list of waypoints
635 _waypointsChanged = true;
641 /** XML loader for GPX file format */
642 class GpxXmlVisitor : public XMLVisitor
645 GpxXmlVisitor(FlightPlan* fp) : _fp(fp), _lat(-9999), _lon(-9999) {}
647 virtual void startElement (const char * name, const XMLAttributes &atts);
648 virtual void endElement (const char * name);
649 virtual void data (const char * s, int length);
658 void GpxXmlVisitor::startElement(const char * name, const XMLAttributes &atts)
661 if (strcmp(name, "rtept")==0)
666 const char* slat = atts.getValue("lat");
667 const char* slon = atts.getValue("lon");
676 void GpxXmlVisitor::data(const char * s, int length)
678 // use "name" when given, otherwise use "cmt" (comment) as ID
679 if ((_element == "name")||
680 ((_waypoint == "")&&(_element == "cmt")))
682 char* buf = (char*) malloc(length+1);
683 memcpy(buf, s, length);
690 void GpxXmlVisitor::endElement(const char * name)
693 if (strcmp(name, "rtept") == 0)
697 _fp->insertWayptAtIndex(new BasicWaypt(SGGeod::fromDeg(_lon, _lat), _waypoint.c_str(), NULL), -1);
702 /** Load a flightplan in GPX format */
703 bool FlightPlan::loadGpxFormat(const SGPath& path)
705 if (path.lower_extension() != "gpx")
707 // not a valid GPX file
712 GpxXmlVisitor gpxVistor(this);
715 readXML(path.str(), gpxVistor);
716 } catch (sg_exception& e)
718 // XML parsing fails => not a GPX XML file
719 SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load flight-plan in GPX format: '" << e.getOrigin()
720 << "'. " << e.getMessage());
726 SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load flight-plan in GPX format. No route found.");
733 /** Load a flightplan in FlightGear XML property format */
734 bool FlightPlan::loadXmlFormat(const SGPath& path)
736 SGPropertyNode_ptr routeData(new SGPropertyNode);
738 readProperties(path.str(), routeData);
739 } catch (sg_exception& e) {
740 SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load flight-plan '" << e.getOrigin()
741 << "'. " << e.getMessage());
742 // XML parsing fails => not a property XML file
746 if (routeData.valid())
749 int version = routeData->getIntValue("version", 1);
751 loadVersion1XMLRoute(routeData);
752 } else if (version == 2) {
753 loadVersion2XMLRoute(routeData);
755 throw sg_io_exception("unsupported XML route version");
758 } catch (sg_exception& e) {
759 SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load flight-plan '" << e.getOrigin()
760 << "'. " << e.getMessage());
767 void FlightPlan::loadXMLRouteHeader(SGPropertyNode_ptr routeData)
770 SGPropertyNode* dep = routeData->getChild("departure");
772 string depIdent = dep->getStringValue("airport");
773 setDeparture((FGAirport*) fgFindAirportID(depIdent));
775 string rwy(dep->getStringValue("runway"));
776 if (_departure->hasRunwayWithIdent(rwy)) {
777 setDeparture(_departure->getRunwayByIdent(rwy));
780 if (dep->hasChild("sid")) {
781 setSID(_departure->findSIDWithIdent(dep->getStringValue("sid")));
783 // departure->setStringValue("transition", dep->getStringValue("transition"));
788 SGPropertyNode* dst = routeData->getChild("destination");
790 setDestination((FGAirport*) fgFindAirportID(dst->getStringValue("airport")));
792 string rwy(dst->getStringValue("runway"));
793 if (_destination->hasRunwayWithIdent(rwy)) {
794 setDestination(_destination->getRunwayByIdent(rwy));
797 if (dst->hasChild("star")) {
798 setSTAR(_destination->findSTARWithIdent(dst->getStringValue("star")));
801 if (dst->hasChild("approach")) {
802 setApproach(_destination->findApproachWithIdent(dst->getStringValue("approach")));
806 // destination->setStringValue("transition", dst->getStringValue("transition"));
810 SGPropertyNode* alt = routeData->getChild("alternate");
812 //alternate->setStringValue(alt->getStringValue("airport"));
816 SGPropertyNode* crs = routeData->getChild("cruise");
818 // cruise->setDoubleValue("speed-kts", crs->getDoubleValue("speed-kts"));
819 // cruise->setDoubleValue("mach", crs->getDoubleValue("mach"));
820 // cruise->setDoubleValue("altitude-ft", crs->getDoubleValue("altitude-ft"));
821 } // of cruise data loading
825 void FlightPlan::loadVersion2XMLRoute(SGPropertyNode_ptr routeData)
827 loadXMLRouteHeader(routeData);
831 SGPropertyNode_ptr routeNode = routeData->getChild("route", 0);
832 if (routeNode.valid()) {
833 for (int i=0; i<routeNode->nChildren(); ++i) {
834 SGPropertyNode_ptr wpNode = routeNode->getChild("wp", i);
835 Leg* l = new Leg(this, Waypt::createFromProperties(NULL, wpNode));
837 } // of route iteration
839 _waypointsChanged = true;
842 void FlightPlan::loadVersion1XMLRoute(SGPropertyNode_ptr routeData)
844 loadXMLRouteHeader(routeData);
848 SGPropertyNode_ptr routeNode = routeData->getChild("route", 0);
849 for (int i=0; i<routeNode->nChildren(); ++i) {
850 SGPropertyNode_ptr wpNode = routeNode->getChild("wp", i);
851 Leg* l = new Leg(this, parseVersion1XMLWaypt(wpNode));
853 } // of route iteration
854 _waypointsChanged = true;
857 WayptRef FlightPlan::parseVersion1XMLWaypt(SGPropertyNode* aWP)
860 if (!_legs.empty()) {
861 lastPos = _legs.back()->waypoint()->position();
862 } else if (_departure) {
863 lastPos = _departure->geod();
867 string ident(aWP->getStringValue("ident"));
868 if (aWP->hasChild("longitude-deg")) {
869 // explicit longitude/latitude
870 w = new BasicWaypt(SGGeod::fromDeg(aWP->getDoubleValue("longitude-deg"),
871 aWP->getDoubleValue("latitude-deg")), ident, NULL);
874 string nid = aWP->getStringValue("navid", ident.c_str());
875 FGPositionedRef p = FGPositioned::findClosestWithIdent(nid, lastPos);
881 SG_LOG(SG_GENERAL, SG_WARN, "unknown navaid in flightplan:" << nid);
882 pos = SGGeod::fromDeg(aWP->getDoubleValue("longitude-deg"),
883 aWP->getDoubleValue("latitude-deg"));
886 if (aWP->hasChild("offset-nm") && aWP->hasChild("offset-radial")) {
887 double radialDeg = aWP->getDoubleValue("offset-radial");
888 // convert magnetic radial to a true radial!
889 radialDeg += magvarDegAt(pos);
890 double offsetNm = aWP->getDoubleValue("offset-nm");
892 SGGeodesy::direct(pos, radialDeg, offsetNm * SG_NM_TO_METER, pos, az2);
895 w = new BasicWaypt(pos, ident, NULL);
898 double altFt = aWP->getDoubleValue("altitude-ft", -9999.9);
899 if (altFt > -9990.0) {
900 w->setAltitude(altFt, RESTRICT_AT);
906 /** Load a flightplan in FlightGear plain-text format */
907 bool FlightPlan::loadPlainTextFormat(const SGPath& path)
910 sg_gzifstream in(path.str().c_str());
912 throw sg_io_exception("Cannot open file for reading.");
918 getline(in, line, '\n');
919 // trim CR from end of line, if found
920 if (line[line.size() - 1] == '\r') {
921 line.erase(line.size() - 1, 1);
924 line = simgear::strutils::strip(line);
925 if (line.empty() || (line[0] == '#')) {
926 continue; // ignore empty/comment lines
929 WayptRef w = waypointFromString(line);
931 throw sg_io_exception("Failed to create waypoint from line '" + line + "'.");
934 _legs.push_back(new Leg(this, w));
935 } // of line iteration
936 } catch (sg_exception& e) {
937 SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load route from: '" << path.str() << "'. " << e.getMessage());
945 double FlightPlan::magvarDegAt(const SGGeod& pos) const
947 double jd = globals->get_time_params()->getJD();
948 return sgGetMagVar(pos, jd) * SG_RADIANS_TO_DEGREES;
951 WayptRef FlightPlan::waypointFromString(const string& tgt )
953 string target(boost::to_upper_copy(tgt));
958 RouteRestriction altSetting = RESTRICT_NONE;
960 size_t pos = target.find( '@' );
961 if ( pos != string::npos ) {
962 altFt = atof( target.c_str() + pos + 1 );
963 target = target.substr( 0, pos );
964 if ( !strcmp(fgGetString("/sim/startup/units"), "meter") )
965 altFt *= SG_METER_TO_FEET;
966 altSetting = RESTRICT_AT;
970 pos = target.find( ',' );
971 if ( pos != string::npos ) {
972 double lon = atof( target.substr(0, pos).c_str());
973 double lat = atof( target.c_str() + pos + 1);
975 char ew = (lon < 0.0) ? 'W' : 'E';
976 char ns = (lat < 0.0) ? 'S' : 'N';
977 snprintf(buf, 32, "%c%03d%c%03d", ew, (int) fabs(lon), ns, (int)fabs(lat));
979 wpt = new BasicWaypt(SGGeod::fromDeg(lon, lat), buf, NULL);
980 if (altSetting != RESTRICT_NONE) {
981 wpt->setAltitude(altFt, altSetting);
988 // route is empty, use current position
989 basePosition = globals->get_aircraft_position();
991 basePosition = _legs.back()->waypoint()->position();
994 string_list pieces(simgear::strutils::split(target, "/"));
995 FGPositionedRef p = FGPositioned::findClosestWithIdent(pieces.front(), basePosition);
997 SG_LOG( SG_NAVAID, SG_INFO, "Unable to find FGPositioned with ident:" << pieces.front());
1001 double magvar = magvarDegAt(basePosition);
1003 if (pieces.size() == 1) {
1004 wpt = new NavaidWaypoint(p, NULL);
1005 } else if (pieces.size() == 3) {
1006 // navaid/radial/distance-nm notation
1007 double radial = atof(pieces[1].c_str()),
1008 distanceNm = atof(pieces[2].c_str());
1010 wpt = new OffsetNavaidWaypoint(p, NULL, radial, distanceNm);
1011 } else if (pieces.size() == 2) {
1012 FGAirport* apt = dynamic_cast<FGAirport*>(p.ptr());
1014 SG_LOG(SG_NAVAID, SG_INFO, "Waypoint is not an airport:" << pieces.front());
1018 if (!apt->hasRunwayWithIdent(pieces[1])) {
1019 SG_LOG(SG_NAVAID, SG_INFO, "No runway: " << pieces[1] << " at " << pieces[0]);
1023 FGRunway* runway = apt->getRunwayByIdent(pieces[1]);
1024 wpt = new NavaidWaypoint(runway, NULL);
1025 } else if (pieces.size() == 4) {
1026 // navid/radial/navid/radial notation
1027 FGPositionedRef p2 = FGPositioned::findClosestWithIdent(pieces[2], basePosition);
1029 SG_LOG( SG_NAVAID, SG_INFO, "Unable to find FGPositioned with ident:" << pieces[2]);
1033 double r1 = atof(pieces[1].c_str()),
1034 r2 = atof(pieces[3].c_str());
1038 SGGeod intersection;
1039 bool ok = SGGeodesy::radialIntersection(p->geod(), r1, p2->geod(), r2, intersection);
1041 SG_LOG(SG_NAVAID, SG_INFO, "no valid intersection for:" << target);
1045 std::string name = p->ident() + "-" + p2->ident();
1046 wpt = new BasicWaypt(intersection, name, NULL);
1050 SG_LOG(SG_NAVAID, SG_INFO, "Unable to parse waypoint:" << target);
1054 if (altSetting != RESTRICT_NONE) {
1055 wpt->setAltitude(altFt, altSetting);
1060 FlightPlan::Leg::Leg(FlightPlan* owner, WayptRef wpt) :
1062 _speedRestrict(RESTRICT_NONE),
1063 _altRestrict(RESTRICT_NONE),
1067 throw sg_exception("can't create FlightPlan::Leg without underlying waypoint");
1069 _speed = _altitudeFt = 0;
1072 FlightPlan::Leg* FlightPlan::Leg::cloneFor(FlightPlan* owner) const
1074 Leg* c = new Leg(owner, _waypt);
1077 c->_speedRestrict = _speedRestrict;
1078 c->_altitudeFt = _altitudeFt;
1079 c->_altRestrict = _altRestrict;
1084 FlightPlan::Leg* FlightPlan::Leg::nextLeg() const
1086 return _parent->legAtIndex(index() + 1);
1089 unsigned int FlightPlan::Leg::index() const
1091 return _parent->findLegIndex(this);
1094 int FlightPlan::Leg::altitudeFt() const
1096 if (_altRestrict != RESTRICT_NONE) {
1100 return _waypt->altitudeFt();
1103 int FlightPlan::Leg::speed() const
1105 if (_speedRestrict != RESTRICT_NONE) {
1109 return _waypt->speed();
1112 int FlightPlan::Leg::speedKts() const
1117 double FlightPlan::Leg::speedMach() const
1119 if (!isMachRestrict(_speedRestrict)) {
1123 return -(_speed / 100.0);
1126 RouteRestriction FlightPlan::Leg::altitudeRestriction() const
1128 if (_altRestrict != RESTRICT_NONE) {
1129 return _altRestrict;
1132 return _waypt->altitudeRestriction();
1135 RouteRestriction FlightPlan::Leg::speedRestriction() const
1137 if (_speedRestrict != RESTRICT_NONE) {
1138 return _speedRestrict;
1141 return _waypt->speedRestriction();
1144 void FlightPlan::Leg::setSpeed(RouteRestriction ty, double speed)
1146 _speedRestrict = ty;
1147 if (isMachRestrict(ty)) {
1148 _speed = (speed * -100);
1154 void FlightPlan::Leg::setAltitude(RouteRestriction ty, int altFt)
1157 _altitudeFt = altFt;
1160 double FlightPlan::Leg::courseDeg() const
1165 double FlightPlan::Leg::distanceNm() const
1167 return _pathDistance;
1170 double FlightPlan::Leg::distanceAlongRoute() const
1172 return _distanceAlongPath;
1175 void FlightPlan::rebuildLegData()
1177 _totalDistance = 0.0;
1178 int lastLeg = static_cast<int>(_legs.size()) - 1;
1179 for (int l=0; l<lastLeg; ++l) {
1180 Leg* cur = _legs[l];
1181 Leg* next = _legs[l + 1];
1183 std::pair<double, double> crsDist =
1184 next->waypoint()->courseAndDistanceFrom(cur->waypoint()->position());
1185 _legs[l]->_courseDeg = crsDist.first;
1186 _legs[l]->_pathDistance = crsDist.second * SG_METER_TO_NM;
1187 _legs[l]->_distanceAlongPath = _totalDistance;
1188 _totalDistance += crsDist.second * SG_METER_TO_NM;
1189 } // of legs iteration
1191 // set some data on the final leg
1193 // keep the same course as the final leg, when passing the final
1195 _legs[lastLeg]->_courseDeg = _legs[lastLeg - 1]->_courseDeg;
1196 _legs[lastLeg]->_pathDistance = 0.0;
1197 _legs[lastLeg]->_distanceAlongPath = _totalDistance;
1201 SGGeod FlightPlan::pointAlongRoute(int aIndex, double aOffsetNm) const
1203 if (aIndex >= (int) _legs.size()) {
1204 throw sg_range_exception();
1207 const int lastLeg = static_cast<int>(_legs.size()) - 1;
1208 // convert the relative offset and leg index into an absolute, positive
1209 // distance in nm from the route origin. This means we can simply walk
1210 // forwards to find the actual leg.
1211 Leg* leg = _legs[(aIndex >= 0) ? aIndex : lastLeg];
1212 double absolutePathDistance = leg->_distanceAlongPath + aOffsetNm;
1213 if (absolutePathDistance < 0.0) {
1214 return _legs[0]->waypoint()->position(); // begining of route
1217 if (absolutePathDistance > _totalDistance) {
1218 return _legs[lastLeg]->waypoint()->position(); // end of route
1221 // find the leg containing the absolute distance
1222 for (int l=0; l<lastLeg; ++l) {
1224 if (absolutePathDistance < leg->_pathDistance) {
1225 break; // found our matching leg
1227 absolutePathDistance -= leg->_pathDistance;
1228 } // of forwards walk along route to find leg
1230 return SGGeodesy::direct(leg->waypoint()->position(),
1231 leg->_courseDeg, absolutePathDistance * SG_NM_TO_METER);
1234 void FlightPlan::lockDelegate()
1236 if (_delegateLock == 0) {
1237 assert(!_departureChanged && !_arrivalChanged &&
1238 !_waypointsChanged && !_currentWaypointChanged);
1244 void FlightPlan::unlockDelegate()
1246 assert(_delegateLock > 0);
1247 if (_delegateLock > 1) {
1252 if (_departureChanged) {
1253 _departureChanged = false;
1255 _delegate->runDepartureChanged();
1259 if (_arrivalChanged) {
1260 _arrivalChanged = false;
1262 _delegate->runArrivalChanged();
1266 if (_waypointsChanged) {
1267 _waypointsChanged = false;
1270 _delegate->runWaypointsChanged();
1274 if (_currentWaypointChanged) {
1275 _currentWaypointChanged = false;
1277 _delegate->runCurrentWaypointChanged();
1284 void FlightPlan::registerDelegateFactory(DelegateFactory* df)
1286 FPDelegateFactoryVec::iterator it = std::find(static_delegateFactories.begin(),
1287 static_delegateFactories.end(), df);
1288 if (it != static_delegateFactories.end()) {
1289 throw sg_exception("duplicate delegate factory registration");
1292 static_delegateFactories.push_back(df);
1295 void FlightPlan::unregisterDelegateFactory(DelegateFactory* df)
1297 FPDelegateFactoryVec::iterator it = std::find(static_delegateFactories.begin(),
1298 static_delegateFactories.end(), df);
1299 if (it == static_delegateFactories.end()) {
1303 static_delegateFactories.erase(it);
1306 void FlightPlan::addDelegate(Delegate* d)
1308 // wrap any existing delegate(s) in the new one
1309 d->_inner = _delegate;
1313 void FlightPlan::removeDelegate(Delegate* d)
1315 if (d == _delegate) {
1316 _delegate = _delegate->_inner;
1317 } else if (_delegate) {
1318 _delegate->removeInner(d);
1322 FlightPlan::Delegate::Delegate() :
1323 _deleteWithPlan(false),
1328 FlightPlan::Delegate::~Delegate()
1332 void FlightPlan::Delegate::removeInner(Delegate* d)
1335 throw sg_exception("FlightPlan delegate not found");
1339 // replace with grand-child
1341 } else { // recurse downwards
1342 _inner->removeInner(d);
1346 void FlightPlan::Delegate::runDepartureChanged()
1348 if (_inner) _inner->runDepartureChanged();
1352 void FlightPlan::Delegate::runArrivalChanged()
1354 if (_inner) _inner->runArrivalChanged();
1358 void FlightPlan::Delegate::runWaypointsChanged()
1360 if (_inner) _inner->runWaypointsChanged();
1364 void FlightPlan::Delegate::runCurrentWaypointChanged()
1366 if (_inner) _inner->runCurrentWaypointChanged();
1367 currentWaypointChanged();
1370 void FlightPlan::Delegate::runCleared()
1372 if (_inner) _inner->runCleared();
1376 void FlightPlan::Delegate::runFinished()
1378 if (_inner) _inner->runFinished();
1382 } // of namespace flightgear