// 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. #include "config.h" #include "FlightPlan.hxx" // std #include #include #include #include #include // SimGear #include #include #include #include #include #include #include #include // FlightGear #include
#include "Main/fg_props.hxx" #include #include #include #include #include #include #include
using std::string; using std::vector; using std::endl; using std::fstream; namespace { const string_list static_icaoFlightRulesCode = { "V", "I", "Y", "Z" }; const string_list static_icaoFlightTypeCode = { "S", "N", "G", "M", "X" }; } // of anonymous namespace namespace flightgear { // implemented in route.cxx const char* restrictionToString(RouteRestriction aRestrict); typedef std::vector FPDelegateFactoryVec; static FPDelegateFactoryVec static_delegateFactories; FlightPlan::FlightPlan(bool isRoute) : _isRoute(isRoute), _currentIndex(-1), _followLegTrackToFix(true), _aircraftCategory(ICAO_AIRCRAFT_CATEGORY_C), _departureRunway(nullptr), _destinationRunway(nullptr), _sid(nullptr), _star(nullptr), _approach(nullptr), _totalDistance(0.0) { _departureChanged = _arrivalChanged = _waypointsChanged = _currentWaypointChanged = false; _cruiseDataChanged = false; for (auto factory : static_delegateFactories) { Delegate* d = factory->createFlightPlanDelegate(this); if (d) { // factory might not always create a delegate d->_factory = factory; // record for clean-up purposes addDelegate(d); } } } FlightPlanRef FlightPlan::create() { return new FlightPlan(false); } FlightPlanRef FlightPlan::createRoute() { return new FlightPlan(true); } FlightPlan::~FlightPlan() { // clean up delegates for (auto d : _delegates) { if (d->_factory) { auto f = d->_factory; f->destroyFlightPlanDelegate(this, d); } } } FlightPlanRef FlightPlan::clone(const string& newIdent, bool convertIntoFlightPlan) const { // this is the only place we allow conversion of a route into an active FP, // by design. Forces people to clone-to-a-flight-plan if they want to // activate a route. FlightPlanRef c = new FlightPlan(convertIntoFlightPlan ? false : _isRoute); c->_ident = newIdent.empty() ? _ident : newIdent; c->lockDelegates(); // copy destination / departure data. c->setDeparture(_departure); c->setDeparture(_departureRunway); if (_approach) { c->setApproach(_approach, _approachTransition); } else if (_destinationRunway) { c->setDestination(_destinationRunway); } else if (_destination) { c->setDestination(_destination); } c->setSTAR(_star, _starTransition); c->setSID(_sid, _sidTransition); // mark data as unchanged since this is a clean plan c->_arrivalChanged = false; c->_departureChanged = false; // copy cruise data if (_cruiseFlightLevel > 0) { c->setCruiseFlightLevel(_cruiseFlightLevel); } else if (_cruiseAltitudeFt > 0) { c->setCruiseAltitudeFt(_cruiseAltitudeFt); } else if (_cruiseAltitudeM > 0) { c->setCruiseAltitudeM(_cruiseAltitudeM); } if (_cruiseAirspeedMach > 0) { c->setCruiseSpeedMach(_cruiseAirspeedMach); } else if (_cruiseAirspeedKnots > 0) { c->setCruiseSpeedKnots(_cruiseAirspeedKnots); } else if (_cruiseAirspeedKph > 0) { c->setCruiseSpeedKPH(_cruiseAirspeedKph); } c->_didLoadFP = true; // set the loaded flag to give delegates a chance // copy legs c->_waypointsChanged = true; for (int l=0; l < numLegs(); ++l) { c->_legs.push_back(_legs[l]->cloneFor(c)); } c->expandVias(); c->unlockDelegates(); return c; } void FlightPlan::setIdent(const string& s) { _ident = s; } string FlightPlan::ident() const { return _ident; } FlightPlan::LegRef FlightPlan::insertWayptAtIndex(Waypt* aWpt, int aIndex) { if (!aWpt) { return nullptr; } WayptVec wps; wps.push_back(aWpt); int index = aIndex; if ((aIndex == -1) || (aIndex > (int) _legs.size())) { index = _legs.size(); } insertWayptsAtIndex(wps, index); return legAtIndex(index); } static WayptVec copyWaypointsExpandingVias(WayptRef preceeding, const WayptVec& wps) { WayptVec result; result.reserve(wps.size()); for (auto wp : wps) { if (wp->type() == "via") { Via* via = static_cast(wp.get()); WayptVec viaPoints = via->expandToWaypoints(preceeding); result.insert(result.end(), viaPoints.begin(), viaPoints.end()); } else { // everything else is copied directly result.push_back(wp); } } return result; } 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(); } WayptVec toInsertWps = wps; // catch insert of VIAs here if (!_isRoute && (index > 0)) { const auto pre = _legs.at(index - 1)->waypoint(); toInsertWps = copyWaypointsExpandingVias(pre, wps); } else if (index == 0) { if (wps.front()->type() == "via") { SG_LOG(SG_AUTOPILOT, SG_DEV_ALERT, "Inserting a VIA at leg 0 of flight-plan, VIA cannot be expanded"); } } auto it = _legs.begin() + index; int endIndex = index + toInsertWps.size() - 1; if (_currentIndex >= endIndex) { _currentIndex += toInsertWps.size(); } LegVec newLegs; for (WayptRef wp : toInsertWps) { newLegs.push_back(LegRef{new Leg(this, wp)}); } lockDelegates(); _waypointsChanged = true; _legs.insert(it, newLegs.begin(), newLegs.end()); unlockDelegates(); } 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_NAVAID, SG_WARN, "removeAtIndex with invalid index:" << aIndex); return; } lockDelegates(); _waypointsChanged = true; auto it = _legs.begin() + index; LegRef l = *it; _legs.erase(it); l->_parent = nullptr; // orphan the leg so it's clear from Nasal if (_currentIndex == index) { // current waypoint was removed _currentWaypointChanged = true; } else if (_currentIndex > index) { --_currentIndex; // shift current index down if necessary } unlockDelegates(); } void FlightPlan::clearAll() { lockDelegates(); _departure.clear(); _departureRunway = nullptr; _destinationRunway = nullptr; _destination.clear(); _sid.clear(); _sidTransition.clear(); _star.clear(); _starTransition.clear(); _approach.clear(); _approachTransition.clear(); _alternate.clear(); _cruiseAirspeedMach = 0.0; _cruiseAirspeedKnots = 0; _cruiseAirspeedKph = 0; _cruiseFlightLevel = 0; _cruiseAltitudeFt = 0; _cruiseAltitudeM = 0; clearLegs(); unlockDelegates(); } void FlightPlan::clearLegs() { // some badly behaved CDU implementations call clear on a Nasal timer // during startup. if (_legs.empty() && (_currentIndex < 0)) { return; } lockDelegates(); _waypointsChanged = true; _currentWaypointChanged = true; _arrivalChanged = true; _departureChanged = true; _cruiseDataChanged = true; _currentIndex = -1; _legs.clear(); notifyCleared(); unlockDelegates(); } int FlightPlan::clearWayptsWithFlag(WayptFlag flag) { int count = 0; // first pass, fix up currentIndex for (int i=0; i<_currentIndex; ++i) { const auto& l = _legs.at(i); if (l->waypoint()->flag(flag)) { ++count; } } // test if the current leg will be removed bool currentIsBeingCleared = false; Leg* curLeg = currentLeg(); if (curLeg) { currentIsBeingCleared = curLeg->waypoint()->flag(flag); } _currentIndex -= count; // if we're clearing the current waypoint, what shall we do with the // index? there's various options, but safest is to select no waypoint // and let the use re-activate. // http://code.google.com/p/flightgear-bugs/issues/detail?id=1134 if (currentIsBeingCleared) { SG_LOG(SG_GENERAL, SG_INFO, "FlightPlan::clearWayptsWithFlag: currentIsBeingCleared:" << currentIsBeingCleared); _currentIndex = -1; } // now delete and remove int numDeleted = 0; auto it = std::remove_if(_legs.begin(), _legs.end(), [flag, &numDeleted](const LegRef& leg) { if (leg->waypoint()->flag(flag)) { ++numDeleted; return true; } return false; }); if (it == _legs.end()) { return 0; // nothing was cleared, don't fire the delegate } lockDelegates(); _waypointsChanged = true; if ((count > 0) || currentIsBeingCleared) { _currentWaypointChanged = true; } _legs.erase(it, _legs.end()); if (_legs.empty()) { // maybe all legs were deleted notifyCleared(); } unlockDelegates(); return numDeleted; } bool FlightPlan::isRoute() const { return _isRoute; } bool FlightPlan::isActive() const { if (_isRoute) return false; return (_currentIndex >= 0); } void FlightPlan::setCurrentIndex(int index) { if ((index < -1) || (index >= numLegs())) { throw sg_range_exception("invalid leg index", "FlightPlan::setCurrentIndex"); } if (index == _currentIndex) { return; } lockDelegates(); _currentIndex = index; _currentWaypointChanged = true; unlockDelegates(); } void FlightPlan::sequence() { lockDelegates(); for (auto d : _delegates) { d->sequence(); } unlockDelegates(); } void FlightPlan::finish() { if (_isRoute) { throw sg_exception("Called finish on FlightPlan marked isRoute"); } if (_currentIndex == -1) { return; } lockDelegates(); _currentIndex = -1; _currentWaypointChanged = true; for (auto d : _delegates) { d->endOfFlightPlan(); } unlockDelegates(); } int FlightPlan::findWayptIndex(const SGGeod& aPos) const { for (int i=0; iwaypoint()->matches(aPos)) { return i; } } return -1; } int FlightPlan::findWayptIndex(const FGPositionedRef aPos) const { for (int i=0; iwaypoint()->matches(aPos)) { return i; } } return -1; } FlightPlan::LegRef FlightPlan::currentLeg() const { if ((_currentIndex < 0) || (_currentIndex >= numLegs())) return nullptr; return legAtIndex(_currentIndex); } FlightPlan::LegRef FlightPlan::previousLeg() const { if (_currentIndex <= 0) { return nullptr; } return legAtIndex(_currentIndex - 1); } FlightPlan::LegRef FlightPlan::nextLeg() const { if ((_currentIndex < 0) || ((_currentIndex + 1) >= numLegs())) { return nullptr; } return legAtIndex(_currentIndex + 1); } FlightPlan::LegRef FlightPlan::legAtIndex(int index) const { if ((index < 0) || (index >= numLegs())) { throw sg_range_exception("index out of range", "FlightPlan::legAtIndex"); } return _legs.at(index); } int FlightPlan::findLegIndex(const Leg* l) const { for (unsigned int i=0; i<_legs.size(); ++i) { if (_legs.at(i).get() == l) { return i; } } return -1; } void FlightPlan::setDeparture(FGAirport* apt) { if (apt == _departure) { return; } lockDelegates(); _departureChanged = true; _departure = apt; _departureRunway = nullptr; clearSID(); unlockDelegates(); } void FlightPlan::setDeparture(FGRunway* rwy) { if (_departureRunway == rwy) { return; } lockDelegates(); _departureChanged = true; _departureRunway = rwy; if (rwy->airport() != _departure) { _departure = rwy->airport(); clearSID(); } unlockDelegates(); } void FlightPlan::clearDeparture() { lockDelegates(); _departureChanged = true; _departure = nullptr; _departureRunway = nullptr; clearSID(); unlockDelegates(); } void FlightPlan::setSID(SID* sid, const std::string& transition) { if ((sid == _sid) && (_sidTransition == transition)) { return; } lockDelegates(); _departureChanged = true; _sid = sid; _sidTransition = transition; unlockDelegates(); } void FlightPlan::setSID(Transition* trans) { if (!trans) { setSID(static_cast(nullptr)); return; } if (trans->parent()->type() != PROCEDURE_SID) throw sg_exception("FlightPlan::setSID: transition does not belong to a SID"); setSID(static_cast(trans->parent()), trans->ident()); } void FlightPlan::clearSID() { lockDelegates(); _departureChanged = true; _sid = nullptr; _sidTransition.clear(); unlockDelegates(); } Transition* FlightPlan::sidTransition() const { if (!_sid || _sidTransition.empty()) { return nullptr; } return _sid->findTransitionByName(_sidTransition); } void FlightPlan::setDestination(FGAirport* apt) { if (apt == _destination) { return; } lockDelegates(); _arrivalChanged = true; _destination = apt; _destinationRunway = nullptr; clearSTAR(); setApproach(static_cast(nullptr)); unlockDelegates(); } void FlightPlan::setDestination(FGRunway* rwy) { if (_destinationRunway == rwy) { return; } lockDelegates(); _arrivalChanged = true; _destinationRunway = rwy; if (rwy && (_destination != rwy->airport())) { _destination = rwy->airport(); clearSTAR(); } unlockDelegates(); } void FlightPlan::clearDestination() { lockDelegates(); _arrivalChanged = true; _destination = nullptr; _destinationRunway = nullptr; clearSTAR(); setApproach(static_cast(nullptr)); unlockDelegates(); } FGAirportRef FlightPlan::alternate() const { return _alternate; } void FlightPlan::setAlternate(FGAirportRef alt) { lockDelegates(); _alternate = alt; _arrivalChanged = true; unlockDelegates(); } void FlightPlan::setSTAR(STAR* star, const std::string& transition) { if ((_star == star) && (_starTransition == transition)) { return; } lockDelegates(); _arrivalChanged = true; _star = star; _starTransition = transition; unlockDelegates(); } 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()); } void FlightPlan::clearSTAR() { lockDelegates(); _arrivalChanged = true; _star = nullptr; _starTransition.clear(); unlockDelegates(); } void FlightPlan::setEstimatedDurationMinutes(int mins) { _estimatedDuration = mins; } void FlightPlan::computeDurationMinutes() { if ((_cruiseAirspeedMach < 0.01) && (_cruiseAirspeedKnots < 10) && (_cruiseAirspeedKph < 10)) { SG_LOG(SG_AUTOPILOT, SG_WARN, "can't compute duration, no cruise speed set"); return; } if ((_cruiseAltitudeFt < 100) && (_cruiseAltitudeM < 100) && (_cruiseFlightLevel < 10)) { SG_LOG(SG_AUTOPILOT, SG_WARN, "can't compute duration, no cruise altitude set"); return; } } Transition* FlightPlan::starTransition() const { if (!_star || _starTransition.empty()) { return nullptr; } return _star->findTransitionByName(_starTransition); } void FlightPlan::setApproach(flightgear::Approach* app, const std::string& trans) { if ((_approach == app) && (trans == _approachTransition)) { return; } lockDelegates(); _arrivalChanged = true; _approach = app; _approachTransition = trans; if (app) { // keep runway + airport in sync if (_destinationRunway != _approach->runway()) { _destinationRunway = _approach->runway(); } if (_destination != _destinationRunway->airport()) { _destination = _destinationRunway->airport(); } } unlockDelegates(); } void FlightPlan::setApproach(Transition* approachWithTrans) { if (!approachWithTrans) { setApproach((Approach*)nullptr); return; } if (!Approach::isApproach(approachWithTrans->parent()->type())) throw sg_exception("FlightPlan::setApproach: transition does not belong to an approach"); setApproach(static_cast(approachWithTrans->parent()), approachWithTrans->ident()); } Transition* FlightPlan::approachTransition() const { if (!_approach || _approachTransition.empty()) { return nullptr; } return _approach->findTransitionByName(_approachTransition); } bool FlightPlan::save(std::ostream& stream) const { try { SGPropertyNode_ptr d(new SGPropertyNode); saveToProperties(d); writeProperties(stream, d, true); return true; } catch (sg_exception& e) { SG_LOG(SG_NAVAID, SG_ALERT, "Failed to save flight-plan " << e.getMessage()); return false; } } bool FlightPlan::save(const SGPath& path) const { try { SGPropertyNode_ptr d(new SGPropertyNode); saveToProperties(d); writeProperties(path, d, true /* write-all */); return true; } catch (sg_exception& e) { SG_LOG(SG_NAVAID, SG_ALERT, "Failed to save flight-plan '" << path << "'. " << e.getMessage()); return false; } } void FlightPlan::saveToProperties(SGPropertyNode* d) const { d->setIntValue("version", 2); // general data if (_isRoute) { d->setBoolValue("is-route", true); } d->setStringValue("flight-rules", static_icaoFlightRulesCode[static_cast(_flightRules)]); d->setStringValue("flight-type", static_icaoFlightTypeCode[static_cast(_flightType)]); if (!_callsign.empty()) { d->setStringValue("callsign", _callsign); } if (!_remarks.empty()) { d->setStringValue("remarks", _remarks); } if (!_aircraftType.empty()) { d->setStringValue("aircraft-type", _aircraftType); } d->setIntValue("estimated-duration-minutes", _estimatedDuration); if (_departure) { d->setStringValue("departure/airport", _departure->ident()); if (_sid) { d->setStringValue("departure/sid", _sid->ident()); if (!_sidTransition.empty()) d->setStringValue("departure/sid_trans", _sidTransition); } 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 (!_starTransition.empty()) d->setStringValue("destination/star_trans", _starTransition); } if (_approach) { d->setStringValue("destination/approach", _approach->ident()); if (!_approachTransition.empty()) d->setStringValue("destination/approach_trans", _approachTransition); } if (_destinationRunway) { d->setStringValue("destination/runway", _destinationRunway->ident()); } } if (_alternate) { d->setStringValue("alternate", _alternate->ident()); } // cruise data if (_cruiseFlightLevel > 0) { d->setIntValue("cruise/flight-level", _cruiseFlightLevel); } else if (_cruiseAltitudeFt > 0) { d->setIntValue("cruise/altitude-ft", _cruiseAltitudeFt); } else if (_cruiseAltitudeM > 0) { d->setIntValue("cruise/altitude-m", _cruiseAltitudeM); } if (_cruiseAirspeedMach > 0.0) { d->setDoubleValue("cruise/mach", _cruiseAirspeedMach); } else if (_cruiseAirspeedKnots > 0) { d->setIntValue("cruise/knots", _cruiseAirspeedKnots); } else if (_cruiseAirspeedKph > 0) { d->setIntValue("cruise/kph", _cruiseAirspeedKph); } // route nodes SGPropertyNode* routeNode = d->getChild("route", 0, true); for (unsigned int i=0; i<_legs.size(); ++i) { auto leg = _legs.at(i); Waypt* wpt = leg->waypoint(); auto legNode = routeNode->getChild("wp", i, true); wpt->saveAsNode(legNode); leg->writeToProperties(legNode); } // of waypoint iteration } static bool anyWaypointsWithFlag(FlightPlan* plan, WayptFlag flag) { bool r = false; plan->forEachLeg([&r, flag](FlightPlan::Leg* l) { if (l->waypoint()->flags() & flag) { r = true; } }); return r; } bool FlightPlan::load(const SGPath& path) { if (!path.exists()) { SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load flight-plan '" << path << "'. The file does not exist."); return false; } SG_LOG(SG_NAVAID, SG_INFO, "going to read flight-plan from:" << path); bool Status = false; lockDelegates(); // try different file formats if (loadGpxFormat(path)) { // GPX format _arrivalChanged = true; _departureChanged = true; Status = true; } else if (loadXmlFormat(path)) { // XML property data if (!_isRoute) { expandVias(); } // we don't want to re-compute the arrival / departure after // a load, since we assume the flight-plan had it specified already // especially, the XML might have a SID/STAR embedded, which we don't // want to lose // however, we do want to run the normal delegate if no procedure was // defined. We'll use the presence of waypoints tagged to decide const bool hasArrival = anyWaypointsWithFlag(this, WPT_ARRIVAL); const bool hasDeparture = anyWaypointsWithFlag(this, WPT_DEPARTURE); _arrivalChanged = !hasArrival; _departureChanged = !hasDeparture; Status = true; } else if (loadPlainTextFormat(path)) { // simple textual list of waypoints _arrivalChanged = true; _departureChanged = true; Status = true; if (!_isRoute) { expandVias(); // plain text could in principle contain VIAs } } if (Status == true) { setIdent(path.file_base()); } _cruiseDataChanged = true; _waypointsChanged = true; _didLoadFP = true; unlockDelegates(); return Status; } bool FlightPlan::load(std::istream &stream) { SGPropertyNode_ptr routeData(new SGPropertyNode); try { readProperties(stream, routeData); } catch (sg_exception& e) { SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load flight-plan '" << e.getOrigin() << "'. " << e.getMessage()); return false; } if (!routeData.valid()) return false; bool Status = false; lockDelegates(); try { int version = routeData->getIntValue("version", 1); if (version == 2) { loadVersion2XMLRoute(routeData); Status = true; } else { throw sg_io_exception("unsupported XML route version"); } } catch (sg_exception& e) { SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load flight-plan '" << e.getOrigin() << "'. " << e.getMessage()); Status = false; } if (!_isRoute) { expandVias(); } // we don't want to re-compute the arrival / departure after // a load, since we assume the flight-plan had it specified already // especially, the XML might have a SID/STAR embedded, which we don't // want to lose // however, we do want to run the normal delegate if no procedure was // defined. We'll use the presence of waypoints tagged to decide const bool hasArrival = anyWaypointsWithFlag(this, WPT_ARRIVAL); const bool hasDeparture = anyWaypointsWithFlag(this, WPT_DEPARTURE); _arrivalChanged = !hasArrival; _departureChanged = !hasDeparture; _cruiseDataChanged = true; _waypointsChanged = true; _didLoadFP = true; unlockDelegates(); return Status; } /** XML loader for GPX file format */ class GpxXmlVisitor : public XMLVisitor { public: GpxXmlVisitor(FlightPlan* fp) : _fp(fp), _lat(-9999), _lon(-9999) {} void startElement (const char * name, const XMLAttributes &atts) override; void endElement (const char * name) override; void data (const char * s, int length) override; const WayptVec& waypoints() const { return _waypoints; } private: FlightPlan* _fp; double _lat, _lon, _elevationM; string _element; string _waypoint; WayptVec _waypoints; }; void GpxXmlVisitor::startElement(const char * name, const XMLAttributes &atts) { _element = name; if (!strcmp(name, "rtept")) { _waypoint.clear(); _lat = _lon = _elevationM = -9999; const char* slat = atts.getValue("lat"); const char* slon = atts.getValue("lon"); if (slat && slon) { _lat = atof(slat); _lon = atof(slon); } } } void GpxXmlVisitor::data(const char * s, int length) { // use "name" when given, otherwise use "cmt" (comment) as ID if ((_element == "name") || ((_waypoint.empty()) && (_element == "cmt"))) { _waypoint = std::string(s, static_cast(length)); } if (_element == "ele") { _elevationM = atof(s); } } void GpxXmlVisitor::endElement(const char * name) { _element.clear(); if (!strcmp(name, "rtept")) { if (_lon > -9990.0) { const auto geod = SGGeod::fromDeg(_lon, _lat); auto pos = FGPositioned::findClosestWithIdent(_waypoint, geod); WayptRef wp; if (pos) { // check distance const auto dist = SGGeodesy::distanceM(geod, pos->geod()); if (dist < 800.0) { wp = new NavaidWaypoint(pos, _fp); } } if (!wp) { wp = new BasicWaypt(geod, _waypoint, _fp); } if (_elevationM > -9990.0) { wp->setAltitude(_elevationM * SG_METER_TO_FEET, RESTRICT_AT); } _waypoints.push_back(wp); } } } /** Load a flightplan in GPX format */ bool FlightPlan::loadGpxFormat(const SGPath& path) { if (path.lower_extension() != "gpx") { // not a valid GPX file return false; } GpxXmlVisitor gpxVisitor(this); try { readXML(path, gpxVisitor); } catch (sg_exception& e) { // XML parsing fails => not a GPX XML file SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load flight-plan in GPX format: '" << e.getOrigin() << "'. " << e.getMessage()); return false; } if (gpxVisitor.waypoints().empty()) { SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load flight-plan in GPX format. No route found."); return false; } clearAll(); // copy in case we need to modify WayptVec wps = gpxVisitor.waypoints(); // detect airports const auto depApt = FGAirport::findByIdent(wps.front()->ident()); const auto destApt = FGAirport::findByIdent(wps.back()->ident()); if (depApt) { wps.erase(wps.begin()); setDeparture(depApt); } // for a single-element waypoint list consisting of a single airport ID, // don't crash if (destApt && !wps.empty()) { wps.pop_back(); setDestination(destApt); } insertWayptsAtIndex(wps, -1); return true; } /** Load a flightplan in FlightGear XML property format */ bool FlightPlan::loadXmlFormat(const SGPath& path) { SGPropertyNode_ptr routeData(new SGPropertyNode); try { readProperties(path, routeData); } catch (sg_exception& e) { SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load flight-plan '" << e.getOrigin() << "'. " << e.getMessage()); return false; } if (routeData.valid()) { try { int version = routeData->getIntValue("version", 1); bool ok = false; if (version == 1) { ok = loadVersion1XMLRoute(routeData); } else if (version == 2) { ok = loadVersion2XMLRoute(routeData); } else { SG_LOG(SG_NAVAID, SG_POPUP, "Unsupported flight plan version " << version << " loading " << path); } return ok; } catch (sg_exception& e) { SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load flight-plan '" << e.getOrigin() << "'. " << e.getMessage()); } } return false; } void FlightPlan::loadXMLRouteHeader(SGPropertyNode_ptr routeData) { // general info const auto rules = routeData->getStringValue("flight-rules", "V"); auto it = std::find(static_icaoFlightRulesCode.begin(), static_icaoFlightRulesCode.end(), rules); _flightRules = static_cast(std::distance(static_icaoFlightRulesCode.begin(), it)); const auto type = routeData->getStringValue("flight-type", "X"); auto it2 = std::find(static_icaoFlightTypeCode.begin(), static_icaoFlightTypeCode.end(), type); _flightType = static_cast(std::distance(static_icaoFlightTypeCode.begin(), it2)); _callsign = routeData->getStringValue("callsign"); _remarks = routeData->getStringValue("remarks"); _aircraftType = routeData->getStringValue("aircraft-type"); _estimatedDuration = routeData->getIntValue("estimated-duration-minutes"); if (routeData->hasValue("is-route")) { if (_isRoute != routeData->getBoolValue("is-route")) { // this is actually okay, we will expand any VIAs SG_LOG(SG_NAVAID, SG_INFO, "Loading XML marked with 'is-route' into FlightPlan with is-route not set"); } } // departure nodes SGPropertyNode* dep = routeData->getChild("departure"); if (dep) { string depIdent = dep->getStringValue("airport"); setDeparture((FGAirport*) fgFindAirportID(depIdent)); if (_departure) { string rwy(dep->getStringValue("runway")); if (_departure->hasRunwayWithIdent(rwy)) { setDeparture(_departure->getRunwayByIdent(rwy)); } if (dep->hasChild("sid")) { // previously, we would write a transition id for 'SID' if set, // but this is ambigous. Starting with 2020.2, we only every try // to parse this value as a SID, and look for a seperate sid_trans // value const string trans = dep->getStringValue("sid_trans"); const auto sid = dep->getStringValue("sid"); setSID(_departure->findSIDWithIdent(sid), trans); } } } // destination SGPropertyNode* dst = routeData->getChild("destination"); if (dst) { setDestination((FGAirport*) fgFindAirportID(dst->getStringValue("airport"))); if (_destination) { string rwy(dst->getStringValue("runway")); if (_destination->hasRunwayWithIdent(rwy)) { setDestination(_destination->getRunwayByIdent(rwy)); } if (dst->hasChild("star")) { // prior to 2020.2 we would attempt to treat 'star' as a // transiiton ID, but this is ambiguous. Look for a seperate value now const auto star = dst->getStringValue("star"); const string trans = dst->getStringValue("star_trans"); setSTAR(_destination->findSTARWithIdent(star), trans); } // of STAR processing if (dst->hasChild("approach")) { auto app = _destination->findApproachWithIdent(dst->getStringValue("approach")); const auto trans = dst->getStringValue("approach_trans"); setApproach(app, trans); } } } // alternate if (routeData->hasChild("alternate")) { setAlternate((FGAirport*) fgFindAirportID(routeData->getStringValue("alternate"))); } // cruise SGPropertyNode* crs = routeData->getChild("cruise"); if (crs) { if (crs->hasChild("flight-level")) { _cruiseFlightLevel = crs->getIntValue("flight-level"); } else if (crs->hasChild("altitude-ft")) { _cruiseAltitudeFt = crs->getIntValue("altitude-ft"); } else if (crs->hasChild("altitude-m")) { _cruiseAltitudeM = crs->getIntValue("altitude-m"); } if (crs->hasChild("mach")) { _cruiseAirspeedMach = crs->getDoubleValue("mach"); } else if (crs->hasChild("knots")) { _cruiseAirspeedKnots = crs->getIntValue("knots"); } else if (crs->hasChild("kph")) { _cruiseAirspeedKph = crs->getIntValue("kph"); } } // of cruise data loading } bool FlightPlan::loadVersion2XMLRoute(SGPropertyNode_ptr routeData) { if (!routeData->hasChild("route")) return false; loadXMLRouteHeader(routeData); // route nodes _legs.clear(); SGPropertyNode_ptr routeNode = routeData->getChild("route", 0); if (routeNode.valid()) { for (auto wpNode : routeNode->getChildren("wp")) { auto wp = Waypt::createFromProperties(this, wpNode); if (!wp) { continue; } LegRef l = new Leg{this, wp}; // sync leg restrictions with waypoint ones if (wp->speedRestriction() != RESTRICT_NONE) { l->setSpeed(wp->speedRestriction(), wp->speed()); } if (wp->altitudeRestriction() != RESTRICT_NONE) { l->setAltitude(wp->altitudeRestriction(), wp->altitudeFt()); } if (wpNode->hasChild("hold-count")) { l->setHoldCount(wpNode->getIntValue("hold-count")); } _legs.push_back(l); } // of route iteration } _waypointsChanged = true; return true; } bool FlightPlan::loadVersion1XMLRoute(SGPropertyNode_ptr routeData) { if (!routeData->hasChild("route")) return false; 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); LegRef l = new Leg(this, parseVersion1XMLWaypt(wpNode)); _legs.push_back(l); } // of route iteration _waypointsChanged = true; return true; } 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, this); } else { string nid = aWP->getStringValue("navid", ident.c_str()); FGPositionedRef p = FGPositioned::findClosestWithIdent(nid, lastPos); SGGeod pos; if (p) { pos = p->geod(); } else { SG_LOG(SG_GENERAL, SG_WARN, "unknown navaid in flightplan:" << nid); pos = SGGeod::fromDeg(aWP->getDoubleValue("longitude-deg"), aWP->getDoubleValue("latitude-deg")); } 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(pos, radialDeg, offsetNm * SG_NM_TO_METER, pos, az2); } w = new BasicWaypt(pos, ident, this); } double altFt = aWP->getDoubleValue("altitude-ft", -9999.9); if (altFt > -9990.0) { w->setAltitude(altFt, RESTRICT_AT); } return w; } /** Load a flightplan in FlightGear plain-text format */ bool FlightPlan::loadPlainTextFormat(const SGPath& path) { try { sg_gzifstream in(path); 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 } // prevent Sentry error 'FLIGHTGEAR-J6', when we try loading XML // data here if (simgear::strutils::starts_with(line, "waypoint()->position(); } WayptRef w = waypointFromString(line, vicinity); if (!w) { SG_LOG(SG_NAVAID, SG_ALERT, "Failed to create waypoint from '" << line << "' in " << path); _legs.clear(); return false; } _legs.push_back(LegRef{new Leg(this, w)}); } // of line iteration } catch (sg_exception& e) { SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load route from: '" << path << "'. " << 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; } SGGeod FlightPlan::vicinityForInsertIndex(int aIndex) const { if (aIndex < 0) { // appending, not inserting const int n = numLegs(); if (n > 0) { // if we have at least one existing leg, use its position // for the search vicinity return pointAlongRoute(n - 1, 0.0); } return SGGeod::invalid(); } // if we're somewhere in the middle of the route compute a search // vicinity halfway between the previous waypoint and the one we are // inserting at, i.e the middle of the leg. // if we're at the beginning, just use zero of course. const double normOffset = (aIndex > 0) ? -0.5 : 0.0; return pointAlongRouteNorm(aIndex, normOffset); } WayptRef FlightPlan::waypointFromString(const string& tgt, const SGGeod& vicinity) { SGGeod basePosition = vicinity; if (!vicinity.isValid()) { // compute basePosition using some heuristics if (_legs.empty()) { // route is empty, use departure position / aircraft position basePosition = _departure ? _departure->geod() : globals->get_aircraft_position(); } else { basePosition = _legs.back()->waypoint()->position(); } } return Waypt::createFromString(this, tgt, basePosition); } bool FlightPlan::expandVias() { // must be called with the delegats locked, so that // waypointsChanged can be set on finish assert(_delegateLock > 0); bool didChangeAny = false; for (unsigned int i=1; i < _legs.size(); ) { if (_legs[i]->waypoint()->type() == "via") { WayptRef preceeding = _legs[i - 1]->waypoint(); Via* via = static_cast(_legs[i]->waypoint()); WayptVec wps = via->expandToWaypoints(preceeding); // delete the VIA leg auto it = _legs.begin() + i; LegRef l = *it; _legs.erase(it); // create new legs and insert it = _legs.begin() + i; LegVec newLegs; for (WayptRef wp : wps) { newLegs.push_back(LegRef{new Leg(this, wp)}); } didChangeAny = true; _legs.insert(it, newLegs.begin(), newLegs.end()); } else { ++i; // normal case, no expansion } } return didChangeAny; } void FlightPlan::activate() { if (_isRoute) { // no allowed, clone and make the non-route FP active SG_LOG(SG_NAVAID, SG_DEV_ALERT, "tried to activate an is-route FlightPlan"); return; } FGRouteMgr* routeManager = globals->get_subsystem(); if (routeManager) { if (routeManager->flightPlan() != this) { SG_LOG(SG_NAVAID, SG_DEBUG, "setting new flight-plan on route-manager"); routeManager->setFlightPlan(this); } } lockDelegates(); _currentIndex = 0; _currentWaypointChanged = true; _waypointsChanged = expandVias(); for (auto d : _delegates) { d->activated(); } unlockDelegates(); } FlightPlan::Leg::Leg(FlightPlan* owner, WayptRef wpt) : _parent(owner), _waypt(wpt) { if (!wpt.valid()) { throw sg_exception("can't create FlightPlan::Leg without underlying waypoint"); } } 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 { if ((index() + 1) >= _parent->_legs.size()) return nullptr; 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; } bool FlightPlan::Leg::convertWaypointToHold() { const auto wty = _waypt->type(); if (wty == "hold") { return true; } if ((wty != "basic") && (wty != "navaid")) { SG_LOG(SG_INSTR, SG_WARN, "convertWaypointToHold: cannot convert waypt " << index() << " " << _waypt->ident() << " to a hold"); return false; } auto hold = new Hold(_waypt->position(), _waypt->ident(), const_cast(_parent)); // default to a 1 minute hold with the radial being our arrival radial hold->setHoldTime(60.0); hold->setHoldRadial(_courseDeg); _waypt = hold; // we drop our reference to the old waypoint markWaypointDirty(); return true; } bool FlightPlan::Leg::setHoldCount(int count) { if (count == 0) { _holdCount = count; return true; } if (!convertWaypointToHold()) { return false; } _holdCount = count; markWaypointDirty(); return true; } void FlightPlan::Leg::markWaypointDirty() { auto fp = owner(); fp->lockDelegates(); fp->_waypointsChanged = true; fp->unlockDelegates(); } int FlightPlan::Leg::holdCount() const { return _holdCount; } void FlightPlan::Leg::writeToProperties(SGPropertyNode* aProp) const { if (_speedRestrict != RESTRICT_NONE) { aProp->setStringValue("speed-restrict", restrictionToString(_speedRestrict)); if (_speedRestrict == SPEED_RESTRICT_MACH) { aProp->setDoubleValue("speed", speedMach()); } else { aProp->setDoubleValue("speed", _speed); } } if (_altRestrict != RESTRICT_NONE) { aProp->setStringValue("alt-restrict", restrictionToString(_altRestrict)); aProp->setDoubleValue("altitude-ft", _altitudeFt); } if (_holdCount > 0) { aProp->setDoubleValue("hold-count", _holdCount); } } void FlightPlan::rebuildLegData() { _totalDistance = 0.0; double totalDistanceIncludingMissed = 0.0; RoutePath path(this); for (unsigned int l=0; l<_legs.size(); ++l) { _legs[l]->_courseDeg = path.trackForIndex(l); _legs[l]->_pathDistance = path.distanceForIndex(l) * SG_METER_TO_NM; totalDistanceIncludingMissed += _legs[l]->_pathDistance; // distance along path includes our own leg distance _legs[l]->_distanceAlongPath = totalDistanceIncludingMissed; // omit missed-approach waypoints from total distance calculation if (!_legs[l]->waypoint()->flag(WPT_MISS)) { _totalDistance += _legs[l]->_pathDistance; } } // of legs iteration } SGGeod FlightPlan::pointAlongRoute(int aIndex, double aOffsetNm) const { RoutePath rp(this); return rp.positionForDistanceFrom(aIndex, aOffsetNm * SG_NM_TO_METER); } SGGeod FlightPlan::pointAlongRouteNorm(int aIndex, double aOffsetNorm) const { RoutePath rp(this); if (fabs(aOffsetNorm) > 1.0) { SG_LOG(SG_AUTOPILOT, SG_ALERT, "FlightPlan::pointAlongRouteNorm: called with invalid arg:" << aOffsetNorm); return rp.positionForIndex(aIndex); } const bool forwards = (aOffsetNorm >= 0.0); double d = 0.0; if (forwards) { d = rp.distanceForIndex(aIndex + 1); } else { d = rp.distanceForIndex(aIndex); } // in degenerate cases, just use basic position of index if (d <= 0.0) { return rp.positionForIndex(aIndex); } return rp.positionForDistanceFrom(aIndex, d * aOffsetNorm); } void FlightPlan::lockDelegates() { if (_delegateLock == 0) { assert(!_departureChanged && !_arrivalChanged && !_waypointsChanged && !_currentWaypointChanged); } ++_delegateLock; if (_delegateLock > 10) { SG_LOG(SG_GENERAL, SG_ALERT, "hmmm"); } } void FlightPlan::unlockDelegates() { assert(_delegateLock > 0); if (_delegateLock > 1) { --_delegateLock; return; } if (_didLoadFP) { _didLoadFP = false; for (auto d : _delegates) { d->loaded(); } } if (_departureChanged) { _departureChanged = false; for (auto d : _delegates) { d->departureChanged(); } } if (_arrivalChanged) { _arrivalChanged = false; for (auto d : _delegates) { d->arrivalChanged(); } } if (_cruiseDataChanged) { _cruiseDataChanged = false; for (auto d : _delegates) { d->cruiseChanged(); } } if (_waypointsChanged) { _waypointsChanged = false; rebuildLegData(); for (auto d : _delegates) { d->waypointsChanged(); } } if (_currentWaypointChanged) { _currentWaypointChanged = false; for (auto d : _delegates) { d->currentWaypointChanged(); } } --_delegateLock; } void FlightPlan::registerDelegateFactory(DelegateFactoryRef df) { auto it = std::find(static_delegateFactories.begin(), static_delegateFactories.end(), df); if (it != static_delegateFactories.end()) { throw sg_exception("duplicate delegate factory registration"); } static_delegateFactories.push_back(df); } void FlightPlan::unregisterDelegateFactory(DelegateFactoryRef df) { auto it = std::find(static_delegateFactories.begin(), static_delegateFactories.end(), df); if (it == static_delegateFactories.end()) { return; } static_delegateFactories.erase(it); } void FlightPlan::addDelegate(Delegate* d) { assert(d); #ifndef NDEBUG auto it = std::find(_delegates.begin(), _delegates.end(), d); #endif assert(it == _delegates.end()); _delegates.push_back(d); } void FlightPlan::removeDelegate(Delegate* d) { assert(d); auto it = std::find(_delegates.begin(), _delegates.end(), d); assert(it != _delegates.end()); _delegates.erase(it); } void FlightPlan::notifyCleared() { for (auto d : _delegates) { d->cleared(); } } FlightPlan::Delegate::Delegate() { } FlightPlan::Delegate::~Delegate() { } void FlightPlan::setFollowLegTrackToFixes(bool tf) { _followLegTrackToFix = tf; } bool FlightPlan::followLegTrackToFixes() const { return _followLegTrackToFix; } void FlightPlan::setMaxFlyByTurnAngle(double deg) { _maxFlyByTurnAngle = deg; } double FlightPlan::maxFlyByTurnAngle() const { return _maxFlyByTurnAngle; } std::string FlightPlan::icaoAircraftCategory() const { std::string r; r.push_back(_aircraftCategory); return r; } void FlightPlan::setIcaoAircraftCategory(const std::string& cat) { if (cat.empty()) { throw sg_range_exception("Invalid ICAO aircraft category:", cat); } if ((cat[0] < ICAO_AIRCRAFT_CATEGORY_A) || (cat[0] > ICAO_AIRCRAFT_CATEGORY_E)) { throw sg_range_exception("Invalid ICAO aircraft category:", cat); } _aircraftCategory = cat[0]; } void FlightPlan::setIcaoAircraftType(const string &ty) { _aircraftType = ty; } bool FlightPlan::parseICAOLatLon(const std::string& s, SGGeod& p) { if (s.size() == 7) { double latDegrees = std::stoi(s); double lonDegrees = std::stoi(s.substr(3, 3)); if (s[2] == 'S') latDegrees = -latDegrees; if (s[6] == 'W') lonDegrees = -lonDegrees; p = SGGeod::fromDeg(lonDegrees, latDegrees); return true; } else if (s.size() == 11) { // problem here is we have minutes, not decimal degrees double latDegrees = std::stoi(s.substr(0, 2)); double latMinutes = std::stoi(s.substr(2, 2)); latDegrees += (latMinutes / 60.0); double lonDegrees = std::stoi(s.substr(5, 3)); double lonMinutes = std::stoi(s.substr(8, 2)); lonDegrees += (lonMinutes / 60.0); if (s[4] == 'S') latDegrees = -latDegrees; if (s[10] == 'W') lonDegrees = -lonDegrees; p = SGGeod::fromDeg(lonDegrees, latDegrees); return true; } return false; } bool FlightPlan::parseICAORouteString(const std::string& routeData) { auto tokens = simgear::strutils::split(routeData); if (tokens.empty()) return false; std::string tk; std::string nextToken; FGAirportRef firstICAO = FGAirport::findByIdent(tokens.front()); unsigned int i = 0; if (firstICAO) { // route string starts with an airport, let's see if it matches // our departure airport if (!_departure) { setDeparture(firstICAO); } else if (_departure != firstICAO) { SG_LOG(SG_AUTOPILOT, SG_WARN, "ICAO route begins with an airport which is not the departure airport:" << tokens.front()); return false; } ++i; // either way, skip the ICAO token now } WayptVec enroute; SGGeod currentPos; if (_departure) currentPos = _departure->geod(); for (; i < tokens.size(); ++i) { tk = tokens.at(i); // update current position for next search if (!enroute.empty()) { currentPos = enroute.back()->position(); } if (isdigit(tk.front())) { // might be a lat lon (but some airway idents also start with a digit...) SGGeod geod; bool ok = parseICAOLatLon(tk, geod); if (ok) { enroute.push_back(new BasicWaypt(geod, tk, this)); continue; } } nextToken = (i < (tokens.size() - 1)) ? tokens.at(i+1) : std::string(); if (tk == "DCT") { if (nextToken.empty()) { SG_LOG(SG_AUTOPILOT, SG_WARN, "ICAO route DIRECT segment missing waypoint"); return false; } FGPositionedRef wpt = FGPositioned::findClosestWithIdent(nextToken, currentPos); if (!wpt) { SG_LOG(SG_AUTOPILOT, SG_WARN, "ICAO route waypoint not found:" << nextToken); return false; } enroute.push_back(new NavaidWaypoint(wpt, this)); ++i; } else if (tk == "STAR") { // look for a STAR based on the preceeding transition point auto starTrans = _destination->selectSTARByEnrouteTransition(enroute.back()->source()); if (!starTrans) { SG_LOG(SG_AUTOPILOT, SG_WARN, "ICAO route couldn't find STAR transitioning from " << enroute.back()->source()->ident()); } else { setSTAR(starTrans); } } else if (tk == "SID") { // select a SID based on the next point FGPositionedRef wpt = FGPositioned::findClosestWithIdent(nextToken, currentPos); auto sidTrans = _departure->selectSIDByEnrouteTransition(wpt); if (!sidTrans) { SG_LOG(SG_AUTOPILOT, SG_WARN, "ICAO route couldn't find SID transitioning to " << nextToken); } else { setSID(sidTrans); } ++i; } else { if (nextToken.empty()) { SG_LOG(SG_AUTOPILOT, SG_WARN, "ICAO route airway segment missing transition:" << tk); return false; } auto nav = Airway::highLevel()->findNodeByIdent(nextToken, currentPos); if (!nav) nav = Airway::lowLevel()->findNodeByIdent(nextToken, currentPos); if (!nav) { SG_LOG(SG_AUTOPILOT, SG_WARN, "ICAO route waypoint not found:" << nextToken); return false; } WayptRef toNav = new NavaidWaypoint(nav, nullptr); // temp waypoint for lookup WayptRef previous; if (enroute.empty()) { if (_sid) { if (!_sidTransition.empty()) { previous = _sid->findTransitionByName(_sidTransition)->enroute(); } else { // final wayypoint of common section previous = _sid->common().back(); } } else { SG_LOG(SG_AUTOPILOT, SG_WARN, "initial airway needs anchor point from SID:" << tk); return false; } } else { previous = enroute.back(); } AirwayRef way = Airway::findByIdentAndVia(tk, enroute.back(), toNav); if (way) { enroute.push_back(new Via(this, way, nav)); ++i; } else { SG_LOG(SG_AUTOPILOT, SG_WARN, "ICAO route unknown airway:" << tk); return false; } } } // of token iteration lockDelegates(); _waypointsChanged = true; SG_LOG(SG_AUTOPILOT, SG_INFO, "adding waypoints from string"); // rebuild legs from waypoints we created for (auto l : _legs) { delete l; } _legs.clear(); insertWayptsAtIndex(enroute, 0); unlockDelegates(); SG_LOG(SG_AUTOPILOT, SG_INFO, "legs now:" << numLegs()); return true; } std::string FlightPlan::asICAORouteString() const { std::string result; if (!_sidTransition.empty()) result += _sidTransition + " "; for (auto l : _legs) { const auto wpt = l->waypoint(); AirwayRef nextLegAirway; if (l->nextLeg() && l->nextLeg()->waypoint()->flag(WPT_VIA)) { nextLegAirway = static_cast(l->nextLeg()->waypoint()->owner()); } if (wpt->flag(WPT_GENERATED)) { if (wpt->flag(WPT_VIA)) { AirwayRef awy = static_cast(wpt->owner()); if (awy == nextLegAirway) { // skipepd entirely, next leg will output the airway continue; } result += awy->ident() + " "; } } else if (wpt->type() == "navaid") { // technically we need DCT unless both ends of the leg are // defined geographically result += "DCT "; } result += wpt->icaoDescription() + " "; } if (!_starTransition.empty()) result += _starTransition; return result; } void FlightPlan::setFlightRules(ICAOFlightRules rules) { _flightRules = rules; } ICAOFlightRules FlightPlan::flightRules() const { return _flightRules; } void FlightPlan::setCallsign(const std::string& callsign) { _callsign = callsign; } void FlightPlan::setRemarks(const std::string& remarks) { _remarks = remarks; } void FlightPlan::setFlightType(ICAOFlightType type) { _flightType = type; } ICAOFlightType FlightPlan::flightType() const { return _flightType; } void FlightPlan::setCruiseSpeedKnots(int kts) { lockDelegates(); _cruiseDataChanged = true; _cruiseAirspeedKnots = kts; _cruiseAirspeedMach = 0.0; _cruiseAirspeedKph = 0; unlockDelegates(); } int FlightPlan::cruiseSpeedKnots() const { return _cruiseAirspeedKnots; } void FlightPlan::setCruiseSpeedMach(double mach) { lockDelegates(); _cruiseDataChanged = true; _cruiseAirspeedKnots = 0; _cruiseAirspeedMach = mach; _cruiseAirspeedKph = 0; unlockDelegates(); } double FlightPlan::cruiseSpeedMach() const { return _cruiseAirspeedMach; } void FlightPlan::setCruiseSpeedKPH(int kph) { lockDelegates(); _cruiseDataChanged = true; _cruiseAirspeedKnots = 0; _cruiseAirspeedMach = 0.0; _cruiseAirspeedKph = kph; unlockDelegates(); } int FlightPlan::cruiseSpeedKPH() const { return _cruiseAirspeedKph; } void FlightPlan::setCruiseFlightLevel(int flightLevel) { lockDelegates(); _cruiseDataChanged = true; _cruiseAltitudeFt = 0; _cruiseAltitudeM = 0; _cruiseFlightLevel = flightLevel; unlockDelegates(); } int FlightPlan::cruiseFlightLevel() const { return _cruiseFlightLevel; } void FlightPlan::setCruiseAltitudeFt(int altFt) { lockDelegates(); _cruiseDataChanged = true; _cruiseAltitudeFt = altFt; _cruiseAltitudeM = 0; _cruiseFlightLevel = 0; unlockDelegates(); } int FlightPlan::cruiseAltitudeFt() const { return _cruiseAltitudeFt; } void FlightPlan::setCruiseAltitudeM(int altM) { lockDelegates(); _cruiseDataChanged = true; _cruiseAltitudeFt = 0; _cruiseAltitudeM = altM; _cruiseFlightLevel = 0; unlockDelegates(); } int FlightPlan::cruiseAltitudeM() const { return _cruiseAltitudeM; } void FlightPlan::forEachLeg(const LegVisitor& lv) { std::for_each(_legs.begin(), _legs.end(), lv); } int FlightPlan::indexOfFirstNonDepartureWaypoint() const { const auto numLegs = _legs.size(); for (size_t i = 0; i < numLegs; ++i) { if (!(_legs.at(i)->waypoint()->flags() & WPT_DEPARTURE)) return static_cast(i); } // all waypoints are marked as departure return -1; } int FlightPlan::indexOfFirstArrivalWaypoint() const { const auto numLegs = _legs.size(); for (size_t i = 0; i < numLegs; ++i) { if (_legs.at(i)->waypoint()->flags() & WPT_ARRIVAL) return static_cast(i); } // no waypoints are marked as arrival return -1; } int FlightPlan::indexOfFirstApproachWaypoint() const { const auto numLegs = _legs.size(); for (size_t i = 0; i < numLegs; ++i) { if (_legs.at(i)->waypoint()->flags() & WPT_APPROACH) return static_cast(i); } // no waypoints are marked as arrival return -1; } int FlightPlan::indexOfDestinationRunwayWaypoint() const { if (!_destinationRunway) return -1; // work backwards in case the departure and destination match // this way we'll find the one we want for (int i = numLegs() - 1; i >= 0; i--) { if (_legs.at(i)->waypoint()->source() == _destinationRunway) { return i; } } return -1; } void FlightPlan::DelegateFactory::destroyFlightPlanDelegate(FlightPlan* fp, Delegate* d) { // mimic old behaviour before destroyFlightPlanDelegate was added SG_UNUSED(fp); delete d; } } // of namespace flightgear