33#include <simgear/structure/exception.hxx>
34#include <simgear/misc/sg_path.hxx>
35#include <simgear/magvar/magvar.hxx>
36#include <simgear/timing/sg_time.hxx>
37#include <simgear/io/iostreams/sgstream.hxx>
38#include <simgear/misc/strutils.hxx>
39#include <simgear/props/props_io.hxx>
40#include <simgear/xml/easyxml.hxx>
85FlightPlan::FlightPlan(
bool isRoute) :
88 _followLegTrackToFix(true),
90 _departureRunway(nullptr),
91 _destinationRunway(nullptr),
97 _departureChanged = _arrivalChanged = _waypointsChanged = _currentWaypointChanged =
false;
98 _cruiseDataChanged =
false;
101 Delegate* d = factory->createFlightPlanDelegate(
this);
103 d->_factory = factory;
111 return new FlightPlan(
false);
116 return new FlightPlan(
true);
122 for (
auto d : _delegates) {
124 auto f = d->_factory;
125 f->destroyFlightPlanDelegate(
this, d);
135 FlightPlanRef c =
new FlightPlan(convertIntoFlightPlan ?
false : _isRoute);
136 c->_ident = newIdent.empty() ? _ident : newIdent;
140 c->setDeparture(_departure);
141 c->setDeparture(_departureRunway);
144 c->setApproach(_approach, _approachTransition);
145 }
else if (_destinationRunway) {
146 c->setDestination(_destinationRunway);
147 }
else if (_destination) {
148 c->setDestination(_destination);
151 c->setSTAR(_star, _starTransition);
152 c->setSID(_sid, _sidTransition);
155 c->_arrivalChanged =
false;
156 c->_departureChanged =
false;
159 if (_cruiseFlightLevel > 0) {
160 c->setCruiseFlightLevel(_cruiseFlightLevel);
161 }
else if (_cruiseAltitudeFt > 0) {
162 c->setCruiseAltitudeFt(_cruiseAltitudeFt);
163 }
else if (_cruiseAltitudeM > 0) {
164 c->setCruiseAltitudeM(_cruiseAltitudeM);
167 if (_cruiseAirspeedMach > 0) {
168 c->setCruiseSpeedMach(_cruiseAirspeedMach);
169 }
else if (_cruiseAirspeedKnots > 0) {
170 c->setCruiseSpeedKnots(_cruiseAirspeedKnots);
171 }
else if (_cruiseAirspeedKph > 0) {
172 c->setCruiseSpeedKPH(_cruiseAirspeedKph);
175 c->_didLoadFP =
true;
178 c->_waypointsChanged =
true;
179 for (
int l=0; l <
numLegs(); ++l) {
180 c->_legs.push_back(_legs[l]->cloneFor(c));
184 c->unlockDelegates();
208 if ((aIndex == -1) || (aIndex > (
int) _legs.size())) {
209 index = _legs.size();
219 result.reserve(wps.size());
221 for (
auto wp : wps) {
222 if (wp->type() ==
"via") {
223 Via* via =
static_cast<Via*
>(wp.get());
225 result.insert(result.end(), viaPoints.begin(), viaPoints.end());
228 result.push_back(wp);
242 if ((aIndex == -1) || (aIndex > (
int) _legs.size())) {
243 index = _legs.size();
248 if (!_isRoute && (index > 0)) {
249 const auto pre = _legs.at(index - 1)->waypoint();
251 }
else if (index == 0) {
252 if (wps.front()->type() ==
"via") {
253 SG_LOG(SG_AUTOPILOT, SG_DEV_ALERT,
"Inserting a VIA at leg 0 of flight-plan, VIA cannot be expanded");
257 auto it = _legs.begin() + index;
258 int endIndex = index + toInsertWps.size() - 1;
259 if (_currentIndex >= endIndex) {
260 _currentIndex += toInsertWps.size();
265 newLegs.push_back(
LegRef{
new Leg(
this, wp)});
269 _waypointsChanged =
true;
270 _legs.insert(it, newLegs.begin(), newLegs.end());
278 index = _legs.size() + index;
281 if ((index < 0) || (index >=
numLegs())) {
282 SG_LOG(SG_NAVAID, SG_WARN,
"removeAtIndex with invalid index:" << aIndex);
287 _waypointsChanged =
true;
289 auto it = _legs.begin() + index;
292 l->_parent =
nullptr;
294 if (_currentIndex == index) {
296 _currentWaypointChanged =
true;
297 }
else if (_currentIndex > index) {
308 _departureRunway =
nullptr;
309 _destinationRunway =
nullptr;
310 _destination.clear();
312 _sidTransition.clear();
314 _starTransition.clear();
316 _approachTransition.clear();
319 _cruiseAirspeedMach = 0.0;
320 _cruiseAirspeedKnots = 0;
321 _cruiseAirspeedKph = 0;
322 _cruiseFlightLevel = 0;
323 _cruiseAltitudeFt = 0;
324 _cruiseAltitudeM = 0;
334 if (_legs.empty() && (_currentIndex < 0)) {
339 _waypointsChanged =
true;
340 _currentWaypointChanged =
true;
341 _arrivalChanged =
true;
342 _departureChanged =
true;
343 _cruiseDataChanged =
true;
356 for (
int i=0;
i<_currentIndex; ++
i) {
357 const auto& l = _legs.at(
i);
358 if (l->waypoint()->flag(flag)) {
364 bool currentIsBeingCleared =
false;
370 _currentIndex -= count;
376 if (currentIsBeingCleared) {
377 SG_LOG(SG_GENERAL, SG_INFO,
"FlightPlan::clearWayptsWithFlag: currentIsBeingCleared:" << currentIsBeingCleared);
383 auto it = std::remove_if(_legs.begin(), _legs.end(),
384 [flag, &numDeleted](
const LegRef& leg)
386 if (leg->waypoint()->flag(flag)) {
392 if (it == _legs.end()) {
397 _waypointsChanged =
true;
398 if ((count > 0) || currentIsBeingCleared) {
399 _currentWaypointChanged =
true;
402 _legs.erase(it, _legs.end());
421 return (_currentIndex >= 0);
426 if ((index < -1) || (index >=
numLegs())) {
427 throw sg_range_exception(
"invalid leg index",
"FlightPlan::setCurrentIndex");
430 if (index == _currentIndex) {
435 _currentIndex = index;
436 _currentWaypointChanged =
true;
443 for (
auto d : _delegates) {
452 throw sg_exception(
"Called finish on FlightPlan marked isRoute");
455 if (_currentIndex == -1) {
461 _currentWaypointChanged =
true;
463 for (
auto d : _delegates) {
464 d->endOfFlightPlan();
473 if (_legs[
i]->waypoint()->matches(aPos)) {
484 if (_legs[
i]->waypoint()->matches(aPos)) {
494 if ((_currentIndex < 0) || (_currentIndex >=
numLegs()))
501 if (_currentIndex <= 0) {
510 if ((_currentIndex < 0) || ((_currentIndex + 1) >=
numLegs())) {
519 if ((index < 0) || (index >=
numLegs())) {
520 throw sg_range_exception(
"index out of range",
"FlightPlan::legAtIndex");
523 return _legs.at(index);
526int FlightPlan::findLegIndex(
const Leg* l)
const
528 for (
unsigned int i=0;
i<_legs.size(); ++
i) {
529 if (_legs.at(
i).get() == l) {
539 if (apt == _departure) {
544 _departureChanged =
true;
546 _departureRunway =
nullptr;
553 if (_departureRunway == rwy) {
558 _departureChanged =
true;
560 _departureRunway = rwy;
561 if (rwy->
airport() != _departure) {
571 _departureChanged =
true;
572 _departure =
nullptr;
573 _departureRunway =
nullptr;
580 if ((
sid == _sid) && (_sidTransition == transition)) {
585 _departureChanged =
true;
587 _sidTransition = transition;
599 throw sg_exception(
"FlightPlan::setSID: transition does not belong to a SID");
607 _departureChanged =
true;
609 _sidTransition.clear();
615 if (!_sid || _sidTransition.empty()) {
619 return _sid->findTransitionByName(_sidTransition);
624 if (apt == _destination) {
629 _arrivalChanged =
true;
631 _destinationRunway =
nullptr;
639 if (_destinationRunway == rwy) {
644 _arrivalChanged =
true;
645 _destinationRunway = rwy;
646 if (rwy && (_destination != rwy->
airport())) {
657 _arrivalChanged =
true;
658 _destination =
nullptr;
659 _destinationRunway =
nullptr;
674 _arrivalChanged =
true;
680 if ((_star ==
star) && (_starTransition == transition)) {
685 _arrivalChanged =
true;
687 _starTransition = transition;
699 throw sg_exception(
"FlightPlan::setSTAR: transition does not belong to a STAR");
708 _arrivalChanged =
true;
710 _starTransition.clear();
716 _estimatedDuration = mins;
721 if ((_cruiseAirspeedMach < 0.01) && (_cruiseAirspeedKnots < 10) && (_cruiseAirspeedKph < 10)) {
722 SG_LOG(SG_AUTOPILOT, SG_WARN,
"can't compute duration, no cruise speed set");
726 if ((_cruiseAltitudeFt < 100) && (_cruiseAltitudeM < 100) && (_cruiseFlightLevel < 10)) {
727 SG_LOG(SG_AUTOPILOT, SG_WARN,
"can't compute duration, no cruise altitude set");
736 if (!_star || _starTransition.empty()) {
740 return _star->findTransitionByName(_starTransition);
745 if ((_approach == app) && (trans == _approachTransition)) {
750 _arrivalChanged =
true;
752 _approachTransition = trans;
755 if (_destinationRunway != _approach->runway()) {
756 _destinationRunway = _approach->runway();
759 if (_destination != _destinationRunway->airport()) {
760 _destination = _destinationRunway->airport();
768 if (!approachWithTrans) {
774 throw sg_exception(
"FlightPlan::setApproach: transition does not belong to an approach");
777 approachWithTrans->
ident());
782 if (!_approach || _approachTransition.empty()) {
786 return _approach->findTransitionByName(_approachTransition);
792 SGPropertyNode_ptr d(
new SGPropertyNode);
794 writeProperties(stream, d,
true);
796 }
catch (sg_exception& e) {
797 SG_LOG(SG_NAVAID, SG_ALERT,
"Failed to save flight-plan " << e.getMessage());
805 SGPropertyNode_ptr d(
new SGPropertyNode);
807 writeProperties(path, d,
true );
809 }
catch (sg_exception& e) {
810 SG_LOG(SG_NAVAID, SG_ALERT,
"Failed to save flight-plan '" << path <<
"'. " << e.getMessage());
815void FlightPlan::saveToProperties(SGPropertyNode* d)
const
817 d->setIntValue(
"version", 2);
821 d->setBoolValue(
"is-route",
true);
824 d->setStringValue(
"flight-rules", static_icaoFlightRulesCode[
static_cast<int>(_flightRules)]);
825 d->setStringValue(
"flight-type", static_icaoFlightTypeCode[
static_cast<int>(_flightType)]);
826 if (!_callsign.empty()) {
827 d->setStringValue(
"callsign", _callsign);
829 if (!_remarks.empty()) {
830 d->setStringValue(
"remarks", _remarks);
832 if (!_aircraftType.empty()) {
833 d->setStringValue(
"aircraft-type", _aircraftType);
835 d->setIntValue(
"estimated-duration-minutes", _estimatedDuration);
838 d->setStringValue(
"departure/airport", _departure->ident());
840 d->setStringValue(
"departure/sid", _sid->ident());
841 if (!_sidTransition.empty())
842 d->setStringValue(
"departure/sid_trans", _sidTransition);
845 if (_departureRunway) {
846 d->setStringValue(
"departure/runway", _departureRunway->ident());
851 d->setStringValue(
"destination/airport", _destination->ident());
853 d->setStringValue(
"destination/star", _star->ident());
854 if (!_starTransition.empty())
855 d->setStringValue(
"destination/star_trans", _starTransition);
859 d->setStringValue(
"destination/approach", _approach->ident());
860 if (!_approachTransition.empty())
861 d->setStringValue(
"destination/approach_trans", _approachTransition);
864 if (_destinationRunway) {
865 d->setStringValue(
"destination/runway", _destinationRunway->ident());
870 d->setStringValue(
"alternate", _alternate->ident());
874 if (_cruiseFlightLevel > 0) {
875 d->setIntValue(
"cruise/flight-level", _cruiseFlightLevel);
876 }
else if (_cruiseAltitudeFt > 0) {
877 d->setIntValue(
"cruise/altitude-ft", _cruiseAltitudeFt);
878 }
else if (_cruiseAltitudeM > 0) {
879 d->setIntValue(
"cruise/altitude-m", _cruiseAltitudeM);
882 if (_cruiseAirspeedMach > 0.0) {
883 d->setDoubleValue(
"cruise/mach", _cruiseAirspeedMach);
884 }
else if (_cruiseAirspeedKnots > 0) {
885 d->setIntValue(
"cruise/knots", _cruiseAirspeedKnots);
886 }
else if (_cruiseAirspeedKph > 0) {
887 d->setIntValue(
"cruise/kph", _cruiseAirspeedKph);
891 SGPropertyNode* routeNode = d->getChild(
"route", 0,
true);
892 for (
unsigned int i=0;
i<_legs.size(); ++
i) {
893 auto leg = _legs.at(
i);
894 Waypt* wpt = leg->waypoint();
895 auto legNode = routeNode->getChild(
"wp",
i,
true);
896 wpt->saveAsNode(legNode);
897 leg->writeToProperties(legNode);
915 if (!path.exists()) {
916 SG_LOG(SG_NAVAID, SG_ALERT,
"Failed to load flight-plan '" << path
917 <<
"'. The file does not exist.");
921 SG_LOG(SG_NAVAID, SG_INFO,
"going to read flight-plan from:" << path);
927 if (loadGpxFormat(path)) {
928 _arrivalChanged =
true;
929 _departureChanged =
true;
931 }
else if (loadXmlFormat(path)) {
945 _arrivalChanged = !hasArrival;
946 _departureChanged = !hasDeparture;
948 }
else if (loadPlainTextFormat(path)) {
949 _arrivalChanged =
true;
950 _departureChanged =
true;
958 if (Status ==
true) {
962 _cruiseDataChanged =
true;
963 _waypointsChanged =
true;
973 SGPropertyNode_ptr routeData(
new SGPropertyNode);
975 readProperties(stream, routeData);
976 }
catch (sg_exception& e) {
977 SG_LOG(SG_NAVAID, SG_ALERT,
"Failed to load flight-plan '" << e.getOrigin()
978 <<
"'. " << e.getMessage());
982 if (!routeData.valid())
988 int version = routeData->getIntValue(
"version", 1);
990 loadVersion2XMLRoute(routeData);
993 throw sg_io_exception(
"unsupported XML route version");
995 }
catch (sg_exception& e) {
996 SG_LOG(SG_NAVAID, SG_ALERT,
"Failed to load flight-plan '" << e.getOrigin()
997 <<
"'. " << e.getMessage());
1014 _arrivalChanged = !hasArrival;
1015 _departureChanged = !hasDeparture;
1017 _cruiseDataChanged =
true;
1018 _waypointsChanged =
true;
1032 void startElement (
const char *
name,
const XMLAttributes &atts)
override;
1033 void endElement (
const char *
name)
override;
1034 void data (
const char * s,
int length)
override;
1039 double _lat, _lon, _elevationM;
1048 if (!strcmp(
name,
"rtept")) {
1050 _lat = _lon = _elevationM = -9999;
1051 const char* slat = atts.getValue(
"lat");
1052 const char* slon = atts.getValue(
"lon");
1063 if ((_element ==
"name") ||
1064 ((_waypoint.empty()) && (_element ==
"cmt")))
1066 _waypoint = std::string(s,
static_cast<size_t>(length));
1069 if (_element ==
"ele") {
1070 _elevationM =
atof(s);
1077 if (!strcmp(
name,
"rtept")) {
1078 if (_lon > -9990.0) {
1079 const auto geod = SGGeod::fromDeg(_lon, _lat);
1085 const auto dist = SGGeodesy::distanceM(geod, pos->geod());
1095 if (_elevationM > -9990.0) {
1096 wp->setAltitude(_elevationM * SG_METER_TO_FEET,
RESTRICT_AT);
1098 _waypoints.push_back(wp);
1104bool FlightPlan::loadGpxFormat(
const SGPath& path)
1106 if (path.lower_extension() !=
"gpx") {
1111 GpxXmlVisitor gpxVisitor(
this);
1113 readXML(path, gpxVisitor);
1114 }
catch (sg_exception& e) {
1116 SG_LOG(SG_NAVAID, SG_ALERT,
"Failed to load flight-plan in GPX format: '" << e.getOrigin()
1117 <<
"'. " << e.getMessage());
1121 if (gpxVisitor.waypoints().empty()) {
1122 SG_LOG(SG_NAVAID, SG_ALERT,
"Failed to load flight-plan in GPX format. No route found.");
1129 WayptVec wps = gpxVisitor.waypoints();
1135 wps.erase(wps.begin());
1136 setDeparture(depApt);
1141 if (destApt && !wps.empty()) {
1143 setDestination(destApt);
1146 insertWayptsAtIndex(wps, -1);
1152bool FlightPlan::loadXmlFormat(
const SGPath& path)
1154 SGPropertyNode_ptr routeData(
new SGPropertyNode);
1157 readProperties(path, routeData);
1158 }
catch (sg_exception& e) {
1159 SG_LOG(SG_NAVAID, SG_ALERT,
"Failed to load flight-plan '" << e.getOrigin()
1160 <<
"'. " << e.getMessage());
1164 if (routeData.valid())
1167 int version = routeData->getIntValue(
"version", 1);
1170 ok = loadVersion1XMLRoute(routeData);
1171 }
else if (version == 2) {
1172 ok = loadVersion2XMLRoute(routeData);
1174 SG_LOG(SG_NAVAID, SG_POPUP,
"Unsupported flight plan version " << version <<
" loading " << path);
1178 }
catch (sg_exception& e) {
1179 SG_LOG(SG_NAVAID, SG_ALERT,
"Failed to load flight-plan '" << e.getOrigin()
1180 <<
"'. " << e.getMessage());
1187void FlightPlan::loadXMLRouteHeader(SGPropertyNode_ptr routeData)
1190 const auto rules = routeData->getStringValue(
"flight-rules",
"V");
1191 auto it = std::find(static_icaoFlightRulesCode.begin(),
1192 static_icaoFlightRulesCode.end(), rules);
1193 _flightRules =
static_cast<ICAOFlightRules>(std::distance(static_icaoFlightRulesCode.begin(), it));
1195 const auto type = routeData->getStringValue(
"flight-type",
"X");
1196 auto it2 = std::find(static_icaoFlightTypeCode.begin(),
1197 static_icaoFlightTypeCode.end(), type);
1198 _flightType =
static_cast<ICAOFlightType>(std::distance(static_icaoFlightTypeCode.begin(), it2));
1200 _callsign = routeData->getStringValue(
"callsign");
1201 _remarks = routeData->getStringValue(
"remarks");
1202 _aircraftType = routeData->getStringValue(
"aircraft-type");
1203 _estimatedDuration = routeData->getIntValue(
"estimated-duration-minutes");
1205 if (routeData->hasValue(
"is-route")) {
1206 if (_isRoute != routeData->getBoolValue(
"is-route")) {
1208 SG_LOG(SG_NAVAID, SG_INFO,
"Loading XML marked with 'is-route' into FlightPlan with is-route not set");
1213 SGPropertyNode* dep = routeData->getChild(
"departure");
1215 string depIdent = dep->getStringValue(
"airport");
1218 string rwy(dep->getStringValue(
"runway"));
1219 if (_departure->hasRunwayWithIdent(rwy)) {
1220 setDeparture(_departure->getRunwayByIdent(rwy));
1223 if (dep->hasChild(
"sid")) {
1228 const string trans = dep->getStringValue(
"sid_trans");
1229 const auto sid = dep->getStringValue(
"sid");
1230 setSID(_departure->findSIDWithIdent(sid), trans);
1236 SGPropertyNode* dst = routeData->getChild(
"destination");
1240 string rwy(dst->getStringValue(
"runway"));
1241 if (_destination->hasRunwayWithIdent(rwy)) {
1242 setDestination(_destination->getRunwayByIdent(rwy));
1245 if (dst->hasChild(
"star")) {
1248 const auto star = dst->getStringValue(
"star");
1249 const string trans = dst->getStringValue(
"star_trans");
1250 setSTAR(_destination->findSTARWithIdent(star), trans);
1253 if (dst->hasChild(
"approach")) {
1254 auto app = _destination->findApproachWithIdent(dst->getStringValue(
"approach"));
1255 const auto trans = dst->getStringValue(
"approach_trans");
1256 setApproach(app, trans);
1262 if (routeData->hasChild(
"alternate")) {
1267 SGPropertyNode* crs = routeData->getChild(
"cruise");
1269 if (crs->hasChild(
"flight-level")) {
1270 _cruiseFlightLevel = crs->getIntValue(
"flight-level");
1271 }
else if (crs->hasChild(
"altitude-ft")) {
1272 _cruiseAltitudeFt = crs->getIntValue(
"altitude-ft");
1273 }
else if (crs->hasChild(
"altitude-m")) {
1274 _cruiseAltitudeM = crs->getIntValue(
"altitude-m");
1277 if (crs->hasChild(
"mach")) {
1278 _cruiseAirspeedMach = crs->getDoubleValue(
"mach");
1279 }
else if (crs->hasChild(
"knots")) {
1280 _cruiseAirspeedKnots = crs->getIntValue(
"knots");
1281 }
else if (crs->hasChild(
"kph")) {
1282 _cruiseAirspeedKph = crs->getIntValue(
"kph");
1287bool FlightPlan::loadVersion2XMLRoute(SGPropertyNode_ptr routeData)
1289 if (!routeData->hasChild(
"route"))
1292 loadXMLRouteHeader(routeData);
1296 SGPropertyNode_ptr routeNode = routeData->getChild(
"route", 0);
1297 if (routeNode.valid()) {
1298 for (
auto wpNode : routeNode->getChildren(
"wp")) {
1299 auto wp = Waypt::createFromProperties(
this, wpNode);
1304 LegRef l =
new Leg{
this, wp};
1305 if (wpNode->hasChild(
"hold-count")) {
1306 l->setHoldCount(wpNode->getIntValue(
"hold-count"));
1311 _waypointsChanged =
true;
1315bool FlightPlan::loadVersion1XMLRoute(SGPropertyNode_ptr routeData)
1317 if (!routeData->hasChild(
"route"))
1320 loadXMLRouteHeader(routeData);
1324 SGPropertyNode_ptr routeNode = routeData->getChild(
"route", 0);
1325 for (
int i=0;
i<routeNode->nChildren(); ++
i) {
1326 SGPropertyNode_ptr wpNode = routeNode->getChild(
"wp",
i);
1327 LegRef l =
new Leg(
this, parseVersion1XMLWaypt(wpNode));
1330 _waypointsChanged =
true;
1334WayptRef FlightPlan::parseVersion1XMLWaypt(SGPropertyNode* aWP)
1337 if (!_legs.empty()) {
1338 lastPos = _legs.back()->waypoint()->position();
1339 }
else if (_departure) {
1340 lastPos = _departure->geod();
1344 string ident(aWP->getStringValue(
"ident"));
1345 if (aWP->hasChild(
"longitude-deg")) {
1347 w =
new BasicWaypt(SGGeod::fromDeg(aWP->getDoubleValue(
"longitude-deg"),
1348 aWP->getDoubleValue(
"latitude-deg")), ident,
this);
1351 string nid = aWP->getStringValue(
"navid", ident.c_str());
1358 SG_LOG(SG_GENERAL, SG_WARN,
"unknown navaid in flightplan:" << nid);
1359 pos = SGGeod::fromDeg(aWP->getDoubleValue(
"longitude-deg"),
1360 aWP->getDoubleValue(
"latitude-deg"));
1363 if (aWP->hasChild(
"offset-nm") && aWP->hasChild(
"offset-radial")) {
1364 double radialDeg = aWP->getDoubleValue(
"offset-radial");
1366 radialDeg += magvarDegAt(pos);
1367 double offsetNm = aWP->getDoubleValue(
"offset-nm");
1369 SGGeodesy::direct(pos, radialDeg, offsetNm * SG_NM_TO_METER, pos, az2);
1372 w =
new BasicWaypt(pos, ident,
this);
1375 double altFt = aWP->getDoubleValue(
"altitude-ft", -9999.9);
1376 if (altFt > -9990.0) {
1377 w->setAltitude(altFt, RESTRICT_AT, ALTITUDE_FEET);
1384bool FlightPlan::loadPlainTextFormat(
const SGPath& path)
1387 sg_gzifstream in(path);
1388 if (!in.is_open()) {
1389 throw sg_io_exception(
"Cannot open file for reading.");
1395 getline(in, line,
'\n');
1397 if (line[line.size() - 1] ==
'\r') {
1398 line.erase(line.size() - 1, 1);
1401 line = simgear::strutils::strip(line);
1402 if (line.empty() || (line[0] ==
'#')) {
1408 if (simgear::strutils::starts_with(line,
"<?xml")) {
1413 if (!_legs.empty()) {
1414 vicinity = _legs.back()->waypoint()->position();
1416 WayptRef w = waypointFromString(line, vicinity);
1418 SG_LOG(SG_NAVAID, SG_ALERT,
"Failed to create waypoint from '" << line <<
"' in " << path);
1423 _legs.push_back(LegRef{
new Leg(
this, w)});
1425 }
catch (sg_exception& e) {
1426 SG_LOG(SG_NAVAID, SG_ALERT,
"Failed to load route from: '" << path <<
"'. " << e.getMessage());
1434double FlightPlan::magvarDegAt(
const SGGeod& pos)
const
1436 double jd =
globals->get_time_params()->getJD();
1437 return sgGetMagVar(pos, jd) * SG_RADIANS_TO_DEGREES;
1442 if (aIndex < 0 || aIndex >=
numLegs()) {
1450 return SGGeod::invalid();
1457 const double normOffset = (aIndex > 0) ? -0.5 : 0.0;
1463 SGGeod basePosition = vicinity;
1464 if (!vicinity.isValid()) {
1466 if (_legs.empty()) {
1468 basePosition = _departure ? _departure->geod() :
globals->get_aircraft_position();
1470 basePosition = _legs.back()->waypoint()->position();
1477bool FlightPlan::expandVias()
1482 assert(_delegateLock > 0);
1483 bool didChangeAny =
false;
1485 for (
unsigned int i=1;
i < _legs.size(); ) {
1486 if (_legs[
i]->waypoint()->type() ==
"via") {
1487 WayptRef preceeding = _legs[
i - 1]->waypoint();
1488 Via* via =
static_cast<Via*
>(_legs[
i]->waypoint());
1492 auto it = _legs.begin() +
i;
1497 it = _legs.begin() +
i;
1501 newLegs.push_back(LegRef{
new Leg(
this, wp)});
1504 didChangeAny =
true;
1505 _legs.insert(it, newLegs.begin(), newLegs.end());
1511 return didChangeAny;
1518 SG_LOG(SG_NAVAID, SG_DEV_ALERT,
"tried to activate an is-route FlightPlan");
1524 if (routeManager->flightPlan() !=
this) {
1525 SG_LOG(SG_NAVAID, SG_DEBUG,
"setting new flight-plan on route-manager");
1526 routeManager->setFlightPlan(
this);
1533 _currentWaypointChanged =
true;
1534 _waypointsChanged = expandVias();
1536 for (
auto d : _delegates) {
1548 throw sg_exception(
"can't create FlightPlan::Leg without underlying waypoint");
1557 c->_speedRestrict = _speedRestrict;
1558 c->_speedUnits = _speedUnits;
1559 c->_altitude = _altitude;
1560 c->_altRestrict = _altRestrict;
1561 c->_altitudeUnits = _altitudeUnits;
1562 c->_holdCount = c->_holdCount;
1569 if ((
index() + 1) >= _parent->_legs.size())
1572 return _parent->legAtIndex(
index() + 1);
1577 return _parent->findLegIndex(
this);
1600 return _waypt->speed(units);
1616 return _altRestrict;
1619 return _waypt->altitudeRestriction();
1625 return _speedRestrict;
1628 return _waypt->speedRestriction();
1633 _speedRestrict = ty;
1642 _speedUnits = aUnit;
1652 _altitudeUnits = aUnit;
1663 return _pathDistance;
1668 return _distanceAlongPath;
1674 const auto wty = _waypt->type();
1675 if (wty ==
"hold") {
1679 if ((wty !=
"basic") && (wty !=
"navaid")) {
1680 SG_LOG(SG_INSTR, SG_WARN,
"convertWaypointToHold: cannot convert waypt " <<
index() <<
" " << _waypt->ident() <<
" to a hold");
1684 auto hold =
new Hold(_waypt->position(), _waypt->ident(),
const_cast<FlightPlan*
>(_parent));
1687 hold->setHoldTime(60.0);
1688 hold->setHoldRadial(_courseDeg);
1715 fp->lockDelegates();
1716 fp->_waypointsChanged =
true;
1717 fp->unlockDelegates();
1725void FlightPlan::Leg::writeToProperties(SGPropertyNode* aProp)
const
1730 aProp->setDoubleValue(
"speed-mach", _speed);
1732 aProp->setDoubleValue(
"speed-kph", _speed);
1734 aProp->setDoubleValue(
"speed", _speed);
1741 aProp->setDoubleValue(
"flight-level", _altitude);
1743 aProp->setDoubleValue(
"altitude-m", _altitude);
1745 aProp->setDoubleValue(
"altitude-ft", _altitude);
1749 if (_holdCount > 0) {
1750 aProp->setDoubleValue(
"hold-count", _holdCount);
1755void FlightPlan::rebuildLegData()
1757 _totalDistance = 0.0;
1758 double totalDistanceIncludingMissed = 0.0;
1759 RoutePath path(
this);
1761 for (
unsigned int l=0; l<_legs.size(); ++l) {
1762 _legs[l]->_courseDeg = path.trackForIndex(l);
1763 _legs[l]->_pathDistance = path.distanceForIndex(l) * SG_METER_TO_NM;
1765 totalDistanceIncludingMissed += _legs[l]->_pathDistance;
1767 _legs[l]->_distanceAlongPath = totalDistanceIncludingMissed;
1770 if (!_legs[l]->waypoint()->flag(
WPT_MISS)) {
1771 _totalDistance += _legs[l]->_pathDistance;
1786 if (fabs(aOffsetNorm) > 1.0) {
1787 SG_LOG(SG_AUTOPILOT, SG_ALERT,
"FlightPlan::pointAlongRouteNorm: called with invalid arg:" << aOffsetNorm);
1791 const bool forwards = (aOffsetNorm >= 0.0);
1807void FlightPlan::lockDelegates()
1809 if (_delegateLock == 0) {
1810 assert(!_departureChanged && !_arrivalChanged &&
1811 !_waypointsChanged && !_currentWaypointChanged);
1815 if (_delegateLock > 10) {
1816 SG_LOG(SG_GENERAL, SG_ALERT,
"hmmm");
1820void FlightPlan::unlockDelegates()
1822 assert(_delegateLock > 0);
1823 if (_delegateLock > 1) {
1830 for (
auto d : _delegates) {
1835 if (_departureChanged) {
1836 _departureChanged =
false;
1837 for (
auto d : _delegates) {
1838 d->departureChanged();
1842 if (_arrivalChanged) {
1843 _arrivalChanged =
false;
1844 for (
auto d : _delegates) {
1845 d->arrivalChanged();
1849 if (_cruiseDataChanged) {
1850 _cruiseDataChanged =
false;
1851 for (
auto d : _delegates) {
1856 if (_waypointsChanged) {
1857 _waypointsChanged =
false;
1859 for (
auto d : _delegates) {
1860 d->waypointsChanged();
1864 if (_currentWaypointChanged) {
1865 _currentWaypointChanged =
false;
1866 for (
auto d : _delegates) {
1867 d->currentWaypointChanged();
1878 throw sg_exception(
"duplicate delegate factory registration");
1898 auto it = std::find(_delegates.begin(), _delegates.end(), d);
1900 assert(it == _delegates.end());
1901 _delegates.push_back(d);
1907 auto it = std::find(_delegates.begin(), _delegates.end(), d);
1908 assert(it != _delegates.end());
1909 _delegates.erase(it);
1912void FlightPlan::notifyCleared()
1914 for (
auto d : _delegates) {
1929 _followLegTrackToFix = tf;
1934 return _followLegTrackToFix;
1939 _maxFlyByTurnAngle = deg;
1944 return _maxFlyByTurnAngle;
1950 r.push_back(_aircraftCategory);
1957 throw sg_range_exception(
"Invalid ICAO aircraft category:", cat);
1961 throw sg_range_exception(
"Invalid ICAO aircraft category:", cat);
1964 _aircraftCategory = cat[0];
1972bool FlightPlan::parseICAOLatLon(
const std::string& s, SGGeod&
p)
1974 if (s.size() == 7) {
1975 double latDegrees = std::stoi(s);
1976 double lonDegrees = std::stoi(s.substr(3, 3));
1977 if (s[2] ==
'S') latDegrees = -latDegrees;
1978 if (s[6] ==
'W') lonDegrees = -lonDegrees;
1979 p = SGGeod::fromDeg(lonDegrees, latDegrees);
1981 }
else if (s.size() == 11) {
1983 double latDegrees = std::stoi(s.substr(0, 2));
1984 double latMinutes = std::stoi(s.substr(2, 2));
1985 latDegrees += (latMinutes / 60.0);
1986 double lonDegrees = std::stoi(s.substr(5, 3));
1987 double lonMinutes = std::stoi(s.substr(8, 2));
1988 lonDegrees += (lonMinutes / 60.0);
1990 if (s[4] ==
'S') latDegrees = -latDegrees;
1991 if (s[10] ==
'W') lonDegrees = -lonDegrees;
1992 p = SGGeod::fromDeg(lonDegrees, latDegrees);
2001 auto tokens = simgear::strutils::split(routeData);
2006 std::string nextToken;
2016 }
else if (_departure != firstICAO) {
2017 SG_LOG(SG_AUTOPILOT, SG_WARN,
"ICAO route begins with an airport which is not the departure airport:" << tokens.front());
2026 currentPos = _departure->geod();
2028 for (;
i < tokens.size(); ++
i) {
2031 if (!enroute.empty()) {
2032 currentPos = enroute.back()->position();
2035 if (isdigit(tk.front())) {
2038 bool ok = parseICAOLatLon(tk, geod);
2040 enroute.push_back(
new BasicWaypt(geod, tk,
this));
2045 nextToken = (
i < (tokens.size() - 1)) ? tokens.at(
i+1) : std::string();
2048 if (nextToken.empty()) {
2049 SG_LOG(SG_AUTOPILOT, SG_WARN,
"ICAO route DIRECT segment missing waypoint");
2055 SG_LOG(SG_AUTOPILOT, SG_WARN,
"ICAO route waypoint not found:" << nextToken);
2060 }
else if (tk ==
"STAR") {
2062 auto starTrans = _destination->selectSTARByEnrouteTransition(enroute.back()->source());
2064 SG_LOG(SG_AUTOPILOT, SG_WARN,
"ICAO route couldn't find STAR transitioning from " <<
2065 enroute.back()->source()->ident());
2069 }
else if (tk ==
"SID") {
2072 auto sidTrans = _departure->selectSIDByEnrouteTransition(wpt);
2074 SG_LOG(SG_AUTOPILOT, SG_WARN,
"ICAO route couldn't find SID transitioning to " << nextToken);
2080 if (nextToken.empty()) {
2081 SG_LOG(SG_AUTOPILOT, SG_WARN,
"ICAO route airway segment missing transition:" << tk);
2089 SG_LOG(SG_AUTOPILOT, SG_WARN,
"ICAO route waypoint not found:" << nextToken);
2095 if (enroute.empty()) {
2097 if (!_sidTransition.empty()) {
2098 previous = _sid->findTransitionByName(_sidTransition)->enroute();
2101 previous = _sid->common().back();
2104 SG_LOG(SG_AUTOPILOT, SG_WARN,
"initial airway needs anchor point from SID:" << tk);
2108 previous = enroute.back();
2113 enroute.push_back(
new Via(
this, way, nav));
2116 SG_LOG(SG_AUTOPILOT, SG_WARN,
"ICAO route unknown airway:" << tk);
2123 _waypointsChanged =
true;
2125 SG_LOG(SG_AUTOPILOT, SG_INFO,
"adding waypoints from string");
2127 for (
auto l : _legs) {
2135 SG_LOG(SG_AUTOPILOT, SG_INFO,
"legs now:" <<
numLegs());
2143 if (!_sidTransition.empty())
2144 result += _sidTransition +
" ";
2146 for (
auto l : _legs) {
2147 const auto wpt = l->waypoint();
2150 if (l->nextLeg() && l->nextLeg()->waypoint()->flag(
WPT_VIA)) {
2151 nextLegAirway =
static_cast<Airway*
>(l->nextLeg()->waypoint()->owner());
2157 if (awy == nextLegAirway) {
2162 result += awy->
ident() +
" ";
2164 }
else if (wpt->type() ==
"navaid") {
2169 result += wpt->icaoDescription() +
" ";
2172 if (!_starTransition.empty())
2173 result += _starTransition;
2180 _flightRules = rules;
2185 return _flightRules;
2211 _cruiseDataChanged =
true;
2212 _cruiseAirspeedKnots = kts;
2213 _cruiseAirspeedMach = 0.0;
2214 _cruiseAirspeedKph = 0;
2220 return _cruiseAirspeedKnots;
2226 _cruiseDataChanged =
true;
2227 _cruiseAirspeedKnots = 0;
2228 _cruiseAirspeedMach = mach;
2229 _cruiseAirspeedKph = 0;
2235 return _cruiseAirspeedMach;
2241 _cruiseDataChanged =
true;
2242 _cruiseAirspeedKnots = 0;
2243 _cruiseAirspeedMach = 0.0;
2244 _cruiseAirspeedKph = kph;
2250 return _cruiseAirspeedKph;
2256 _cruiseDataChanged =
true;
2257 _cruiseAltitudeFt = 0;
2258 _cruiseAltitudeM = 0;
2259 _cruiseFlightLevel = flightLevel;
2265 return _cruiseFlightLevel;
2271 _cruiseDataChanged =
true;
2272 _cruiseAltitudeFt = altFt;
2273 _cruiseAltitudeM = 0;
2274 _cruiseFlightLevel = 0;
2280 return _cruiseAltitudeFt;
2286 _cruiseDataChanged =
true;
2287 _cruiseAltitudeFt = 0;
2288 _cruiseAltitudeM = altM;
2289 _cruiseFlightLevel = 0;
2295 return _cruiseAltitudeM;
2300 std::for_each(_legs.begin(), _legs.end(), lv);
2306 const auto numLegs = _legs.size();
2309 return static_cast<int>(
i);
2318 const auto numLegs = _legs.size();
2321 return static_cast<int>(
i);
2330 const auto numLegs = _legs.size();
2333 return static_cast<int>(
i);
2342 if (!_destinationRunway)
2348 if (_legs.at(
i)->waypoint()->source() == _destinationRunway) {
const FGAirport * fgFindAirportID(const std::string &id)
SGSharedPtr< FGAirport > FGAirportRef
SGSharedPtr< FGPositioned > FGPositionedRef
static FGAirportRef findByIdent(const std::string &aIdent)
Helper to look up an FGAirport instance by unique ident.
static FGPositionedRef findClosestWithIdent(const std::string &aIdent, const SGGeod &aPos, Filter *aFilter=NULL)
Top level route manager class.
FGAirportRef airport() const
SGGeod positionForIndex(int index) const
SGGeod positionForDistanceFrom(int index, double distanceM) const
double distanceForIndex(int index) const
FGPositionedRef findNodeByIdent(const std::string &ident, const SGGeod &near) const
static Network * lowLevel()
static Network * highLevel()
std::string ident() const override
static AirwayRef findByIdentAndVia(const std::string &aIdent, const WayptRef &from, const WayptRef &to)
Find the airway based on its ident.
Describe an approach procedure, including the missed approach segment.
static bool isApproach(ProcedureType ty)
virtual void destroyFlightPlanDelegate(FlightPlan *fp, Delegate *d)
flight-plan leg encapsulation
bool convertWaypointToHold()
double speed(RouteUnits units=DEFAULT_UNITS) const
double distanceAlongRoute() const
RouteRestriction altitudeRestriction() const
void setSpeed(RouteRestriction ty, double speed, RouteUnits units=DEFAULT_UNITS)
RouteRestriction speedRestriction() const
bool setHoldCount(int count)
requesting holding at the waypoint upon reaching it.
double distanceNm() const
void markWaypointDirty()
helper function, if the waypoint is modified in some way, to notify the flightplan owning this leg,...
double altitude(RouteUnits units=DEFAULT_UNITS) const
unsigned int index() const
FlightPlan * owner() const
void setAltitude(RouteRestriction ty, double alt, RouteUnits units=DEFAULT_UNITS)
ICAOFlightType flightType() const
int cruiseSpeedKnots() const
void setCruiseSpeedMach(double mach)
int cruiseAltitudeM() const
std::string icaoAircraftCategory() const
SGSharedPtr< Leg > LegRef
void setAlternate(FGAirportRef alt)
void deleteIndex(int index)
std::shared_ptr< DelegateFactory > DelegateFactoryRef
double maxFlyByTurnAngle() const
int indexOfFirstNonDepartureWaypoint() const
int indexOfFirstApproachWaypoint() const
std::string remarks() const
SGGeod vicinityForInsertIndex(int aIndex) const
given an index to insert a waypoint into the plan, find the geographical vicinity.
int clearWayptsWithFlag(WayptFlag flag)
LegRef currentLeg() const
bool parseICAORouteString(const std::string &routeData)
attempt to replace the route waypoints (and potentially the SID and STAR) based on an ICAO standard r...
void setSID(SID *sid, const std::string &transition=std::string())
int indexOfFirstArrivalWaypoint() const
void insertWayptsAtIndex(const WayptVec &wps, int aIndex)
FlightPlanRef clone(const std::string &newIdent={}, bool convertToFlightPlan=false) const
void setCruiseSpeedKPH(int kmh)
static void unregisterDelegateFactory(DelegateFactoryRef df)
void setSTAR(STAR *star, const std::string &transition=std::string())
std::string callsign() const
bool isRoute() const
is this flight-pan a route (for planning) or an active flight-plan (which can be flown?...
static FlightPlanRef create()
create a FlightPlan with isRoute not set
void setEstimatedDurationMinutes(int minutes)
void forEachLeg(const LegVisitor &lv)
void setCruiseFlightLevel(int flightLevel)
void setDeparture(FGAirport *apt)
std::string asICAORouteString() const
void setCruiseAltitudeFt(int altFt)
FGAirportRef alternate() const
void setFlightRules(ICAOFlightRules rules)
double cruiseSpeedMach() const
int cruiseSpeedKPH() const
void setIcaoAircraftType(const std::string &ty)
LegRef insertWayptAtIndex(Waypt *aWpt, int aIndex)
void setRemarks(const std::string &remarks)
void setApproach(Approach *app, const std::string &transition={})
note setting an approach will implicitly update the destination airport and runway to match
LegRef previousLeg() const
int cruiseFlightLevel() const
void addDelegate(Delegate *d)
bool followLegTrackToFixes() const
void setDestination(FGAirport *apt)
void setCallsign(const std::string &callsign)
void setFlightType(ICAOFlightType type)
Transition * approachTransition() const
int cruiseAltitudeFt() const
virtual std::string ident() const
int findWayptIndex(const SGGeod &aPos) const
void setIdent(const std::string &s)
void setCurrentIndex(int index)
LegRef legAtIndex(int index) const
void setCruiseSpeedKnots(int kts)
bool load(const SGPath &p)
void setFollowLegTrackToFixes(bool tf)
WayptRef waypointFromString(const std::string &target, const SGGeod &vicinity=SGGeod::invalid())
Create a WayPoint from a string in the following format:'vicinity' specifies the search area,...
bool save(const SGPath &p) const
void setMaxFlyByTurnAngle(double deg)
static void registerDelegateFactory(DelegateFactoryRef df)
Transition * sidTransition() const
SGGeod pointAlongRoute(int aIndex, double aOffsetNm) const
given a waypoint index, and an offset in NM, find the geodetic position on the route path.
void removeDelegate(Delegate *d)
SGGeod pointAlongRouteNorm(int aIndex, double aOffsetNorm) const
given a waypoint index, find a point at a normalised offset, which must be [-1 .
std::function< void(Leg *)> LegVisitor
void setIcaoAircraftCategory(const std::string &cat)
int indexOfDestinationRunwayWaypoint() const
void computeDurationMinutes()
computeDurationMinutes - use performance data and cruise data to estimate enroute time
static FlightPlanRef createRoute()
@factory to create a FlightPlan with isRoute=true
void setCruiseAltitudeM(int altM)
ICAOFlightRules flightRules() const
Transition * starTransition() const
void startElement(const char *name, const XMLAttributes &atts) override
void data(const char *s, int length) override
const WayptVec & waypoints() const
void endElement(const char *name) override
GpxXmlVisitor(FlightPlan *fp)
Waypoint based upon a navaid.
virtual ProcedureType type() const =0
virtual std::string ident() const
Encapsulate a transition segment.
Procedure * parent() const
WayptVec expandToWaypoints(WayptRef aPreceeding) const
Abstract base class for waypoints (and things that are treated similarly by navigation systems).
virtual unsigned int flags() const
static WayptRef createFromString(RouteBase *aOwner, const std::string &s, const SGGeod &vicinity)
Create a waypoint from the route manager's standard string format:
virtual bool flag(WayptFlag aFlag) const
Test if the specified flag is set for this element.
std::vector< std::string > string_list
FlightPlan.hxx - defines a full flight-plan object, including departure, cruise, arrival information ...
SGSharedPtr< FlightPlan > FlightPlanRef
double convertAltitudeUnits(RouteUnits aSrc, RouteUnits aDest, double aValue)
std::vector< FlightPlan::DelegateFactoryRef > FPDelegateFactoryVec
static FPDelegateFactoryVec static_delegateFactories
SGSharedPtr< FGPositioned > FGPositionedRef
static WayptVec copyWaypointsExpandingVias(WayptRef preceeding, const WayptVec &wps)
SGSharedPtr< Waypt > WayptRef
SGSharedPtr< Airway > AirwayRef
double convertSpeedUnits(RouteUnits aSrc, RouteUnits aDest, double aAltitudeFt, double aValue)
const char * restrictionToString(RouteRestriction aRestrict)
const char ICAO_AIRCRAFT_CATEGORY_E
bool isMachRestrict(RouteRestriction rr)
static bool anyWaypointsWithFlag(FlightPlan *plan, WayptFlag flag)
@ WPT_MISS
segment is part of missed approach
@ WPT_GENERATED
waypoint was created automatically (not manually entered/loaded) for example waypoints from airway ro...
@ WPT_VIA
waypoint prodcued by expanding a VIA segment
const char ICAO_AIRCRAFT_CATEGORY_C
const char ICAO_AIRCRAFT_CATEGORY_A
std::vector< WayptRef > WayptVec
static double atof(const string &str)