1 // route.cxx - classes supporting waypoints and route structures
3 // Written by James Turner, started 2009.
5 // Copyright (C) 2009 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.
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>
50 #include <Navaids/LevelDXML.hxx>
51 #include <Airports/simple.hxx>
58 namespace flightgear {
60 const double NO_MAG_VAR = -1000.0; // an impossible mag-var value
62 bool isMachRestrict(RouteRestriction rr)
64 return (rr == SPEED_RESTRICT_MACH) || (rr == SPEED_COMPUTED_MACH);
67 Waypt::Waypt(RouteBase* aOwner) :
70 _altRestrict(RESTRICT_NONE),
71 _speedRestrict(RESTRICT_NONE),
74 _magVarDeg(NO_MAG_VAR)
82 std::string Waypt::ident() const
87 bool Waypt::flag(WayptFlag aFlag) const
89 return ((_flags & aFlag) != 0);
92 void Waypt::setFlag(WayptFlag aFlag, bool aV)
94 _flags = (_flags & ~aFlag);
95 if (aV) _flags |= aFlag;
98 bool Waypt::matches(Waypt* aOther) const
101 if (ident() != aOther->ident()) { // cheap check first
105 return matches(aOther->position());
109 bool Waypt::matches(const SGGeod& aPos) const
111 double d = SGGeodesy::distanceM(position(), aPos);
112 return (d < 100.0); // 100 metres seems plenty
115 void Waypt::setAltitude(double aAlt, RouteRestriction aRestrict)
118 _altRestrict = aRestrict;
121 void Waypt::setSpeed(double aSpeed, RouteRestriction aRestrict)
124 _speedRestrict = aRestrict;
127 double Waypt::speedKts() const
129 assert(_speedRestrict != SPEED_RESTRICT_MACH);
133 double Waypt::speedMach() const
135 assert(_speedRestrict == SPEED_RESTRICT_MACH);
139 std::pair<double, double>
140 Waypt::courseAndDistanceFrom(const SGGeod& aPos) const
142 if (flag(WPT_DYNAMIC)) {
143 return std::make_pair(0.0, 0.0);
146 double course, az2, distance;
147 SGGeodesy::inverse(aPos, position(), course, az2, distance);
148 return std::make_pair(course, distance);
151 double Waypt::magvarDeg() const
153 if (_magVarDeg == NO_MAG_VAR) {
154 // derived classes with a default pos must override this method
155 assert(!(position() == SGGeod()));
157 double jd = globals->get_time_params()->getJD();
158 _magVarDeg = sgGetMagVar(position(), jd) * SG_RADIANS_TO_DEGREES;
164 double Waypt::headingRadialDeg() const
169 ///////////////////////////////////////////////////////////////////////////
172 static RouteRestriction restrictionFromString(const char* aStr)
174 std::string l = boost::to_lower_copy(std::string(aStr));
176 if (l == "at") return RESTRICT_AT;
177 if (l == "above") return RESTRICT_ABOVE;
178 if (l == "below") return RESTRICT_BELOW;
179 if (l == "none") return RESTRICT_NONE;
180 if (l == "mach") return SPEED_RESTRICT_MACH;
182 if (l.empty()) return RESTRICT_NONE;
183 throw sg_io_exception("unknown restriction specification:" + l,
184 "Route restrictFromString");
187 static const char* restrictionToString(RouteRestriction aRestrict)
190 case RESTRICT_AT: return "at";
191 case RESTRICT_BELOW: return "below";
192 case RESTRICT_ABOVE: return "above";
193 case RESTRICT_NONE: return "none";
194 case SPEED_RESTRICT_MACH: return "mach";
197 throw sg_exception("invalid route restriction",
198 "Route restrictToString");
202 Waypt* Waypt::createInstance(RouteBase* aOwner, const std::string& aTypeName)
205 if (aTypeName == "basic") {
206 r = new BasicWaypt(aOwner);
207 } else if (aTypeName == "navaid") {
208 r = new NavaidWaypoint(aOwner);
209 } else if (aTypeName == "offset-navaid") {
210 r = new OffsetNavaidWaypoint(aOwner);
211 } else if (aTypeName == "hold") {
212 r = new Hold(aOwner);
213 } else if (aTypeName == "runway") {
214 r = new RunwayWaypt(aOwner);
215 } else if (aTypeName == "hdgToAlt") {
216 r = new HeadingToAltitude(aOwner);
217 } else if (aTypeName == "dmeIntercept") {
218 r = new DMEIntercept(aOwner);
219 } else if (aTypeName == "radialIntercept") {
220 r = new RadialIntercept(aOwner);
221 } else if (aTypeName == "vectors") {
222 r = new ATCVectors(aOwner);
225 if (!r || (r->type() != aTypeName)) {
226 throw sg_exception("broken factory method for type:" + aTypeName,
227 "Waypt::createInstance");
233 WayptRef Waypt::createFromProperties(RouteBase* aOwner, SGPropertyNode_ptr aProp)
235 if (!aProp->hasChild("type")) {
236 throw sg_io_exception("bad props node, no type provided",
237 "Waypt::createFromProperties");
240 WayptRef nd(createInstance(aOwner, aProp->getStringValue("type")));
241 nd->initFromProperties(aProp);
245 void Waypt::saveAsNode(SGPropertyNode* n) const
247 n->setStringValue("type", type());
248 writeToProperties(n);
251 void Waypt::initFromProperties(SGPropertyNode_ptr aProp)
253 if (aProp->hasChild("generated")) {
254 setFlag(WPT_GENERATED, aProp->getBoolValue("generated"));
257 if (aProp->hasChild("overflight")) {
258 setFlag(WPT_OVERFLIGHT, aProp->getBoolValue("overflight"));
261 if (aProp->hasChild("arrival")) {
262 setFlag(WPT_ARRIVAL, aProp->getBoolValue("arrival"));
265 if (aProp->hasChild("approach")) {
266 setFlag(WPT_APPROACH, aProp->getBoolValue("approach"));
269 if (aProp->hasChild("departure")) {
270 setFlag(WPT_DEPARTURE, aProp->getBoolValue("departure"));
273 if (aProp->hasChild("miss")) {
274 setFlag(WPT_MISS, aProp->getBoolValue("miss"));
277 if (aProp->hasChild("alt-restrict")) {
278 _altRestrict = restrictionFromString(aProp->getStringValue("alt-restrict"));
279 _altitudeFt = aProp->getDoubleValue("altitude-ft");
282 if (aProp->hasChild("speed-restrict")) {
283 _speedRestrict = restrictionFromString(aProp->getStringValue("speed-restrict"));
284 _speed = aProp->getDoubleValue("speed");
290 void Waypt::writeToProperties(SGPropertyNode_ptr aProp) const
292 if (flag(WPT_OVERFLIGHT)) {
293 aProp->setBoolValue("overflight", true);
296 if (flag(WPT_DEPARTURE)) {
297 aProp->setBoolValue("departure", true);
300 if (flag(WPT_ARRIVAL)) {
301 aProp->setBoolValue("arrival", true);
304 if (flag(WPT_APPROACH)) {
305 aProp->setBoolValue("approach", true);
308 if (flag(WPT_MISS)) {
309 aProp->setBoolValue("miss", true);
312 if (flag(WPT_GENERATED)) {
313 aProp->setBoolValue("generated", true);
316 if (_altRestrict != RESTRICT_NONE) {
317 aProp->setStringValue("alt-restrict", restrictionToString(_altRestrict));
318 aProp->setDoubleValue("altitude-ft", _altitudeFt);
321 if (_speedRestrict != RESTRICT_NONE) {
322 aProp->setStringValue("speed-restrict", restrictionToString(_speedRestrict));
323 aProp->setDoubleValue("speed", _speed);
327 void RouteBase::dumpRouteToKML(const WayptVec& aRoute, const std::string& aName)
329 SGPath p = "/Users/jmt/Desktop/" + aName + ".kml";
331 f.open(p.str().c_str(), fstream::out | fstream::app);
333 SG_LOG(SG_GENERAL, SG_WARN, "unable to open:" << p.str());
338 f << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
339 "<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n"
342 dumpRouteToKMLLineString(aName, aRoute, f);
350 void RouteBase::dumpRouteToKMLLineString(const std::string& aIdent,
351 const WayptVec& aRoute, std::ostream& aStream)
354 aStream << "<Placemark>\n";
355 aStream << "<name>" << aIdent << "</name>\n";
356 aStream << "<LineString>\n";
357 aStream << "<tessellate>1</tessellate>\n";
358 aStream << "<coordinates>\n";
361 for (unsigned int i=0; i<aRoute.size(); ++i) {
362 SGGeod pos = aRoute[i]->position();
363 aStream << pos.getLongitudeDeg() << "," << pos.getLatitudeDeg() << " " << endl;
367 aStream << "</coordinates>\n"
369 "</Placemark>\n" << endl;
372 void RouteBase::loadAirportProcedures(const SGPath& aPath, FGAirport* aApt)
376 NavdataVisitor visitor(aApt, aPath);
377 readXML(aPath.str(), visitor);
378 } catch (sg_io_exception& ex) {
379 SG_LOG(SG_GENERAL, SG_WARN, "failure parsing procedures: " << aPath.str() <<
380 "\n\t" << ex.getMessage() << "\n\tat:" << ex.getLocation().asString());
381 } catch (sg_exception& ex) {
382 SG_LOG(SG_GENERAL, SG_WARN, "failure parsing procedures: " << aPath.str() <<
383 "\n\t" << ex.getMessage());
387 ////////////////////////////////////////////////////////////////////////////
389 FlightPlan::FlightPlan() :
391 _departureRunway(NULL),
392 _destinationRunway(NULL),
401 FlightPlan::~FlightPlan()
406 FlightPlan* FlightPlan::clone(const string& newIdent) const
408 FlightPlan* c = new FlightPlan();
409 c->_ident = newIdent.empty() ? _ident : newIdent;
411 // copy destination / departure data.
412 c->setDeparture(_departure);
413 c->setDeparture(_departureRunway);
416 c->setApproach(_approach);
417 } else if (_destinationRunway) {
418 c->setDestination(_destinationRunway);
419 } else if (_destination) {
420 c->setDestination(_destination);
427 for (int l=0; l < numLegs(); ++l) {
428 c->_legs.push_back(_legs[l]->cloneFor(c));
434 void FlightPlan::setIdent(const string& s)
439 string FlightPlan::ident() const
444 FlightPlan::Leg* FlightPlan::insertWayptAtIndex(Waypt* aWpt, int aIndex)
454 if ((aIndex == -1) || (aIndex > (int) _legs.size())) {
455 index = _legs.size();
458 insertWayptsAtIndex(wps, index);
459 return legAtIndex(aIndex);
462 void FlightPlan::insertWayptsAtIndex(const WayptVec& wps, int aIndex)
469 if ((aIndex == -1) || (aIndex > (int) _legs.size())) {
470 index = _legs.size();
473 LegVec::iterator it = _legs.begin();
476 int endIndex = index + wps.size() - 1;
477 if (_currentIndex >= endIndex) {
478 _currentIndex += wps.size();
482 BOOST_FOREACH(WayptRef wp, wps) {
483 newLegs.push_back(new Leg(this, wp));
486 _legs.insert(it, newLegs.begin(), newLegs.end());
490 _delegate->runWaypointsChanged();
494 void FlightPlan::deleteIndex(int aIndex)
497 if (aIndex < 0) { // negative indices count the the end
498 index = _legs.size() + index;
501 if ((index < 0) || (index >= numLegs())) {
502 SG_LOG(SG_AUTOPILOT, SG_WARN, "removeAtIndex with invalid index:" << aIndex);
505 LegVec::iterator it = _legs.begin();
511 bool curChanged = false;
512 if (_currentIndex == index) {
513 // current waypoint was removed
515 } else if (_currentIndex > index) {
516 --_currentIndex; // shift current index down if necessary
521 _delegate->runWaypointsChanged();
523 _delegate->runCurrentWaypointChanged();
528 void FlightPlan::clear()
531 BOOST_FOREACH(Leg* l, _legs) {
537 _delegate->runDepartureChanged();
538 _delegate->runArrivalChanged();
539 _delegate->runWaypointsChanged();
540 _delegate->runCurrentWaypointChanged();
544 int FlightPlan::clearWayptsWithFlag(WayptFlag flag)
547 for (unsigned int i=0; i<_legs.size(); ++i) {
549 if (!l->waypoint()->flag(flag)) {
553 // okay, we're going to clear this leg
555 if (_currentIndex > (int) i) {
560 LegVec::iterator it = _legs.begin();
566 return 0; // nothing was cleared, don't fire the delegate
571 _delegate->runWaypointsChanged();
572 _delegate->runCurrentWaypointChanged();
578 void FlightPlan::setCurrentIndex(int index)
580 if ((index < 0) || (index >= numLegs())) {
581 throw sg_range_exception("invalid leg index", "FlightPlan::setCurrentIndex");
584 if (index == _currentIndex) {
588 _currentIndex = index;
590 _delegate->runCurrentWaypointChanged();
594 int FlightPlan::findWayptIndex(const SGGeod& aPos) const
596 for (int i=0; i<numLegs(); ++i) {
597 if (_legs[i]->waypoint()->matches(aPos)) {
605 FlightPlan::Leg* FlightPlan::currentLeg() const
607 if ((_currentIndex < 0) || (_currentIndex >= numLegs()))
609 return legAtIndex(_currentIndex);
612 FlightPlan::Leg* FlightPlan::previousLeg() const
614 if (_currentIndex == 0) {
618 return legAtIndex(_currentIndex - 1);
621 FlightPlan::Leg* FlightPlan::nextLeg() const
623 if ((_currentIndex < 0) || ((_currentIndex + 1) >= numLegs())) {
627 return legAtIndex(_currentIndex + 1);
630 FlightPlan::Leg* FlightPlan::legAtIndex(int index) const
632 if ((index < 0) || (index >= numLegs())) {
633 throw sg_range_exception("index out of range", "FlightPlan::legAtIndex");
639 int FlightPlan::findLegIndex(const Leg *l) const
641 for (unsigned int i=0; i<_legs.size(); ++i) {
650 void FlightPlan::setDeparture(FGAirport* apt)
652 if (apt == _departure) {
657 _departureRunway = NULL;
661 _delegate->runDepartureChanged();
665 void FlightPlan::setDeparture(FGRunway* rwy)
667 if (_departureRunway == rwy) {
671 _departureRunway = rwy;
672 if (rwy->airport() != _departure) {
673 _departure = rwy->airport();
678 _delegate->runDepartureChanged();
682 void FlightPlan::setSID(SID* sid, const std::string& transition)
689 _sidTransition = transition;
692 _delegate->runDepartureChanged();
696 void FlightPlan::setSID(Transition* trans)
703 if (trans->parent()->type() != PROCEDURE_SID)
704 throw sg_exception("FlightPlan::setSID: transition does not belong to a SID");
706 setSID((SID*) trans->parent(), trans->ident());
709 Transition* FlightPlan::sidTransition() const
711 if (!_sid || _sidTransition.empty()) {
715 return _sid->findTransitionByName(_sidTransition);
718 void FlightPlan::setDestination(FGAirport* apt)
720 if (apt == _destination) {
725 _destinationRunway = NULL;
726 setSTAR((STAR*)NULL);
729 _delegate->runArrivalChanged();
733 void FlightPlan::setDestination(FGRunway* rwy)
735 if (_destinationRunway == rwy) {
739 _destinationRunway = rwy;
740 if (_destination != rwy->airport()) {
741 _destination = rwy->airport();
742 setSTAR((STAR*)NULL);
746 _delegate->runArrivalChanged();
750 void FlightPlan::setSTAR(STAR* star, const std::string& transition)
757 _starTransition = transition;
760 _delegate->runArrivalChanged();
764 void FlightPlan::setSTAR(Transition* trans)
767 setSTAR((STAR*) NULL);
771 if (trans->parent()->type() != PROCEDURE_STAR)
772 throw sg_exception("FlightPlan::setSTAR: transition does not belong to a STAR");
774 setSTAR((STAR*) trans->parent(), trans->ident());
777 Transition* FlightPlan::starTransition() const
779 if (!_star || _starTransition.empty()) {
783 return _star->findTransitionByName(_starTransition);
786 void FlightPlan::setApproach(flightgear::Approach *app)
788 if (_approach == app) {
794 // keep runway + airport in sync
795 if (_destinationRunway != _approach->runway()) {
796 _destinationRunway = _approach->runway();
799 if (_destination != _destinationRunway->airport()) {
800 _destination = _destinationRunway->airport();
805 _delegate->runArrivalChanged();
809 bool FlightPlan::save(const SGPath& path)
811 SG_LOG(SG_IO, SG_INFO, "Saving route to " << path.str());
813 SGPropertyNode_ptr d(new SGPropertyNode);
814 d->setIntValue("version", 2);
817 d->setStringValue("departure/airport", _departure->ident());
819 d->setStringValue("departure/sid", _sid->ident());
822 if (_departureRunway) {
823 d->setStringValue("departure/runway", _departureRunway->ident());
828 d->setStringValue("destination/airport", _destination->ident());
830 d->setStringValue("destination/star", _star->ident());
834 d->setStringValue("destination/approach", _approach->ident());
837 //d->setStringValue("destination/transition", destination->getStringValue("transition"));
839 if (_destinationRunway) {
840 d->setStringValue("destination/runway", _destinationRunway->ident());
845 SGPropertyNode* routeNode = d->getChild("route", 0, true);
846 for (unsigned int i=0; i<_legs.size(); ++i) {
847 Waypt* wpt = _legs[i]->waypoint();
848 wpt->saveAsNode(routeNode->getChild("wp", i, true));
849 } // of waypoint iteration
850 writeProperties(path.str(), d, true /* write-all */);
852 } catch (sg_exception& e) {
853 SG_LOG(SG_IO, SG_ALERT, "Failed to save flight-plan '" << path.str() << "'. " << e.getMessage());
858 bool FlightPlan::load(const SGPath& path)
862 SG_LOG(SG_IO, SG_ALERT, "Failed to load flight-plan '" << path.str()
863 << "'. The file does not exist.");
867 SGPropertyNode_ptr routeData(new SGPropertyNode);
868 SG_LOG(SG_IO, SG_INFO, "going to read flight-plan from:" << path.str());
872 readProperties(path.str(), routeData);
873 } catch (sg_exception& ) {
874 // if XML parsing fails, the file might be simple textual list of waypoints
875 Status = loadPlainTextRoute(path);
879 if (routeData.valid())
882 int version = routeData->getIntValue("version", 1);
884 loadVersion1XMLRoute(routeData);
885 } else if (version == 2) {
886 loadVersion2XMLRoute(routeData);
888 throw sg_io_exception("unsupported XML route version");
891 } catch (sg_exception& e) {
892 SG_LOG(SG_IO, SG_ALERT, "Failed to load flight-plan '" << e.getOrigin()
893 << "'. " << e.getMessage());
900 _delegate->runWaypointsChanged();
906 void FlightPlan::loadXMLRouteHeader(SGPropertyNode_ptr routeData)
909 SGPropertyNode* dep = routeData->getChild("departure");
911 string depIdent = dep->getStringValue("airport");
912 setDeparture((FGAirport*) fgFindAirportID(depIdent));
914 if (dep->hasChild("runway")) {
915 setDeparture(_departure->getRunwayByIdent(dep->getStringValue("runway")));
918 if (dep->hasChild("sid")) {
919 setSID(_departure->findSIDWithIdent(dep->getStringValue("sid")));
921 // departure->setStringValue("transition", dep->getStringValue("transition"));
926 SGPropertyNode* dst = routeData->getChild("destination");
928 setDestination((FGAirport*) fgFindAirportID(dst->getStringValue("airport")));
930 if (dst->hasChild("runway")) {
931 setDestination(_destination->getRunwayByIdent(dst->getStringValue("runway")));
934 if (dst->hasChild("star")) {
935 setSTAR(_destination->findSTARWithIdent(dst->getStringValue("star")));
938 if (dst->hasChild("approach")) {
939 setApproach(_destination->findApproachWithIdent(dst->getStringValue("approach")));
943 // destination->setStringValue("transition", dst->getStringValue("transition"));
947 SGPropertyNode* alt = routeData->getChild("alternate");
949 //alternate->setStringValue(alt->getStringValue("airport"));
953 SGPropertyNode* crs = routeData->getChild("cruise");
955 // cruise->setDoubleValue("speed-kts", crs->getDoubleValue("speed-kts"));
956 // cruise->setDoubleValue("mach", crs->getDoubleValue("mach"));
957 // cruise->setDoubleValue("altitude-ft", crs->getDoubleValue("altitude-ft"));
958 } // of cruise data loading
962 void FlightPlan::loadVersion2XMLRoute(SGPropertyNode_ptr routeData)
964 loadXMLRouteHeader(routeData);
968 SGPropertyNode_ptr routeNode = routeData->getChild("route", 0);
969 for (int i=0; i<routeNode->nChildren(); ++i) {
970 SGPropertyNode_ptr wpNode = routeNode->getChild("wp", i);
971 Leg* l = new Leg(this, Waypt::createFromProperties(NULL, wpNode));
973 } // of route iteration
976 void FlightPlan::loadVersion1XMLRoute(SGPropertyNode_ptr routeData)
978 loadXMLRouteHeader(routeData);
982 SGPropertyNode_ptr routeNode = routeData->getChild("route", 0);
983 for (int i=0; i<routeNode->nChildren(); ++i) {
984 SGPropertyNode_ptr wpNode = routeNode->getChild("wp", i);
985 Leg* l = new Leg(this, parseVersion1XMLWaypt(wpNode));
987 } // of route iteration
991 WayptRef FlightPlan::parseVersion1XMLWaypt(SGPropertyNode* aWP)
994 if (!_legs.empty()) {
995 lastPos = _legs.back()->waypoint()->position();
996 } else if (_departure) {
997 lastPos = _departure->geod();
1001 string ident(aWP->getStringValue("ident"));
1002 if (aWP->hasChild("longitude-deg")) {
1003 // explicit longitude/latitude
1004 w = new BasicWaypt(SGGeod::fromDeg(aWP->getDoubleValue("longitude-deg"),
1005 aWP->getDoubleValue("latitude-deg")), ident, NULL);
1008 string nid = aWP->getStringValue("navid", ident.c_str());
1009 FGPositionedRef p = FGPositioned::findClosestWithIdent(nid, lastPos);
1011 throw sg_io_exception("bad route file, unknown navid:" + nid);
1014 SGGeod pos(p->geod());
1015 if (aWP->hasChild("offset-nm") && aWP->hasChild("offset-radial")) {
1016 double radialDeg = aWP->getDoubleValue("offset-radial");
1017 // convert magnetic radial to a true radial!
1018 radialDeg += magvarDegAt(pos);
1019 double offsetNm = aWP->getDoubleValue("offset-nm");
1021 SGGeodesy::direct(p->geod(), radialDeg, offsetNm * SG_NM_TO_METER, pos, az2);
1024 w = new BasicWaypt(pos, ident, NULL);
1027 double altFt = aWP->getDoubleValue("altitude-ft", -9999.9);
1028 if (altFt > -9990.0) {
1029 w->setAltitude(altFt, RESTRICT_AT);
1035 bool FlightPlan::loadPlainTextRoute(const SGPath& path)
1038 sg_gzifstream in(path.str().c_str());
1039 if (!in.is_open()) {
1040 throw sg_io_exception("Cannot open file for reading.");
1046 getline(in, line, '\n');
1047 // trim CR from end of line, if found
1048 if (line[line.size() - 1] == '\r') {
1049 line.erase(line.size() - 1, 1);
1052 line = simgear::strutils::strip(line);
1053 if (line.empty() || (line[0] == '#')) {
1054 continue; // ignore empty/comment lines
1057 WayptRef w = waypointFromString(line);
1059 throw sg_io_exception("Failed to create waypoint from line '" + line + "'.");
1062 _legs.push_back(new Leg(this, w));
1063 } // of line iteration
1064 } catch (sg_exception& e) {
1065 SG_LOG(SG_IO, SG_ALERT, "Failed to load route from: '" << path.str() << "'. " << e.getMessage());
1073 double FlightPlan::magvarDegAt(const SGGeod& pos) const
1075 double jd = globals->get_time_params()->getJD();
1076 return sgGetMagVar(pos, jd) * SG_RADIANS_TO_DEGREES;
1079 WayptRef FlightPlan::waypointFromString(const string& tgt )
1081 string target(boost::to_upper_copy(tgt));
1086 RouteRestriction altSetting = RESTRICT_NONE;
1088 size_t pos = target.find( '@' );
1089 if ( pos != string::npos ) {
1090 altFt = atof( target.c_str() + pos + 1 );
1091 target = target.substr( 0, pos );
1092 if ( !strcmp(fgGetString("/sim/startup/units"), "meter") )
1093 altFt *= SG_METER_TO_FEET;
1094 altSetting = RESTRICT_AT;
1097 // check for lon,lat
1098 pos = target.find( ',' );
1099 if ( pos != string::npos ) {
1100 double lon = atof( target.substr(0, pos).c_str());
1101 double lat = atof( target.c_str() + pos + 1);
1103 char ew = (lon < 0.0) ? 'W' : 'E';
1104 char ns = (lat < 0.0) ? 'S' : 'N';
1105 snprintf(buf, 32, "%c%03d%c%03d", ew, (int) fabs(lon), ns, (int)fabs(lat));
1107 wpt = new BasicWaypt(SGGeod::fromDeg(lon, lat), buf, NULL);
1108 if (altSetting != RESTRICT_NONE) {
1109 wpt->setAltitude(altFt, altSetting);
1114 SGGeod basePosition;
1115 if (_legs.empty()) {
1116 // route is empty, use current position
1117 basePosition = globals->get_aircraft_position();
1119 basePosition = _legs.back()->waypoint()->position();
1122 string_list pieces(simgear::strutils::split(target, "/"));
1123 FGPositionedRef p = FGPositioned::findClosestWithIdent(pieces.front(), basePosition);
1125 SG_LOG( SG_AUTOPILOT, SG_INFO, "Unable to find FGPositioned with ident:" << pieces.front());
1129 double magvar = magvarDegAt(basePosition);
1131 if (pieces.size() == 1) {
1132 wpt = new NavaidWaypoint(p, NULL);
1133 } else if (pieces.size() == 3) {
1134 // navaid/radial/distance-nm notation
1135 double radial = atof(pieces[1].c_str()),
1136 distanceNm = atof(pieces[2].c_str());
1138 wpt = new OffsetNavaidWaypoint(p, NULL, radial, distanceNm);
1139 } else if (pieces.size() == 2) {
1140 FGAirport* apt = dynamic_cast<FGAirport*>(p.ptr());
1142 SG_LOG(SG_AUTOPILOT, SG_INFO, "Waypoint is not an airport:" << pieces.front());
1146 if (!apt->hasRunwayWithIdent(pieces[1])) {
1147 SG_LOG(SG_AUTOPILOT, SG_INFO, "No runway: " << pieces[1] << " at " << pieces[0]);
1151 FGRunway* runway = apt->getRunwayByIdent(pieces[1]);
1152 wpt = new NavaidWaypoint(runway, NULL);
1153 } else if (pieces.size() == 4) {
1154 // navid/radial/navid/radial notation
1155 FGPositionedRef p2 = FGPositioned::findClosestWithIdent(pieces[2], basePosition);
1157 SG_LOG( SG_AUTOPILOT, SG_INFO, "Unable to find FGPositioned with ident:" << pieces[2]);
1161 double r1 = atof(pieces[1].c_str()),
1162 r2 = atof(pieces[3].c_str());
1166 SGGeod intersection;
1167 bool ok = SGGeodesy::radialIntersection(p->geod(), r1, p2->geod(), r2, intersection);
1169 SG_LOG(SG_AUTOPILOT, SG_INFO, "no valid intersection for:" << target);
1173 std::string name = p->ident() + "-" + p2->ident();
1174 wpt = new BasicWaypt(intersection, name, NULL);
1178 SG_LOG(SG_AUTOPILOT, SG_INFO, "Unable to parse waypoint:" << target);
1182 if (altSetting != RESTRICT_NONE) {
1183 wpt->setAltitude(altFt, altSetting);
1188 FlightPlan::Leg::Leg(FlightPlan* owner, WayptRef wpt) :
1190 _speedRestrict(RESTRICT_NONE),
1191 _altRestrict(RESTRICT_NONE),
1195 throw sg_exception("can't create FlightPlan::Leg without underlying waypoint");
1197 _speed = _altitudeFt = 0;
1200 FlightPlan::Leg* FlightPlan::Leg::cloneFor(FlightPlan* owner) const
1202 Leg* c = new Leg(owner, _waypt);
1205 c->_speedRestrict = _speedRestrict;
1206 c->_altitudeFt = _altitudeFt;
1207 c->_altRestrict = _altRestrict;
1212 FlightPlan::Leg* FlightPlan::Leg::nextLeg() const
1214 return _parent->legAtIndex(index() + 1);
1217 unsigned int FlightPlan::Leg::index() const
1219 return _parent->findLegIndex(this);
1222 int FlightPlan::Leg::altitudeFt() const
1224 if (_altRestrict != RESTRICT_NONE) {
1228 return _waypt->altitudeFt();
1231 int FlightPlan::Leg::speed() const
1233 if (_speedRestrict != RESTRICT_NONE) {
1237 return _waypt->speed();
1240 int FlightPlan::Leg::speedKts() const
1245 double FlightPlan::Leg::speedMach() const
1247 if (!isMachRestrict(_speedRestrict)) {
1251 return -(_speed / 100.0);
1254 RouteRestriction FlightPlan::Leg::altitudeRestriction() const
1256 if (_altRestrict != RESTRICT_NONE) {
1257 return _altRestrict;
1260 return _waypt->altitudeRestriction();
1263 RouteRestriction FlightPlan::Leg::speedRestriction() const
1265 if (_speedRestrict != RESTRICT_NONE) {
1266 return _speedRestrict;
1269 return _waypt->speedRestriction();
1272 void FlightPlan::Leg::setSpeed(RouteRestriction ty, double speed)
1274 _speedRestrict = ty;
1275 if (isMachRestrict(ty)) {
1276 _speed = (speed * -100);
1282 void FlightPlan::Leg::setAltitude(RouteRestriction ty, int altFt)
1285 _altitudeFt = altFt;
1288 double FlightPlan::Leg::courseDeg() const
1293 double FlightPlan::Leg::distanceNm() const
1295 return _pathDistance;
1298 double FlightPlan::Leg::distanceAlongRoute() const
1300 return _distanceAlongPath;
1303 void FlightPlan::rebuildLegData()
1305 _totalDistance = 0.0;
1306 int lastLeg = static_cast<int>(_legs.size()) - 1;
1307 for (int l=0; l<lastLeg; ++l) {
1308 Leg* cur = _legs[l];
1309 Leg* next = _legs[l + 1];
1311 std::pair<double, double> crsDist =
1312 next->waypoint()->courseAndDistanceFrom(cur->waypoint()->position());
1313 _legs[l]->_courseDeg = crsDist.first;
1314 _legs[l]->_pathDistance = crsDist.second * SG_METER_TO_NM;
1315 _legs[l]->_distanceAlongPath = _totalDistance;
1316 _totalDistance += crsDist.second * SG_METER_TO_NM;
1317 } // of legs iteration
1320 void FlightPlan::setDelegate(Delegate* d)
1322 // wrap any existing delegate(s) in the new one
1323 d->_inner = _delegate;
1327 void FlightPlan::removeDelegate(Delegate* d)
1329 if (d == _delegate) {
1330 _delegate = _delegate->_inner;
1331 } else if (_delegate) {
1332 _delegate->removeInner(d);
1336 FlightPlan::Delegate::Delegate() :
1342 FlightPlan::Delegate::~Delegate()
1347 void FlightPlan::Delegate::removeInner(Delegate* d)
1354 // replace with grand-child
1356 } else { // recurse downwards
1357 _inner->removeInner(d);
1361 void FlightPlan::Delegate::runDepartureChanged()
1363 if (_inner) _inner->runDepartureChanged();
1367 void FlightPlan::Delegate::runArrivalChanged()
1369 if (_inner) _inner->runArrivalChanged();
1373 void FlightPlan::Delegate::runWaypointsChanged()
1375 if (_inner) _inner->runWaypointsChanged();
1379 void FlightPlan::Delegate::runCurrentWaypointChanged()
1381 if (_inner) _inner->runCurrentWaypointChanged();
1382 currentWaypointChanged();
1385 } // of namespace flightgear