// FlightPlan.cxx - flight plan object // Written by James Turner, started 2012. // // Copyright (C) 2012 Curtis L. Olson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as // published by the Free Software Foundation; either version 2 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "FlightPlan.hxx" // std #include #include // Boost #include #include #include // SimGear #include #include #include #include #include #include #include // FlightGear #include
#include "Main/fg_props.hxx" #include #include using std::string; using std::vector; using std::endl; using std::fstream; namespace flightgear { FlightPlan::FlightPlan() : _currentIndex(-1), _departureRunway(NULL), _destinationRunway(NULL), _sid(NULL), _star(NULL), _approach(NULL), _delegate(NULL) { } FlightPlan::~FlightPlan() { } FlightPlan* FlightPlan::clone(const string& newIdent) const { FlightPlan* c = new FlightPlan(); c->_ident = newIdent.empty() ? _ident : newIdent; // copy destination / departure data. c->setDeparture(_departure); c->setDeparture(_departureRunway); if (_approach) { c->setApproach(_approach); } else if (_destinationRunway) { c->setDestination(_destinationRunway); } else if (_destination) { c->setDestination(_destination); } c->setSTAR(_star); c->setSID(_sid); // copy legs for (int l=0; l < numLegs(); ++l) { c->_legs.push_back(_legs[l]->cloneFor(c)); } return c; } void FlightPlan::setIdent(const string& s) { _ident = s; } string FlightPlan::ident() const { return _ident; } FlightPlan::Leg* FlightPlan::insertWayptAtIndex(Waypt* aWpt, int aIndex) { if (!aWpt) { return NULL; } WayptVec wps; wps.push_back(aWpt); int index = aIndex; if ((aIndex == -1) || (aIndex > (int) _legs.size())) { index = _legs.size(); } insertWayptsAtIndex(wps, index); return legAtIndex(aIndex); } void FlightPlan::insertWayptsAtIndex(const WayptVec& wps, int aIndex) { if (wps.empty()) { return; } int index = aIndex; if ((aIndex == -1) || (aIndex > (int) _legs.size())) { index = _legs.size(); } LegVec::iterator it = _legs.begin(); it += index; int endIndex = index + wps.size() - 1; if (_currentIndex >= endIndex) { _currentIndex += wps.size(); } LegVec newLegs; BOOST_FOREACH(WayptRef wp, wps) { newLegs.push_back(new Leg(this, wp)); } _legs.insert(it, newLegs.begin(), newLegs.end()); rebuildLegData(); if (_delegate) { _delegate->runWaypointsChanged(); } } void FlightPlan::deleteIndex(int aIndex) { int index = aIndex; if (aIndex < 0) { // negative indices count the the end index = _legs.size() + index; } if ((index < 0) || (index >= numLegs())) { SG_LOG(SG_AUTOPILOT, SG_WARN, "removeAtIndex with invalid index:" << aIndex); return; } LegVec::iterator it = _legs.begin(); it += index; Leg* l = *it; _legs.erase(it); delete l; bool curChanged = false; if (_currentIndex == index) { // current waypoint was removed curChanged = true; } else if (_currentIndex > index) { --_currentIndex; // shift current index down if necessary } rebuildLegData(); if (_delegate) { _delegate->runWaypointsChanged(); if (curChanged) { _delegate->runCurrentWaypointChanged(); } } } void FlightPlan::clear() { _currentIndex = -1; BOOST_FOREACH(Leg* l, _legs) { delete l; } _legs.clear(); rebuildLegData(); if (_delegate) { _delegate->runDepartureChanged(); _delegate->runArrivalChanged(); _delegate->runWaypointsChanged(); _delegate->runCurrentWaypointChanged(); } } int FlightPlan::clearWayptsWithFlag(WayptFlag flag) { int count = 0; for (unsigned int i=0; i<_legs.size(); ++i) { Leg* l = _legs[i]; if (!l->waypoint()->flag(flag)) { continue; } // okay, we're going to clear this leg ++count; if (_currentIndex > (int) i) { --_currentIndex; } delete l; LegVec::iterator it = _legs.begin(); it += i; _legs.erase(it); } if (count == 0) { return 0; // nothing was cleared, don't fire the delegate } rebuildLegData(); if (_delegate) { _delegate->runWaypointsChanged(); _delegate->runCurrentWaypointChanged(); } return count; } void FlightPlan::setCurrentIndex(int index) { if ((index < -1) || (index >= numLegs())) { throw sg_range_exception("invalid leg index", "FlightPlan::setCurrentIndex"); } if (index == _currentIndex) { return; } _currentIndex = index; if (_delegate) { _delegate->runCurrentWaypointChanged(); } } int FlightPlan::findWayptIndex(const SGGeod& aPos) const { for (int i=0; iwaypoint()->matches(aPos)) { return i; } } return -1; } FlightPlan::Leg* FlightPlan::currentLeg() const { if ((_currentIndex < 0) || (_currentIndex >= numLegs())) return NULL; return legAtIndex(_currentIndex); } FlightPlan::Leg* FlightPlan::previousLeg() const { if (_currentIndex == 0) { return NULL; } return legAtIndex(_currentIndex - 1); } FlightPlan::Leg* FlightPlan::nextLeg() const { if ((_currentIndex < 0) || ((_currentIndex + 1) >= numLegs())) { return NULL; } return legAtIndex(_currentIndex + 1); } FlightPlan::Leg* FlightPlan::legAtIndex(int index) const { if ((index < 0) || (index >= numLegs())) { throw sg_range_exception("index out of range", "FlightPlan::legAtIndex"); } return _legs[index]; } int FlightPlan::findLegIndex(const Leg *l) const { for (unsigned int i=0; i<_legs.size(); ++i) { if (_legs[i] == l) { return i; } } return -1; } void FlightPlan::setDeparture(FGAirport* apt) { if (apt == _departure) { return; } _departure = apt; _departureRunway = NULL; setSID((SID*)NULL); if (_delegate) { _delegate->runDepartureChanged(); } } void FlightPlan::setDeparture(FGRunway* rwy) { if (_departureRunway == rwy) { return; } _departureRunway = rwy; if (rwy->airport() != _departure) { _departure = rwy->airport(); setSID((SID*)NULL); } if (_delegate) { _delegate->runDepartureChanged(); } } void FlightPlan::setSID(SID* sid, const std::string& transition) { if (sid == _sid) { return; } _sid = sid; _sidTransition = transition; if (_delegate) { _delegate->runDepartureChanged(); } } void FlightPlan::setSID(Transition* trans) { if (!trans) { setSID((SID*) NULL); return; } if (trans->parent()->type() != PROCEDURE_SID) throw sg_exception("FlightPlan::setSID: transition does not belong to a SID"); setSID((SID*) trans->parent(), trans->ident()); } Transition* FlightPlan::sidTransition() const { if (!_sid || _sidTransition.empty()) { return NULL; } return _sid->findTransitionByName(_sidTransition); } void FlightPlan::setDestination(FGAirport* apt) { if (apt == _destination) { return; } _destination = apt; _destinationRunway = NULL; setSTAR((STAR*)NULL); if (_delegate) { _delegate->runArrivalChanged(); } } void FlightPlan::setDestination(FGRunway* rwy) { if (_destinationRunway == rwy) { return; } _destinationRunway = rwy; if (_destination != rwy->airport()) { _destination = rwy->airport(); setSTAR((STAR*)NULL); } if (_delegate) { _delegate->runArrivalChanged(); } } void FlightPlan::setSTAR(STAR* star, const std::string& transition) { if (_star == star) { return; } _star = star; _starTransition = transition; if (_delegate) { _delegate->runArrivalChanged(); } } void FlightPlan::setSTAR(Transition* trans) { if (!trans) { setSTAR((STAR*) NULL); return; } if (trans->parent()->type() != PROCEDURE_STAR) throw sg_exception("FlightPlan::setSTAR: transition does not belong to a STAR"); setSTAR((STAR*) trans->parent(), trans->ident()); } Transition* FlightPlan::starTransition() const { if (!_star || _starTransition.empty()) { return NULL; } return _star->findTransitionByName(_starTransition); } void FlightPlan::setApproach(flightgear::Approach *app) { if (_approach == app) { return; } _approach = app; if (app) { // keep runway + airport in sync if (_destinationRunway != _approach->runway()) { _destinationRunway = _approach->runway(); } if (_destination != _destinationRunway->airport()) { _destination = _destinationRunway->airport(); } } if (_delegate) { _delegate->runArrivalChanged(); } } bool FlightPlan::save(const SGPath& path) { SG_LOG(SG_IO, SG_INFO, "Saving route to " << path.str()); try { SGPropertyNode_ptr d(new SGPropertyNode); d->setIntValue("version", 2); if (_departure) { d->setStringValue("departure/airport", _departure->ident()); if (_sid) { d->setStringValue("departure/sid", _sid->ident()); } if (_departureRunway) { d->setStringValue("departure/runway", _departureRunway->ident()); } } if (_destination) { d->setStringValue("destination/airport", _destination->ident()); if (_star) { d->setStringValue("destination/star", _star->ident()); } if (_approach) { d->setStringValue("destination/approach", _approach->ident()); } //d->setStringValue("destination/transition", destination->getStringValue("transition")); if (_destinationRunway) { d->setStringValue("destination/runway", _destinationRunway->ident()); } } // route nodes SGPropertyNode* routeNode = d->getChild("route", 0, true); for (unsigned int i=0; i<_legs.size(); ++i) { Waypt* wpt = _legs[i]->waypoint(); wpt->saveAsNode(routeNode->getChild("wp", i, true)); } // of waypoint iteration writeProperties(path.str(), d, true /* write-all */); return true; } catch (sg_exception& e) { SG_LOG(SG_IO, SG_ALERT, "Failed to save flight-plan '" << path.str() << "'. " << e.getMessage()); return false; } } bool FlightPlan::load(const SGPath& path) { if (!path.exists()) { SG_LOG(SG_IO, SG_ALERT, "Failed to load flight-plan '" << path.str() << "'. The file does not exist."); return false; } SGPropertyNode_ptr routeData(new SGPropertyNode); SG_LOG(SG_IO, SG_INFO, "going to read flight-plan from:" << path.str()); bool Status = false; try { readProperties(path.str(), routeData); } catch (sg_exception& ) { // if XML parsing fails, the file might be simple textual list of waypoints Status = loadPlainTextRoute(path); routeData = 0; } if (routeData.valid()) { try { int version = routeData->getIntValue("version", 1); if (version == 1) { loadVersion1XMLRoute(routeData); } else if (version == 2) { loadVersion2XMLRoute(routeData); } else { throw sg_io_exception("unsupported XML route version"); } Status = true; } catch (sg_exception& e) { SG_LOG(SG_IO, SG_ALERT, "Failed to load flight-plan '" << e.getOrigin() << "'. " << e.getMessage()); Status = false; } } rebuildLegData(); if (_delegate) { _delegate->runWaypointsChanged(); } return Status; } void FlightPlan::loadXMLRouteHeader(SGPropertyNode_ptr routeData) { // departure nodes SGPropertyNode* dep = routeData->getChild("departure"); if (dep) { string depIdent = dep->getStringValue("airport"); setDeparture((FGAirport*) fgFindAirportID(depIdent)); if (_departure) { if (dep->hasChild("runway")) { setDeparture(_departure->getRunwayByIdent(dep->getStringValue("runway"))); } if (dep->hasChild("sid")) { setSID(_departure->findSIDWithIdent(dep->getStringValue("sid"))); } // departure->setStringValue("transition", dep->getStringValue("transition")); } } // destination SGPropertyNode* dst = routeData->getChild("destination"); if (dst) { setDestination((FGAirport*) fgFindAirportID(dst->getStringValue("airport"))); if (_destination) { if (dst->hasChild("runway")) { setDestination(_destination->getRunwayByIdent(dst->getStringValue("runway"))); } if (dst->hasChild("star")) { setSTAR(_destination->findSTARWithIdent(dst->getStringValue("star"))); } if (dst->hasChild("approach")) { setApproach(_destination->findApproachWithIdent(dst->getStringValue("approach"))); } } // destination->setStringValue("transition", dst->getStringValue("transition")); } // alternate SGPropertyNode* alt = routeData->getChild("alternate"); if (alt) { //alternate->setStringValue(alt->getStringValue("airport")); } // cruise SGPropertyNode* crs = routeData->getChild("cruise"); if (crs) { // cruise->setDoubleValue("speed-kts", crs->getDoubleValue("speed-kts")); // cruise->setDoubleValue("mach", crs->getDoubleValue("mach")); // cruise->setDoubleValue("altitude-ft", crs->getDoubleValue("altitude-ft")); } // of cruise data loading } void FlightPlan::loadVersion2XMLRoute(SGPropertyNode_ptr routeData) { loadXMLRouteHeader(routeData); // route nodes _legs.clear(); SGPropertyNode_ptr routeNode = routeData->getChild("route", 0); for (int i=0; inChildren(); ++i) { SGPropertyNode_ptr wpNode = routeNode->getChild("wp", i); Leg* l = new Leg(this, Waypt::createFromProperties(NULL, wpNode)); _legs.push_back(l); } // of route iteration } void FlightPlan::loadVersion1XMLRoute(SGPropertyNode_ptr routeData) { loadXMLRouteHeader(routeData); // _legs nodes _legs.clear(); SGPropertyNode_ptr routeNode = routeData->getChild("route", 0); for (int i=0; inChildren(); ++i) { SGPropertyNode_ptr wpNode = routeNode->getChild("wp", i); Leg* l = new Leg(this, parseVersion1XMLWaypt(wpNode)); _legs.push_back(l); } // of route iteration } WayptRef FlightPlan::parseVersion1XMLWaypt(SGPropertyNode* aWP) { SGGeod lastPos; if (!_legs.empty()) { lastPos = _legs.back()->waypoint()->position(); } else if (_departure) { lastPos = _departure->geod(); } WayptRef w; string ident(aWP->getStringValue("ident")); if (aWP->hasChild("longitude-deg")) { // explicit longitude/latitude w = new BasicWaypt(SGGeod::fromDeg(aWP->getDoubleValue("longitude-deg"), aWP->getDoubleValue("latitude-deg")), ident, NULL); } else { string nid = aWP->getStringValue("navid", ident.c_str()); FGPositionedRef p = FGPositioned::findClosestWithIdent(nid, lastPos); if (!p) { throw sg_io_exception("bad route file, unknown navid:" + nid); } SGGeod pos(p->geod()); if (aWP->hasChild("offset-nm") && aWP->hasChild("offset-radial")) { double radialDeg = aWP->getDoubleValue("offset-radial"); // convert magnetic radial to a true radial! radialDeg += magvarDegAt(pos); double offsetNm = aWP->getDoubleValue("offset-nm"); double az2; SGGeodesy::direct(p->geod(), radialDeg, offsetNm * SG_NM_TO_METER, pos, az2); } w = new BasicWaypt(pos, ident, NULL); } double altFt = aWP->getDoubleValue("altitude-ft", -9999.9); if (altFt > -9990.0) { w->setAltitude(altFt, RESTRICT_AT); } return w; } bool FlightPlan::loadPlainTextRoute(const SGPath& path) { try { sg_gzifstream in(path.str().c_str()); if (!in.is_open()) { throw sg_io_exception("Cannot open file for reading."); } _legs.clear(); while (!in.eof()) { string line; getline(in, line, '\n'); // trim CR from end of line, if found if (line[line.size() - 1] == '\r') { line.erase(line.size() - 1, 1); } line = simgear::strutils::strip(line); if (line.empty() || (line[0] == '#')) { continue; // ignore empty/comment lines } WayptRef w = waypointFromString(line); if (!w) { throw sg_io_exception("Failed to create waypoint from line '" + line + "'."); } _legs.push_back(new Leg(this, w)); } // of line iteration } catch (sg_exception& e) { SG_LOG(SG_IO, SG_ALERT, "Failed to load route from: '" << path.str() << "'. " << e.getMessage()); _legs.clear(); return false; } return true; } double FlightPlan::magvarDegAt(const SGGeod& pos) const { double jd = globals->get_time_params()->getJD(); return sgGetMagVar(pos, jd) * SG_RADIANS_TO_DEGREES; } WayptRef FlightPlan::waypointFromString(const string& tgt ) { string target(boost::to_upper_copy(tgt)); WayptRef wpt; // extract altitude double altFt = 0.0; RouteRestriction altSetting = RESTRICT_NONE; size_t pos = target.find( '@' ); if ( pos != string::npos ) { altFt = atof( target.c_str() + pos + 1 ); target = target.substr( 0, pos ); if ( !strcmp(fgGetString("/sim/startup/units"), "meter") ) altFt *= SG_METER_TO_FEET; altSetting = RESTRICT_AT; } // check for lon,lat pos = target.find( ',' ); if ( pos != string::npos ) { double lon = atof( target.substr(0, pos).c_str()); double lat = atof( target.c_str() + pos + 1); char buf[32]; char ew = (lon < 0.0) ? 'W' : 'E'; char ns = (lat < 0.0) ? 'S' : 'N'; snprintf(buf, 32, "%c%03d%c%03d", ew, (int) fabs(lon), ns, (int)fabs(lat)); wpt = new BasicWaypt(SGGeod::fromDeg(lon, lat), buf, NULL); if (altSetting != RESTRICT_NONE) { wpt->setAltitude(altFt, altSetting); } return wpt; } SGGeod basePosition; if (_legs.empty()) { // route is empty, use current position basePosition = globals->get_aircraft_position(); } else { basePosition = _legs.back()->waypoint()->position(); } string_list pieces(simgear::strutils::split(target, "/")); FGPositionedRef p = FGPositioned::findClosestWithIdent(pieces.front(), basePosition); if (!p) { SG_LOG( SG_AUTOPILOT, SG_INFO, "Unable to find FGPositioned with ident:" << pieces.front()); return NULL; } double magvar = magvarDegAt(basePosition); if (pieces.size() == 1) { wpt = new NavaidWaypoint(p, NULL); } else if (pieces.size() == 3) { // navaid/radial/distance-nm notation double radial = atof(pieces[1].c_str()), distanceNm = atof(pieces[2].c_str()); radial += magvar; wpt = new OffsetNavaidWaypoint(p, NULL, radial, distanceNm); } else if (pieces.size() == 2) { FGAirport* apt = dynamic_cast(p.ptr()); if (!apt) { SG_LOG(SG_AUTOPILOT, SG_INFO, "Waypoint is not an airport:" << pieces.front()); return NULL; } if (!apt->hasRunwayWithIdent(pieces[1])) { SG_LOG(SG_AUTOPILOT, SG_INFO, "No runway: " << pieces[1] << " at " << pieces[0]); return NULL; } FGRunway* runway = apt->getRunwayByIdent(pieces[1]); wpt = new NavaidWaypoint(runway, NULL); } else if (pieces.size() == 4) { // navid/radial/navid/radial notation FGPositionedRef p2 = FGPositioned::findClosestWithIdent(pieces[2], basePosition); if (!p2) { SG_LOG( SG_AUTOPILOT, SG_INFO, "Unable to find FGPositioned with ident:" << pieces[2]); return NULL; } double r1 = atof(pieces[1].c_str()), r2 = atof(pieces[3].c_str()); r1 += magvar; r2 += magvar; SGGeod intersection; bool ok = SGGeodesy::radialIntersection(p->geod(), r1, p2->geod(), r2, intersection); if (!ok) { SG_LOG(SG_AUTOPILOT, SG_INFO, "no valid intersection for:" << target); return NULL; } std::string name = p->ident() + "-" + p2->ident(); wpt = new BasicWaypt(intersection, name, NULL); } if (!wpt) { SG_LOG(SG_AUTOPILOT, SG_INFO, "Unable to parse waypoint:" << target); return NULL; } if (altSetting != RESTRICT_NONE) { wpt->setAltitude(altFt, altSetting); } return wpt; } FlightPlan::Leg::Leg(FlightPlan* owner, WayptRef wpt) : _parent(owner), _speedRestrict(RESTRICT_NONE), _altRestrict(RESTRICT_NONE), _waypt(wpt) { if (!wpt.valid()) { throw sg_exception("can't create FlightPlan::Leg without underlying waypoint"); } _speed = _altitudeFt = 0; } FlightPlan::Leg* FlightPlan::Leg::cloneFor(FlightPlan* owner) const { Leg* c = new Leg(owner, _waypt); // clone local data c->_speed = _speed; c->_speedRestrict = _speedRestrict; c->_altitudeFt = _altitudeFt; c->_altRestrict = _altRestrict; return c; } FlightPlan::Leg* FlightPlan::Leg::nextLeg() const { return _parent->legAtIndex(index() + 1); } unsigned int FlightPlan::Leg::index() const { return _parent->findLegIndex(this); } int FlightPlan::Leg::altitudeFt() const { if (_altRestrict != RESTRICT_NONE) { return _altitudeFt; } return _waypt->altitudeFt(); } int FlightPlan::Leg::speed() const { if (_speedRestrict != RESTRICT_NONE) { return _speed; } return _waypt->speed(); } int FlightPlan::Leg::speedKts() const { return speed(); } double FlightPlan::Leg::speedMach() const { if (!isMachRestrict(_speedRestrict)) { return 0.0; } return -(_speed / 100.0); } RouteRestriction FlightPlan::Leg::altitudeRestriction() const { if (_altRestrict != RESTRICT_NONE) { return _altRestrict; } return _waypt->altitudeRestriction(); } RouteRestriction FlightPlan::Leg::speedRestriction() const { if (_speedRestrict != RESTRICT_NONE) { return _speedRestrict; } return _waypt->speedRestriction(); } void FlightPlan::Leg::setSpeed(RouteRestriction ty, double speed) { _speedRestrict = ty; if (isMachRestrict(ty)) { _speed = (speed * -100); } else { _speed = speed; } } void FlightPlan::Leg::setAltitude(RouteRestriction ty, int altFt) { _altRestrict = ty; _altitudeFt = altFt; } double FlightPlan::Leg::courseDeg() const { return _courseDeg; } double FlightPlan::Leg::distanceNm() const { return _pathDistance; } double FlightPlan::Leg::distanceAlongRoute() const { return _distanceAlongPath; } void FlightPlan::rebuildLegData() { _totalDistance = 0.0; int lastLeg = static_cast(_legs.size()) - 1; for (int l=0; l crsDist = next->waypoint()->courseAndDistanceFrom(cur->waypoint()->position()); _legs[l]->_courseDeg = crsDist.first; _legs[l]->_pathDistance = crsDist.second * SG_METER_TO_NM; _legs[l]->_distanceAlongPath = _totalDistance; _totalDistance += crsDist.second * SG_METER_TO_NM; } // of legs iteration } void FlightPlan::setDelegate(Delegate* d) { // wrap any existing delegate(s) in the new one d->_inner = _delegate; _delegate = d; } void FlightPlan::removeDelegate(Delegate* d) { if (d == _delegate) { _delegate = _delegate->_inner; } else if (_delegate) { _delegate->removeInner(d); } } FlightPlan::Delegate::Delegate() : _inner(NULL) { } FlightPlan::Delegate::~Delegate() { } void FlightPlan::Delegate::removeInner(Delegate* d) { if (!_inner) { return; } if (_inner == d) { // replace with grand-child _inner = d->_inner; } else { // recurse downwards _inner->removeInner(d); } } void FlightPlan::Delegate::runDepartureChanged() { if (_inner) _inner->runDepartureChanged(); departureChanged(); } void FlightPlan::Delegate::runArrivalChanged() { if (_inner) _inner->runArrivalChanged(); arrivalChanged(); } void FlightPlan::Delegate::runWaypointsChanged() { if (_inner) _inner->runWaypointsChanged(); waypointsChanged(); } void FlightPlan::Delegate::runCurrentWaypointChanged() { if (_inner) _inner->runCurrentWaypointChanged(); currentWaypointChanged(); } } // of namespace flightgear