FlightGear next
FlightPlan.cxx
Go to the documentation of this file.
1// FlightPlan.cxx - flight plan object
2
3// Written by James Turner, started 2012.
4//
5// Copyright (C) 2012 Curtis L. Olson
6//
7// This program is free software; you can redistribute it and/or
8// modify it under the terms of the GNU General Public License as
9// published by the Free Software Foundation; either version 2 of the
10// License, or (at your option) any later version.
11//
12// This program is distributed in the hope that it will be useful, but
13// WITHOUT ANY WARRANTY; without even the implied warranty of
14// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15// General Public License for more details.
16//
17// You should have received a copy of the GNU General Public License
18// along with this program; if not, write to the Free Software
19// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
21#include "config.h"
22
23#include "FlightPlan.hxx"
24
25// std
26#include <map>
27#include <fstream>
28#include <cstring>
29#include <cassert>
30#include <algorithm>
31
32// SimGear
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>
41
42// FlightGear
43#include <Main/globals.hxx>
44#include "Main/fg_props.hxx"
45#include <Navaids/procedure.hxx>
46#include <Navaids/waypoint.hxx>
47#include <Navaids/routePath.hxx>
48#include <Navaids/airways.hxx>
52
53using std::string;
54using std::vector;
55using std::endl;
56using std::fstream;
57
58namespace {
59
60const string_list static_icaoFlightRulesCode = {
61 "V",
62 "I",
63 "Y",
64 "Z"
65};
66
67const string_list static_icaoFlightTypeCode = {
68 "S",
69 "N",
70 "G",
71 "M",
72 "X"
73};
74
75} // of anonymous namespace
76
77namespace flightgear {
78
79// implemented in route.cxx
80const char* restrictionToString(RouteRestriction aRestrict);
81
82typedef std::vector<FlightPlan::DelegateFactoryRef> FPDelegateFactoryVec;
84
85FlightPlan::FlightPlan(bool isRoute) :
86 _isRoute(isRoute),
87 _currentIndex(-1),
88 _followLegTrackToFix(true),
89 _aircraftCategory(ICAO_AIRCRAFT_CATEGORY_C),
90 _departureRunway(nullptr),
91 _destinationRunway(nullptr),
92 _sid(nullptr),
93 _star(nullptr),
94 _approach(nullptr),
95 _totalDistance(0.0)
96{
97 _departureChanged = _arrivalChanged = _waypointsChanged = _currentWaypointChanged = false;
98 _cruiseDataChanged = false;
99
100 for (auto factory : static_delegateFactories) {
101 Delegate* d = factory->createFlightPlanDelegate(this);
102 if (d) { // factory might not always create a delegate
103 d->_factory = factory; // record for clean-up purposes
104 addDelegate(d);
105 }
106 }
107}
108
110{
111 return new FlightPlan(false);
112}
113
115{
116 return new FlightPlan(true);
117}
118
120{
121// clean up delegates
122 for (auto d : _delegates) {
123 if (d->_factory) {
124 auto f = d->_factory;
125 f->destroyFlightPlanDelegate(this, d);
126 }
127 }
128}
129
130FlightPlanRef FlightPlan::clone(const string& newIdent, bool convertIntoFlightPlan) const
131{
132 // this is the only place we allow conversion of a route into an active FP,
133 // by design. Forces people to clone-to-a-flight-plan if they want to
134 // activate a route.
135 FlightPlanRef c = new FlightPlan(convertIntoFlightPlan ? false : _isRoute);
136 c->_ident = newIdent.empty() ? _ident : newIdent;
137 c->lockDelegates();
138
139// copy destination / departure data.
140 c->setDeparture(_departure);
141 c->setDeparture(_departureRunway);
142
143 if (_approach) {
144 c->setApproach(_approach, _approachTransition);
145 } else if (_destinationRunway) {
146 c->setDestination(_destinationRunway);
147 } else if (_destination) {
148 c->setDestination(_destination);
149 }
150
151 c->setSTAR(_star, _starTransition);
152 c->setSID(_sid, _sidTransition);
153
154 // mark data as unchanged since this is a clean plan
155 c->_arrivalChanged = false;
156 c->_departureChanged = false;
157
158 // copy cruise data
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);
165 }
166
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);
173 }
174
175 c->_didLoadFP = true; // set the loaded flag to give delegates a chance
176
177 // copy legs
178 c->_waypointsChanged = true;
179 for (int l=0; l < numLegs(); ++l) {
180 c->_legs.push_back(_legs[l]->cloneFor(c));
181 }
182
183 c->expandVias();
184 c->unlockDelegates();
185 return c;
186}
187
188void FlightPlan::setIdent(const string& s)
189{
190 _ident = s;
191}
192
193string FlightPlan::ident() const
194{
195 return _ident;
196}
197
199{
200 if (!aWpt) {
201 return nullptr;
202 }
203
204 WayptVec wps;
205 wps.push_back(aWpt);
206
207 int index = aIndex;
208 if ((aIndex == -1) || (aIndex > (int) _legs.size())) {
209 index = _legs.size();
210 }
211
212 insertWayptsAtIndex(wps, index);
213 return legAtIndex(index);
214}
215
217{
218 WayptVec result;
219 result.reserve(wps.size());
220
221 for (auto wp : wps) {
222 if (wp->type() == "via") {
223 Via* via = static_cast<Via*>(wp.get());
224 WayptVec viaPoints = via->expandToWaypoints(preceeding);
225 result.insert(result.end(), viaPoints.begin(), viaPoints.end());
226 } else {
227 // everything else is copied directly
228 result.push_back(wp);
229 }
230 }
231
232 return result;
233}
234
235void FlightPlan::insertWayptsAtIndex(const WayptVec& wps, int aIndex)
236{
237 if (wps.empty()) {
238 return;
239 }
240
241 int index = aIndex;
242 if ((aIndex == -1) || (aIndex > (int) _legs.size())) {
243 index = _legs.size();
244 }
245
246 WayptVec toInsertWps = wps;
247 // catch insert of VIAs here
248 if (!_isRoute && (index > 0)) {
249 const auto pre = _legs.at(index - 1)->waypoint();
250 toInsertWps = copyWaypointsExpandingVias(pre, wps);
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");
254 }
255 }
256
257 auto it = _legs.begin() + index;
258 int endIndex = index + toInsertWps.size() - 1;
259 if (_currentIndex >= endIndex) {
260 _currentIndex += toInsertWps.size();
261 }
262
263 LegVec newLegs;
264 for (WayptRef wp : toInsertWps) {
265 newLegs.push_back(LegRef{new Leg(this, wp)});
266 }
267
268 lockDelegates();
269 _waypointsChanged = true;
270 _legs.insert(it, newLegs.begin(), newLegs.end());
271 unlockDelegates();
272}
273
275{
276 int index = aIndex;
277 if (aIndex < 0) { // negative indices count the the end
278 index = _legs.size() + index;
279 }
280
281 if ((index < 0) || (index >= numLegs())) {
282 SG_LOG(SG_NAVAID, SG_WARN, "removeAtIndex with invalid index:" << aIndex);
283 return;
284 }
285
286 lockDelegates();
287 _waypointsChanged = true;
288
289 auto it = _legs.begin() + index;
290 LegRef l = *it;
291 _legs.erase(it);
292 l->_parent = nullptr; // orphan the leg so it's clear from Nasal
293
294 if (_currentIndex == index) {
295 // current waypoint was removed
296 _currentWaypointChanged = true;
297 } else if (_currentIndex > index) {
298 --_currentIndex; // shift current index down if necessary
299 }
300
301 unlockDelegates();
302}
303
305{
306 lockDelegates();
307 _departure.clear();
308 _departureRunway = nullptr;
309 _destinationRunway = nullptr;
310 _destination.clear();
311 _sid.clear();
312 _sidTransition.clear();
313 _star.clear();
314 _starTransition.clear();
315 _approach.clear();
316 _approachTransition.clear();
317 _alternate.clear();
318
319 _cruiseAirspeedMach = 0.0;
320 _cruiseAirspeedKnots = 0;
321 _cruiseAirspeedKph = 0;
322 _cruiseFlightLevel = 0;
323 _cruiseAltitudeFt = 0;
324 _cruiseAltitudeM = 0;
325
326 clearLegs();
327 unlockDelegates();
328}
329
331{
332 // some badly behaved CDU implementations call clear on a Nasal timer
333 // during startup.
334 if (_legs.empty() && (_currentIndex < 0)) {
335 return;
336 }
337
338 lockDelegates();
339 _waypointsChanged = true;
340 _currentWaypointChanged = true;
341 _arrivalChanged = true;
342 _departureChanged = true;
343 _cruiseDataChanged = true;
344
345 _currentIndex = -1;
346 _legs.clear();
347
348 notifyCleared();
349 unlockDelegates();
350}
351
353{
354 int count = 0;
355// first pass, fix up currentIndex
356 for (int i=0; i<_currentIndex; ++i) {
357 const auto& l = _legs.at(i);
358 if (l->waypoint()->flag(flag)) {
359 ++count;
360 }
361 }
362
363 // test if the current leg will be removed
364 bool currentIsBeingCleared = false;
365 Leg* curLeg = currentLeg();
366 if (curLeg) {
367 currentIsBeingCleared = curLeg->waypoint()->flag(flag);
368 }
369
370 _currentIndex -= count;
371
372 // if we're clearing the current waypoint, what shall we do with the
373 // index? there's various options, but safest is to select no waypoint
374 // and let the use re-activate.
375 // http://code.google.com/p/flightgear-bugs/issues/detail?id=1134
376 if (currentIsBeingCleared) {
377 SG_LOG(SG_GENERAL, SG_INFO, "FlightPlan::clearWayptsWithFlag: currentIsBeingCleared:" << currentIsBeingCleared);
378 _currentIndex = -1;
379 }
380
381// now delete and remove
382 int numDeleted = 0;
383 auto it = std::remove_if(_legs.begin(), _legs.end(),
384 [flag, &numDeleted](const LegRef& leg)
385 {
386 if (leg->waypoint()->flag(flag)) {
387 ++numDeleted;
388 return true;
389 }
390 return false;
391 });
392 if (it == _legs.end()) {
393 return 0; // nothing was cleared, don't fire the delegate
394 }
395
396 lockDelegates();
397 _waypointsChanged = true;
398 if ((count > 0) || currentIsBeingCleared) {
399 _currentWaypointChanged = true;
400 }
401
402 _legs.erase(it, _legs.end());
403
404 if (_legs.empty()) { // maybe all legs were deleted
405 notifyCleared();
406 }
407
408 unlockDelegates();
409 return numDeleted;
410}
411
413{
414 return _isRoute;
415}
416
418{
419 if (_isRoute)
420 return false;
421 return (_currentIndex >= 0);
422}
423
425{
426 if ((index < -1) || (index >= numLegs())) {
427 throw sg_range_exception("invalid leg index", "FlightPlan::setCurrentIndex");
428 }
429
430 if (index == _currentIndex) {
431 return;
432 }
433
434 lockDelegates();
435 _currentIndex = index;
436 _currentWaypointChanged = true;
437 unlockDelegates();
438}
439
441{
442 lockDelegates();
443 for (auto d : _delegates) {
444 d->sequence();
445 }
446 unlockDelegates();
447}
448
450{
451 if (_isRoute) {
452 throw sg_exception("Called finish on FlightPlan marked isRoute");
453 }
454
455 if (_currentIndex == -1) {
456 return;
457 }
458
459 lockDelegates();
460 _currentIndex = -1;
461 _currentWaypointChanged = true;
462
463 for (auto d : _delegates) {
464 d->endOfFlightPlan();
465 }
466
467 unlockDelegates();
468}
469
470int FlightPlan::findWayptIndex(const SGGeod& aPos) const
471{
472 for (int i=0; i<numLegs(); ++i) {
473 if (_legs[i]->waypoint()->matches(aPos)) {
474 return i;
475 }
476 }
477
478 return -1;
479}
480
482{
483 for (int i=0; i<numLegs(); ++i) {
484 if (_legs[i]->waypoint()->matches(aPos)) {
485 return i;
486 }
487 }
488
489 return -1;
490}
491
493{
494 if ((_currentIndex < 0) || (_currentIndex >= numLegs()))
495 return nullptr;
496 return legAtIndex(_currentIndex);
497}
498
500{
501 if (_currentIndex <= 0) {
502 return nullptr;
503 }
504
505 return legAtIndex(_currentIndex - 1);
506}
507
509{
510 if ((_currentIndex < 0) || ((_currentIndex + 1) >= numLegs())) {
511 return nullptr;
512 }
513
514 return legAtIndex(_currentIndex + 1);
515}
516
518{
519 if ((index < 0) || (index >= numLegs())) {
520 throw sg_range_exception("index out of range", "FlightPlan::legAtIndex");
521 }
522
523 return _legs.at(index);
524}
525
526int FlightPlan::findLegIndex(const Leg* l) const
527{
528 for (unsigned int i=0; i<_legs.size(); ++i) {
529 if (_legs.at(i).get() == l) {
530 return i;
531 }
532 }
533
534 return -1;
535}
536
538{
539 if (apt == _departure) {
540 return;
541 }
542
543 lockDelegates();
544 _departureChanged = true;
545 _departure = apt;
546 _departureRunway = nullptr;
547 clearSID();
548 unlockDelegates();
549}
550
552{
553 if (_departureRunway == rwy) {
554 return;
555 }
556
557 lockDelegates();
558 _departureChanged = true;
559
560 _departureRunway = rwy;
561 if (rwy->airport() != _departure) {
562 _departure = rwy->airport();
563 clearSID();
564 }
565 unlockDelegates();
566}
567
569{
570 lockDelegates();
571 _departureChanged = true;
572 _departure = nullptr;
573 _departureRunway = nullptr;
574 clearSID();
575 unlockDelegates();
576}
577
578void FlightPlan::setSID(SID* sid, const std::string& transition)
579{
580 if ((sid == _sid) && (_sidTransition == transition)) {
581 return;
582 }
583
584 lockDelegates();
585 _departureChanged = true;
586 _sid = sid;
587 _sidTransition = transition;
588 unlockDelegates();
589}
590
592{
593 if (!trans) {
594 setSID(static_cast<SID*>(nullptr));
595 return;
596 }
597
598 if (trans->parent()->type() != PROCEDURE_SID)
599 throw sg_exception("FlightPlan::setSID: transition does not belong to a SID");
600
601 setSID(static_cast<SID*>(trans->parent()), trans->ident());
602}
603
605{
606 lockDelegates();
607 _departureChanged = true;
608 _sid = nullptr;
609 _sidTransition.clear();
610 unlockDelegates();
611}
612
614{
615 if (!_sid || _sidTransition.empty()) {
616 return nullptr;
617 }
618
619 return _sid->findTransitionByName(_sidTransition);
620}
621
623{
624 if (apt == _destination) {
625 return;
626 }
627
628 lockDelegates();
629 _arrivalChanged = true;
630 _destination = apt;
631 _destinationRunway = nullptr;
632 clearSTAR();
633 setApproach(static_cast<Approach*>(nullptr));
634 unlockDelegates();
635}
636
638{
639 if (_destinationRunway == rwy) {
640 return;
641 }
642
643 lockDelegates();
644 _arrivalChanged = true;
645 _destinationRunway = rwy;
646 if (rwy && (_destination != rwy->airport())) {
647 _destination = rwy->airport();
648 clearSTAR();
649 }
650
651 unlockDelegates();
652}
653
655{
656 lockDelegates();
657 _arrivalChanged = true;
658 _destination = nullptr;
659 _destinationRunway = nullptr;
660 clearSTAR();
661 setApproach(static_cast<Approach*>(nullptr));
662 unlockDelegates();
663}
664
666{
667 return _alternate;
668}
669
671{
672 lockDelegates();
673 _alternate = alt;
674 _arrivalChanged = true;
675 unlockDelegates();
676}
677
678void FlightPlan::setSTAR(STAR* star, const std::string& transition)
679{
680 if ((_star == star) && (_starTransition == transition)) {
681 return;
682 }
683
684 lockDelegates();
685 _arrivalChanged = true;
686 _star = star;
687 _starTransition = transition;
688 unlockDelegates();
689}
690
692{
693 if (!trans) {
694 setSTAR((STAR*) NULL);
695 return;
696 }
697
698 if (trans->parent()->type() != PROCEDURE_STAR)
699 throw sg_exception("FlightPlan::setSTAR: transition does not belong to a STAR");
700
701 setSTAR((STAR*) trans->parent(), trans->ident());
702}
703
705{
706
707 lockDelegates();
708 _arrivalChanged = true;
709 _star = nullptr;
710 _starTransition.clear();
711 unlockDelegates();
712}
713
715{
716 _estimatedDuration = mins;
717}
718
720{
721 if ((_cruiseAirspeedMach < 0.01) && (_cruiseAirspeedKnots < 10) && (_cruiseAirspeedKph < 10)) {
722 SG_LOG(SG_AUTOPILOT, SG_WARN, "can't compute duration, no cruise speed set");
723 return;
724 }
725
726 if ((_cruiseAltitudeFt < 100) && (_cruiseAltitudeM < 100) && (_cruiseFlightLevel < 10)) {
727 SG_LOG(SG_AUTOPILOT, SG_WARN, "can't compute duration, no cruise altitude set");
728 return;
729 }
730
731
732}
733
735{
736 if (!_star || _starTransition.empty()) {
737 return nullptr;
738 }
739
740 return _star->findTransitionByName(_starTransition);
741}
742
743void FlightPlan::setApproach(flightgear::Approach* app, const std::string& trans)
744{
745 if ((_approach == app) && (trans == _approachTransition)) {
746 return;
747 }
748
749 lockDelegates();
750 _arrivalChanged = true;
751 _approach = app;
752 _approachTransition = trans;
753 if (app) {
754 // keep runway + airport in sync
755 if (_destinationRunway != _approach->runway()) {
756 _destinationRunway = _approach->runway();
757 }
758
759 if (_destination != _destinationRunway->airport()) {
760 _destination = _destinationRunway->airport();
761 }
762 }
763 unlockDelegates();
764}
765
766void FlightPlan::setApproach(Transition* approachWithTrans)
767{
768 if (!approachWithTrans) {
769 setApproach((Approach*)nullptr);
770 return;
771 }
772
773 if (!Approach::isApproach(approachWithTrans->parent()->type()))
774 throw sg_exception("FlightPlan::setApproach: transition does not belong to an approach");
775
776 setApproach(static_cast<Approach*>(approachWithTrans->parent()),
777 approachWithTrans->ident());
778}
779
781{
782 if (!_approach || _approachTransition.empty()) {
783 return nullptr;
784 }
785
786 return _approach->findTransitionByName(_approachTransition);
787}
788
789bool FlightPlan::save(std::ostream& stream) const
790{
791 try {
792 SGPropertyNode_ptr d(new SGPropertyNode);
793 saveToProperties(d);
794 writeProperties(stream, d, true);
795 return true;
796 } catch (sg_exception& e) {
797 SG_LOG(SG_NAVAID, SG_ALERT, "Failed to save flight-plan " << e.getMessage());
798 return false;
799 }
800}
801
802bool FlightPlan::save(const SGPath& path) const
803{
804 try {
805 SGPropertyNode_ptr d(new SGPropertyNode);
806 saveToProperties(d);
807 writeProperties(path, d, true /* write-all */);
808 return true;
809 } catch (sg_exception& e) {
810 SG_LOG(SG_NAVAID, SG_ALERT, "Failed to save flight-plan '" << path << "'. " << e.getMessage());
811 return false;
812 }
813}
814
815void FlightPlan::saveToProperties(SGPropertyNode* d) const
816{
817 d->setIntValue("version", 2);
818
819 // general data
820 if (_isRoute) {
821 d->setBoolValue("is-route", true);
822 }
823
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);
828 }
829 if (!_remarks.empty()) {
830 d->setStringValue("remarks", _remarks);
831 }
832 if (!_aircraftType.empty()) {
833 d->setStringValue("aircraft-type", _aircraftType);
834 }
835 d->setIntValue("estimated-duration-minutes", _estimatedDuration);
836
837 if (_departure) {
838 d->setStringValue("departure/airport", _departure->ident());
839 if (_sid) {
840 d->setStringValue("departure/sid", _sid->ident());
841 if (!_sidTransition.empty())
842 d->setStringValue("departure/sid_trans", _sidTransition);
843 }
844
845 if (_departureRunway) {
846 d->setStringValue("departure/runway", _departureRunway->ident());
847 }
848 }
849
850 if (_destination) {
851 d->setStringValue("destination/airport", _destination->ident());
852 if (_star) {
853 d->setStringValue("destination/star", _star->ident());
854 if (!_starTransition.empty())
855 d->setStringValue("destination/star_trans", _starTransition);
856 }
857
858 if (_approach) {
859 d->setStringValue("destination/approach", _approach->ident());
860 if (!_approachTransition.empty())
861 d->setStringValue("destination/approach_trans", _approachTransition);
862 }
863
864 if (_destinationRunway) {
865 d->setStringValue("destination/runway", _destinationRunway->ident());
866 }
867 }
868
869 if (_alternate) {
870 d->setStringValue("alternate", _alternate->ident());
871 }
872
873 // cruise data
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);
880 }
881
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);
888 }
889
890 // route nodes
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);
898 } // of waypoint iteration
899}
900
902{
903 bool r = false;
904 plan->forEachLeg([&r, flag](FlightPlan::Leg* l) {
905 if (l->waypoint()->flags() & flag) {
906 r = true;
907 }
908 });
909
910 return r;
911}
912
913bool FlightPlan::load(const SGPath& path)
914{
915 if (!path.exists()) {
916 SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load flight-plan '" << path
917 << "'. The file does not exist.");
918 return false;
919 }
920
921 SG_LOG(SG_NAVAID, SG_INFO, "going to read flight-plan from:" << path);
922
923 bool Status = false;
924 lockDelegates();
925
926 // try different file formats
927 if (loadGpxFormat(path)) { // GPX format
928 _arrivalChanged = true;
929 _departureChanged = true;
930 Status = true;
931 } else if (loadXmlFormat(path)) { // XML property data
932 if (!_isRoute) {
933 expandVias();
934 }
935
936 // we don't want to re-compute the arrival / departure after
937 // a load, since we assume the flight-plan had it specified already
938 // especially, the XML might have a SID/STAR embedded, which we don't
939 // want to lose
940
941 // however, we do want to run the normal delegate if no procedure was
942 // defined. We'll use the presence of waypoints tagged to decide
943 const bool hasArrival = anyWaypointsWithFlag(this, WPT_ARRIVAL);
944 const bool hasDeparture = anyWaypointsWithFlag(this, WPT_DEPARTURE);
945 _arrivalChanged = !hasArrival;
946 _departureChanged = !hasDeparture;
947 Status = true;
948 } else if (loadPlainTextFormat(path)) { // simple textual list of waypoints
949 _arrivalChanged = true;
950 _departureChanged = true;
951 Status = true;
952
953 if (!_isRoute) {
954 expandVias(); // plain text could in principle contain VIAs
955 }
956 }
957
958 if (Status == true) {
959 setIdent(path.file_base());
960 }
961
962 _cruiseDataChanged = true;
963 _waypointsChanged = true;
964 _didLoadFP = true;
965
966 unlockDelegates();
967
968 return Status;
969}
970
971bool FlightPlan::load(std::istream &stream)
972{
973 SGPropertyNode_ptr routeData(new SGPropertyNode);
974 try {
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());
979 return false;
980 }
981
982 if (!routeData.valid())
983 return false;
984
985 bool Status = false;
986 lockDelegates();
987 try {
988 int version = routeData->getIntValue("version", 1);
989 if (version == 2) {
990 loadVersion2XMLRoute(routeData);
991 Status = true;
992 } else {
993 throw sg_io_exception("unsupported XML route version");
994 }
995 } catch (sg_exception& e) {
996 SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load flight-plan '" << e.getOrigin()
997 << "'. " << e.getMessage());
998 Status = false;
999 }
1000
1001 if (!_isRoute) {
1002 expandVias();
1003 }
1004
1005 // we don't want to re-compute the arrival / departure after
1006 // a load, since we assume the flight-plan had it specified already
1007 // especially, the XML might have a SID/STAR embedded, which we don't
1008 // want to lose
1009
1010 // however, we do want to run the normal delegate if no procedure was
1011 // defined. We'll use the presence of waypoints tagged to decide
1012 const bool hasArrival = anyWaypointsWithFlag(this, WPT_ARRIVAL);
1013 const bool hasDeparture = anyWaypointsWithFlag(this, WPT_DEPARTURE);
1014 _arrivalChanged = !hasArrival;
1015 _departureChanged = !hasDeparture;
1016
1017 _cruiseDataChanged = true;
1018 _waypointsChanged = true;
1019 _didLoadFP = true;
1020
1021 unlockDelegates();
1022
1023 return Status;
1024}
1025
1027class GpxXmlVisitor : public XMLVisitor
1028{
1029public:
1030 GpxXmlVisitor(FlightPlan* fp) : _fp(fp), _lat(-9999), _lon(-9999) {}
1031
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;
1035
1036 const WayptVec& waypoints() const { return _waypoints; }
1037private:
1038 FlightPlan* _fp;
1039 double _lat, _lon, _elevationM;
1040 string _element;
1041 string _waypoint;
1042 WayptVec _waypoints;
1043};
1044
1045void GpxXmlVisitor::startElement(const char * name, const XMLAttributes &atts)
1046{
1047 _element = name;
1048 if (!strcmp(name, "rtept")) {
1049 _waypoint.clear();
1050 _lat = _lon = _elevationM = -9999;
1051 const char* slat = atts.getValue("lat");
1052 const char* slon = atts.getValue("lon");
1053 if (slat && slon) {
1054 _lat = atof(slat);
1055 _lon = atof(slon);
1056 }
1057 }
1058}
1059
1060void GpxXmlVisitor::data(const char * s, int length)
1061{
1062 // use "name" when given, otherwise use "cmt" (comment) as ID
1063 if ((_element == "name") ||
1064 ((_waypoint.empty()) && (_element == "cmt")))
1065 {
1066 _waypoint = std::string(s, static_cast<size_t>(length));
1067 }
1068
1069 if (_element == "ele") {
1070 _elevationM = atof(s);
1071 }
1072}
1073
1075{
1076 _element.clear();
1077 if (!strcmp(name, "rtept")) {
1078 if (_lon > -9990.0) {
1079 const auto geod = SGGeod::fromDeg(_lon, _lat);
1080 auto pos = FGPositioned::findClosestWithIdent(_waypoint, geod);
1081 WayptRef wp;
1082
1083 if (pos) {
1084 // check distance
1085 const auto dist = SGGeodesy::distanceM(geod, pos->geod());
1086 if (dist < 800.0) {
1087 wp = new NavaidWaypoint(pos, _fp);
1088 }
1089 }
1090
1091 if (!wp) {
1092 wp = new BasicWaypt(geod, _waypoint, _fp);
1093 }
1094
1095 if (_elevationM > -9990.0) {
1096 wp->setAltitude(_elevationM * SG_METER_TO_FEET, RESTRICT_AT);
1097 }
1098 _waypoints.push_back(wp);
1099 }
1100 }
1101}
1102
1104bool FlightPlan::loadGpxFormat(const SGPath& path)
1105{
1106 if (path.lower_extension() != "gpx") {
1107 // not a valid GPX file
1108 return false;
1109 }
1110
1111 GpxXmlVisitor gpxVisitor(this);
1112 try {
1113 readXML(path, gpxVisitor);
1114 } catch (sg_exception& e) {
1115 // XML parsing fails => not a GPX XML file
1116 SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load flight-plan in GPX format: '" << e.getOrigin()
1117 << "'. " << e.getMessage());
1118 return false;
1119 }
1120
1121 if (gpxVisitor.waypoints().empty()) {
1122 SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load flight-plan in GPX format. No route found.");
1123 return false;
1124 }
1125
1126 clearAll();
1127
1128 // copy in case we need to modify
1129 WayptVec wps = gpxVisitor.waypoints();
1130 // detect airports
1131 const auto depApt = FGAirport::findByIdent(wps.front()->ident());
1132 const auto destApt = FGAirport::findByIdent(wps.back()->ident());
1133
1134 if (depApt) {
1135 wps.erase(wps.begin());
1136 setDeparture(depApt);
1137 }
1138
1139 // for a single-element waypoint list consisting of a single airport ID,
1140 // don't crash
1141 if (destApt && !wps.empty()) {
1142 wps.pop_back();
1143 setDestination(destApt);
1144 }
1145
1146 insertWayptsAtIndex(wps, -1);
1147
1148 return true;
1149}
1150
1152bool FlightPlan::loadXmlFormat(const SGPath& path)
1153{
1154 SGPropertyNode_ptr routeData(new SGPropertyNode);
1155
1156 try {
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());
1161 return false;
1162 }
1163
1164 if (routeData.valid())
1165 {
1166 try {
1167 int version = routeData->getIntValue("version", 1);
1168 bool ok = false;
1169 if (version == 1) {
1170 ok = loadVersion1XMLRoute(routeData);
1171 } else if (version == 2) {
1172 ok = loadVersion2XMLRoute(routeData);
1173 } else {
1174 SG_LOG(SG_NAVAID, SG_POPUP, "Unsupported flight plan version " << version << " loading " << path);
1175 }
1176
1177 return ok;
1178 } catch (sg_exception& e) {
1179 SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load flight-plan '" << e.getOrigin()
1180 << "'. " << e.getMessage());
1181 }
1182 }
1183
1184 return false;
1185}
1186
1187void FlightPlan::loadXMLRouteHeader(SGPropertyNode_ptr routeData)
1188{
1189 // general info
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));
1194
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));
1199
1200 _callsign = routeData->getStringValue("callsign");
1201 _remarks = routeData->getStringValue("remarks");
1202 _aircraftType = routeData->getStringValue("aircraft-type");
1203 _estimatedDuration = routeData->getIntValue("estimated-duration-minutes");
1204
1205 if (routeData->hasValue("is-route")) {
1206 if (_isRoute != routeData->getBoolValue("is-route")) {
1207 // this is actually okay, we will expand any VIAs
1208 SG_LOG(SG_NAVAID, SG_INFO, "Loading XML marked with 'is-route' into FlightPlan with is-route not set");
1209 }
1210 }
1211
1212 // departure nodes
1213 SGPropertyNode* dep = routeData->getChild("departure");
1214 if (dep) {
1215 string depIdent = dep->getStringValue("airport");
1216 setDeparture((FGAirport*) fgFindAirportID(depIdent));
1217 if (_departure) {
1218 string rwy(dep->getStringValue("runway"));
1219 if (_departure->hasRunwayWithIdent(rwy)) {
1220 setDeparture(_departure->getRunwayByIdent(rwy));
1221 }
1222
1223 if (dep->hasChild("sid")) {
1224 // previously, we would write a transition id for 'SID' if set,
1225 // but this is ambigous. Starting with 2020.2, we only every try
1226 // to parse this value as a SID, and look for a seperate sid_trans
1227 // value
1228 const string trans = dep->getStringValue("sid_trans");
1229 const auto sid = dep->getStringValue("sid");
1230 setSID(_departure->findSIDWithIdent(sid), trans);
1231 }
1232 }
1233 }
1234
1235 // destination
1236 SGPropertyNode* dst = routeData->getChild("destination");
1237 if (dst) {
1238 setDestination((FGAirport*) fgFindAirportID(dst->getStringValue("airport")));
1239 if (_destination) {
1240 string rwy(dst->getStringValue("runway"));
1241 if (_destination->hasRunwayWithIdent(rwy)) {
1242 setDestination(_destination->getRunwayByIdent(rwy));
1243 }
1244
1245 if (dst->hasChild("star")) {
1246 // prior to 2020.2 we would attempt to treat 'star' as a
1247 // transiiton ID, but this is ambiguous. Look for a seperate value now
1248 const auto star = dst->getStringValue("star");
1249 const string trans = dst->getStringValue("star_trans");
1250 setSTAR(_destination->findSTARWithIdent(star), trans);
1251 } // of STAR processing
1252
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);
1257 }
1258 }
1259 }
1260
1261 // alternate
1262 if (routeData->hasChild("alternate")) {
1263 setAlternate((FGAirport*) fgFindAirportID(routeData->getStringValue("alternate")));
1264 }
1265
1266 // cruise
1267 SGPropertyNode* crs = routeData->getChild("cruise");
1268 if (crs) {
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");
1275 }
1276
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");
1283 }
1284 } // of cruise data loading
1285}
1286
1287bool FlightPlan::loadVersion2XMLRoute(SGPropertyNode_ptr routeData)
1288{
1289 if (!routeData->hasChild("route"))
1290 return false;
1291
1292 loadXMLRouteHeader(routeData);
1293
1294 // route nodes
1295 _legs.clear();
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);
1300 if (!wp) {
1301 continue;
1302 }
1303
1304 LegRef l = new Leg{this, wp};
1305 if (wpNode->hasChild("hold-count")) {
1306 l->setHoldCount(wpNode->getIntValue("hold-count"));
1307 }
1308 _legs.push_back(l);
1309 } // of route iteration
1310 }
1311 _waypointsChanged = true;
1312 return true;
1313}
1314
1315bool FlightPlan::loadVersion1XMLRoute(SGPropertyNode_ptr routeData)
1316{
1317 if (!routeData->hasChild("route"))
1318 return false;
1319
1320 loadXMLRouteHeader(routeData);
1321
1322 // _legs nodes
1323 _legs.clear();
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));
1328 _legs.push_back(l);
1329 } // of route iteration
1330 _waypointsChanged = true;
1331 return true;
1332}
1333
1334WayptRef FlightPlan::parseVersion1XMLWaypt(SGPropertyNode* aWP)
1335{
1336 SGGeod lastPos;
1337 if (!_legs.empty()) {
1338 lastPos = _legs.back()->waypoint()->position();
1339 } else if (_departure) {
1340 lastPos = _departure->geod();
1341 }
1342
1343 WayptRef w;
1344 string ident(aWP->getStringValue("ident"));
1345 if (aWP->hasChild("longitude-deg")) {
1346 // explicit longitude/latitude
1347 w = new BasicWaypt(SGGeod::fromDeg(aWP->getDoubleValue("longitude-deg"),
1348 aWP->getDoubleValue("latitude-deg")), ident, this);
1349
1350 } else {
1351 string nid = aWP->getStringValue("navid", ident.c_str());
1353 SGGeod pos;
1354
1355 if (p) {
1356 pos = p->geod();
1357 } else {
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"));
1361 }
1362
1363 if (aWP->hasChild("offset-nm") && aWP->hasChild("offset-radial")) {
1364 double radialDeg = aWP->getDoubleValue("offset-radial");
1365 // convert magnetic radial to a true radial!
1366 radialDeg += magvarDegAt(pos);
1367 double offsetNm = aWP->getDoubleValue("offset-nm");
1368 double az2;
1369 SGGeodesy::direct(pos, radialDeg, offsetNm * SG_NM_TO_METER, pos, az2);
1370 }
1371
1372 w = new BasicWaypt(pos, ident, this);
1373 }
1374
1375 double altFt = aWP->getDoubleValue("altitude-ft", -9999.9);
1376 if (altFt > -9990.0) {
1377 w->setAltitude(altFt, RESTRICT_AT, ALTITUDE_FEET);
1378 }
1379
1380 return w;
1381}
1382
1384bool FlightPlan::loadPlainTextFormat(const SGPath& path)
1385{
1386 try {
1387 sg_gzifstream in(path);
1388 if (!in.is_open()) {
1389 throw sg_io_exception("Cannot open file for reading.");
1390 }
1391
1392 _legs.clear();
1393 while (!in.eof()) {
1394 string line;
1395 getline(in, line, '\n');
1396 // trim CR from end of line, if found
1397 if (line[line.size() - 1] == '\r') {
1398 line.erase(line.size() - 1, 1);
1399 }
1400
1401 line = simgear::strutils::strip(line);
1402 if (line.empty() || (line[0] == '#')) {
1403 continue; // ignore empty/comment lines
1404 }
1405
1406 // prevent Sentry error 'FLIGHTGEAR-J6', when we try loading XML
1407 // data here
1408 if (simgear::strutils::starts_with(line, "<?xml")) {
1409 return false;
1410 }
1411
1412 SGGeod vicinity;
1413 if (!_legs.empty()) {
1414 vicinity = _legs.back()->waypoint()->position();
1415 }
1416 WayptRef w = waypointFromString(line, vicinity);
1417 if (!w) {
1418 SG_LOG(SG_NAVAID, SG_ALERT, "Failed to create waypoint from '" << line << "' in " << path);
1419 _legs.clear();
1420 return false;
1421 }
1422
1423 _legs.push_back(LegRef{new Leg(this, w)});
1424 } // of line iteration
1425 } catch (sg_exception& e) {
1426 SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load route from: '" << path << "'. " << e.getMessage());
1427 _legs.clear();
1428 return false;
1429 }
1430
1431 return true;
1432}
1433
1434double FlightPlan::magvarDegAt(const SGGeod& pos) const
1435{
1436 double jd = globals->get_time_params()->getJD();
1437 return sgGetMagVar(pos, jd) * SG_RADIANS_TO_DEGREES;
1438}
1439
1441{
1442 if (aIndex < 0 || aIndex >= numLegs()) { // appending, not inserting
1443 const int n = numLegs();
1444 if (n > 0) {
1445 // if we have at least one existing leg, use its position
1446 // for the search vicinity
1447 return pointAlongRoute(n - 1, 0.0);
1448 }
1449
1450 return SGGeod::invalid();
1451 }
1452
1453 // if we're somewhere in the middle of the route compute a search
1454 // vicinity halfway between the previous waypoint and the one we are
1455 // inserting at, i.e the middle of the leg.
1456 // if we're at the beginning, just use zero of course.
1457 const double normOffset = (aIndex > 0) ? -0.5 : 0.0;
1458 return pointAlongRouteNorm(aIndex, normOffset);
1459}
1460
1461WayptRef FlightPlan::waypointFromString(const string& tgt, const SGGeod& vicinity)
1462{
1463 SGGeod basePosition = vicinity;
1464 if (!vicinity.isValid()) {
1465 // compute basePosition using some heuristics
1466 if (_legs.empty()) {
1467 // route is empty, use departure position / aircraft position
1468 basePosition = _departure ? _departure->geod() : globals->get_aircraft_position();
1469 } else {
1470 basePosition = _legs.back()->waypoint()->position();
1471 }
1472 }
1473
1474 return Waypt::createFromString(this, tgt, basePosition);
1475}
1476
1477bool FlightPlan::expandVias()
1478{
1479 // must be called with the delegats locked, so that
1480 // waypointsChanged can be set on finish
1481
1482 assert(_delegateLock > 0);
1483 bool didChangeAny = false;
1484
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());
1489 WayptVec wps = via->expandToWaypoints(preceeding);
1490
1491 // delete the VIA leg
1492 auto it = _legs.begin() + i;
1493 LegRef l = *it;
1494 _legs.erase(it);
1495
1496 // create new legs and insert
1497 it = _legs.begin() + i;
1498
1499 LegVec newLegs;
1500 for (WayptRef wp : wps) {
1501 newLegs.push_back(LegRef{new Leg(this, wp)});
1502 }
1503
1504 didChangeAny = true;
1505 _legs.insert(it, newLegs.begin(), newLegs.end());
1506 } else {
1507 ++i; // normal case, no expansion
1508 }
1509 }
1510
1511 return didChangeAny;
1512}
1513
1515{
1516 if (_isRoute) {
1517 // no allowed, clone and make the non-route FP active
1518 SG_LOG(SG_NAVAID, SG_DEV_ALERT, "tried to activate an is-route FlightPlan");
1519 return;
1520 }
1521
1522 auto routeManager = globals->get_subsystem<FGRouteMgr>();
1523 if (routeManager) {
1524 if (routeManager->flightPlan() != this) {
1525 SG_LOG(SG_NAVAID, SG_DEBUG, "setting new flight-plan on route-manager");
1526 routeManager->setFlightPlan(this);
1527 }
1528 }
1529
1530 lockDelegates();
1531
1532 _currentIndex = 0;
1533 _currentWaypointChanged = true;
1534 _waypointsChanged = expandVias();
1535
1536 for (auto d : _delegates) {
1537 d->activated();
1538 }
1539
1540 unlockDelegates();
1541}
1542
1543FlightPlan::Leg::Leg(FlightPlan* owner, WayptRef wpt) :
1544 _parent(owner),
1545 _waypt(wpt)
1546{
1547 if (!wpt.valid()) {
1548 throw sg_exception("can't create FlightPlan::Leg without underlying waypoint");
1549 }
1550}
1551
1552FlightPlan::Leg* FlightPlan::Leg::cloneFor(FlightPlan* owner) const
1553{
1554 Leg* c = new Leg(owner, _waypt);
1555// clone local data
1556 c->_speed = _speed;
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;
1563
1564 return c;
1565}
1566
1568{
1569 if ((index() + 1) >= _parent->_legs.size())
1570 return nullptr;
1571
1572 return _parent->legAtIndex(index() + 1);
1573}
1574
1575unsigned int FlightPlan::Leg::index() const
1576{
1577 return _parent->findLegIndex(this);
1578}
1579
1581{
1582 if (_altRestrict != RESTRICT_NONE) {
1583 return convertAltitudeUnits(_altitudeUnits, aUnits, _altitude);
1584 }
1585
1586 return _waypt->altitude(aUnits);
1587}
1588
1590{
1591 return static_cast<int>(altitude(ALTITUDE_FEET));
1592}
1593
1595{
1596 if (_speedRestrict != RESTRICT_NONE) {
1597 return convertSpeedUnits(_speedUnits, units, altitudeFt(), _speed);
1598 }
1599
1600 return _waypt->speed(units);
1601}
1602
1604{
1605 return static_cast<int>(speed(SPEED_KNOTS));
1606}
1607
1609{
1610 return speed(SPEED_MACH);
1611}
1612
1614{
1615 if (_altRestrict != RESTRICT_NONE) {
1616 return _altRestrict;
1617 }
1618
1619 return _waypt->altitudeRestriction();
1620}
1621
1623{
1624 if (_speedRestrict != RESTRICT_NONE) {
1625 return _speedRestrict;
1626 }
1627
1628 return _waypt->speedRestriction();
1629}
1630
1632{
1633 _speedRestrict = ty;
1634 if (aUnit == DEFAULT_UNITS) {
1635 if (isMachRestrict(ty)) {
1636 aUnit = SPEED_MACH;
1637 } else {
1638 // TODO: check for system in metric?
1639 aUnit = SPEED_KNOTS;
1640 }
1641 }
1642 _speedUnits = aUnit;
1643 _speed = speed;
1644}
1645
1647{
1648 _altRestrict = ty;
1649 if (aUnit == DEFAULT_UNITS) {
1650 aUnit = ALTITUDE_FEET;
1651 }
1652 _altitudeUnits = aUnit;
1653 _altitude = alt;
1654}
1655
1657{
1658 return _courseDeg;
1659}
1660
1662{
1663 return _pathDistance;
1664}
1665
1667{
1668 return _distanceAlongPath;
1669}
1670
1671
1673{
1674 const auto wty = _waypt->type();
1675 if (wty == "hold") {
1676 return true;
1677 }
1678
1679 if ((wty != "basic") && (wty != "navaid")) {
1680 SG_LOG(SG_INSTR, SG_WARN, "convertWaypointToHold: cannot convert waypt " << index() << " " << _waypt->ident() << " to a hold");
1681 return false;
1682 }
1683
1684 auto hold = new Hold(_waypt->position(), _waypt->ident(), const_cast<FlightPlan*>(_parent));
1685
1686 // default to a 1 minute hold with the radial being our arrival radial
1687 hold->setHoldTime(60.0);
1688 hold->setHoldRadial(_courseDeg);
1689 _waypt = hold; // we drop our reference to the old waypoint
1690
1692
1693 return true;
1694}
1695
1697{
1698 if (count == 0) {
1699 _holdCount = count;
1700 return true;
1701 }
1702
1703 if (!convertWaypointToHold()) {
1704 return false;
1705 }
1706
1707 _holdCount = count;
1709 return true;
1710}
1711
1713 {
1714 auto fp = owner();
1715 fp->lockDelegates();
1716 fp->_waypointsChanged = true;
1717 fp->unlockDelegates();
1718 }
1719
1721{
1722 return _holdCount;
1723}
1724
1725void FlightPlan::Leg::writeToProperties(SGPropertyNode* aProp) const
1726{
1727 if (_speedRestrict != RESTRICT_NONE) {
1728 aProp->setStringValue("speed-restrict", restrictionToString(_speedRestrict));
1729 if (_speedUnits == SPEED_MACH) {
1730 aProp->setDoubleValue("speed-mach", _speed);
1731 } else if (_speedUnits == SPEED_KPH) {
1732 aProp->setDoubleValue("speed-kph", _speed);
1733 } else {
1734 aProp->setDoubleValue("speed", _speed);
1735 }
1736 }
1737
1738 if (_altRestrict != RESTRICT_NONE) {
1739 aProp->setStringValue("alt-restrict", restrictionToString(_altRestrict));
1740 if (_altitudeUnits == ALTITUDE_FLIGHTLEVEL) {
1741 aProp->setDoubleValue("flight-level", _altitude);
1742 } else if (_altitudeUnits == ALTITUDE_METER) {
1743 aProp->setDoubleValue("altitude-m", _altitude);
1744 } else {
1745 aProp->setDoubleValue("altitude-ft", _altitude);
1746 }
1747 }
1748
1749 if (_holdCount > 0) {
1750 aProp->setDoubleValue("hold-count", _holdCount);
1751 }
1752}
1753
1754
1755void FlightPlan::rebuildLegData()
1756{
1757 _totalDistance = 0.0;
1758 double totalDistanceIncludingMissed = 0.0;
1759 RoutePath path(this);
1760
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;
1764
1765 totalDistanceIncludingMissed += _legs[l]->_pathDistance;
1766 // distance along path includes our own leg distance
1767 _legs[l]->_distanceAlongPath = totalDistanceIncludingMissed;
1768
1769 // omit missed-approach waypoints from total distance calculation
1770 if (!_legs[l]->waypoint()->flag(WPT_MISS)) {
1771 _totalDistance += _legs[l]->_pathDistance;
1772 }
1773} // of legs iteration
1774
1775}
1776
1777SGGeod FlightPlan::pointAlongRoute(int aIndex, double aOffsetNm) const
1778{
1779 RoutePath rp(this);
1780 return rp.positionForDistanceFrom(aIndex, aOffsetNm * SG_NM_TO_METER);
1781}
1782
1783SGGeod FlightPlan::pointAlongRouteNorm(int aIndex, double aOffsetNorm) const
1784{
1785 RoutePath rp(this);
1786 if (fabs(aOffsetNorm) > 1.0) {
1787 SG_LOG(SG_AUTOPILOT, SG_ALERT, "FlightPlan::pointAlongRouteNorm: called with invalid arg:" << aOffsetNorm);
1788 return rp.positionForIndex(aIndex);
1789 }
1790
1791 const bool forwards = (aOffsetNorm >= 0.0);
1792 double d = 0.0;
1793 if (forwards) {
1794 d = rp.distanceForIndex(aIndex + 1);
1795 } else {
1796 d = rp.distanceForIndex(aIndex);
1797 }
1798
1799 // in degenerate cases, just use basic position of index
1800 if (d <= 0.0) {
1801 return rp.positionForIndex(aIndex);
1802 }
1803
1804 return rp.positionForDistanceFrom(aIndex, d * aOffsetNorm);
1805}
1806
1807void FlightPlan::lockDelegates()
1808{
1809 if (_delegateLock == 0) {
1810 assert(!_departureChanged && !_arrivalChanged &&
1811 !_waypointsChanged && !_currentWaypointChanged);
1812 }
1813
1814 ++_delegateLock;
1815 if (_delegateLock > 10) {
1816 SG_LOG(SG_GENERAL, SG_ALERT, "hmmm");
1817 }
1818}
1819
1820void FlightPlan::unlockDelegates()
1821{
1822 assert(_delegateLock > 0);
1823 if (_delegateLock > 1) {
1824 --_delegateLock;
1825 return;
1826 }
1827
1828 if (_didLoadFP) {
1829 _didLoadFP = false;
1830 for (auto d : _delegates) {
1831 d->loaded();
1832 }
1833 }
1834
1835 if (_departureChanged) {
1836 _departureChanged = false;
1837 for (auto d : _delegates) {
1838 d->departureChanged();
1839 }
1840 }
1841
1842 if (_arrivalChanged) {
1843 _arrivalChanged = false;
1844 for (auto d : _delegates) {
1845 d->arrivalChanged();
1846 }
1847 }
1848
1849 if (_cruiseDataChanged) {
1850 _cruiseDataChanged = false;
1851 for (auto d : _delegates) {
1852 d->cruiseChanged();
1853 }
1854 }
1855
1856 if (_waypointsChanged) {
1857 _waypointsChanged = false;
1858 rebuildLegData();
1859 for (auto d : _delegates) {
1860 d->waypointsChanged();
1861 }
1862 }
1863
1864 if (_currentWaypointChanged) {
1865 _currentWaypointChanged = false;
1866 for (auto d : _delegates) {
1867 d->currentWaypointChanged();
1868 }
1869 }
1870
1871 --_delegateLock;
1872}
1873
1875{
1876 auto it = std::find(static_delegateFactories.begin(), static_delegateFactories.end(), df);
1877 if (it != static_delegateFactories.end()) {
1878 throw sg_exception("duplicate delegate factory registration");
1879 }
1880
1881 static_delegateFactories.push_back(df);
1882}
1883
1885{
1886 auto it = std::find(static_delegateFactories.begin(), static_delegateFactories.end(), df);
1887 if (it == static_delegateFactories.end()) {
1888 return;
1889 }
1890
1891 static_delegateFactories.erase(it);
1892}
1893
1895{
1896 assert(d);
1897#ifndef NDEBUG
1898 auto it = std::find(_delegates.begin(), _delegates.end(), d);
1899#endif
1900 assert(it == _delegates.end());
1901 _delegates.push_back(d);
1902}
1903
1905{
1906 assert(d);
1907 auto it = std::find(_delegates.begin(), _delegates.end(), d);
1908 assert(it != _delegates.end());
1909 _delegates.erase(it);
1910}
1911
1912void FlightPlan::notifyCleared()
1913{
1914 for (auto d : _delegates) {
1915 d->cleared();
1916 }
1917}
1918
1922
1926
1928{
1929 _followLegTrackToFix = tf;
1930}
1931
1933{
1934 return _followLegTrackToFix;
1935}
1936
1938{
1939 _maxFlyByTurnAngle = deg;
1940}
1941
1943{
1944 return _maxFlyByTurnAngle;
1945}
1946
1948{
1949 std::string r;
1950 r.push_back(_aircraftCategory);
1951 return r;
1952}
1953
1954void FlightPlan::setIcaoAircraftCategory(const std::string& cat)
1955{
1956 if (cat.empty()) {
1957 throw sg_range_exception("Invalid ICAO aircraft category:", cat);
1958 }
1959
1960 if ((cat[0] < ICAO_AIRCRAFT_CATEGORY_A) || (cat[0] > ICAO_AIRCRAFT_CATEGORY_E)) {
1961 throw sg_range_exception("Invalid ICAO aircraft category:", cat);
1962 }
1963
1964 _aircraftCategory = cat[0];
1965}
1966
1968{
1969 _aircraftType = ty;
1970}
1971
1972bool FlightPlan::parseICAOLatLon(const std::string& s, SGGeod& p)
1973{
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);
1980 return true;
1981 } else if (s.size() == 11) {
1982 // problem here is we have minutes, not decimal degrees
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);
1989
1990 if (s[4] == 'S') latDegrees = -latDegrees;
1991 if (s[10] == 'W') lonDegrees = -lonDegrees;
1992 p = SGGeod::fromDeg(lonDegrees, latDegrees);
1993 return true;
1994 }
1995
1996 return false;
1997}
1998
1999bool FlightPlan::parseICAORouteString(const std::string& routeData)
2000{
2001 auto tokens = simgear::strutils::split(routeData);
2002 if (tokens.empty())
2003 return false;
2004
2005 std::string tk;
2006 std::string nextToken;
2007
2008 FGAirportRef firstICAO = FGAirport::findByIdent(tokens.front());
2009 unsigned int i = 0;
2010
2011 if (firstICAO) {
2012 // route string starts with an airport, let's see if it matches
2013 // our departure airport
2014 if (!_departure) {
2015 setDeparture(firstICAO);
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());
2018 return false;
2019 }
2020 ++i; // either way, skip the ICAO token now
2021 }
2022
2023 WayptVec enroute;
2024 SGGeod currentPos;
2025 if (_departure)
2026 currentPos = _departure->geod();
2027
2028 for (; i < tokens.size(); ++i) {
2029 tk = tokens.at(i);
2030 // update current position for next search
2031 if (!enroute.empty()) {
2032 currentPos = enroute.back()->position();
2033 }
2034
2035 if (isdigit(tk.front())) {
2036 // might be a lat lon (but some airway idents also start with a digit...)
2037 SGGeod geod;
2038 bool ok = parseICAOLatLon(tk, geod);
2039 if (ok) {
2040 enroute.push_back(new BasicWaypt(geod, tk, this));
2041 continue;
2042 }
2043 }
2044
2045 nextToken = (i < (tokens.size() - 1)) ? tokens.at(i+1) : std::string();
2046
2047 if (tk == "DCT") {
2048 if (nextToken.empty()) {
2049 SG_LOG(SG_AUTOPILOT, SG_WARN, "ICAO route DIRECT segment missing waypoint");
2050 return false;
2051 }
2052
2053 FGPositionedRef wpt = FGPositioned::findClosestWithIdent(nextToken, currentPos);
2054 if (!wpt) {
2055 SG_LOG(SG_AUTOPILOT, SG_WARN, "ICAO route waypoint not found:" << nextToken);
2056 return false;
2057 }
2058 enroute.push_back(new NavaidWaypoint(wpt, this));
2059 ++i;
2060 } else if (tk == "STAR") {
2061 // look for a STAR based on the preceeding transition point
2062 auto starTrans = _destination->selectSTARByEnrouteTransition(enroute.back()->source());
2063 if (!starTrans) {
2064 SG_LOG(SG_AUTOPILOT, SG_WARN, "ICAO route couldn't find STAR transitioning from " <<
2065 enroute.back()->source()->ident());
2066 } else {
2067 setSTAR(starTrans);
2068 }
2069 } else if (tk == "SID") {
2070 // select a SID based on the next point
2071 FGPositionedRef wpt = FGPositioned::findClosestWithIdent(nextToken, currentPos);
2072 auto sidTrans = _departure->selectSIDByEnrouteTransition(wpt);
2073 if (!sidTrans) {
2074 SG_LOG(SG_AUTOPILOT, SG_WARN, "ICAO route couldn't find SID transitioning to " << nextToken);
2075 } else {
2076 setSID(sidTrans);
2077 }
2078 ++i;
2079 } else {
2080 if (nextToken.empty()) {
2081 SG_LOG(SG_AUTOPILOT, SG_WARN, "ICAO route airway segment missing transition:" << tk);
2082 return false;
2083 }
2084
2085 auto nav = Airway::highLevel()->findNodeByIdent(nextToken, currentPos);
2086 if (!nav)
2087 nav = Airway::lowLevel()->findNodeByIdent(nextToken, currentPos);
2088 if (!nav) {
2089 SG_LOG(SG_AUTOPILOT, SG_WARN, "ICAO route waypoint not found:" << nextToken);
2090 return false;
2091 }
2092
2093 WayptRef toNav = new NavaidWaypoint(nav, nullptr); // temp waypoint for lookup
2094 WayptRef previous;
2095 if (enroute.empty()) {
2096 if (_sid) {
2097 if (!_sidTransition.empty()) {
2098 previous = _sid->findTransitionByName(_sidTransition)->enroute();
2099 } else {
2100 // final wayypoint of common section
2101 previous = _sid->common().back();
2102 }
2103 } else {
2104 SG_LOG(SG_AUTOPILOT, SG_WARN, "initial airway needs anchor point from SID:" << tk);
2105 return false;
2106 }
2107 } else {
2108 previous = enroute.back();
2109 }
2110
2111 AirwayRef way = Airway::findByIdentAndVia(tk, enroute.back(), toNav);
2112 if (way) {
2113 enroute.push_back(new Via(this, way, nav));
2114 ++i;
2115 } else {
2116 SG_LOG(SG_AUTOPILOT, SG_WARN, "ICAO route unknown airway:" << tk);
2117 return false;
2118 }
2119 }
2120 } // of token iteration
2121
2122 lockDelegates();
2123 _waypointsChanged = true;
2124
2125 SG_LOG(SG_AUTOPILOT, SG_INFO, "adding waypoints from string");
2126 // rebuild legs from waypoints we created
2127 for (auto l : _legs) {
2128 delete l;
2129 }
2130 _legs.clear();
2131 insertWayptsAtIndex(enroute, 0);
2132
2133 unlockDelegates();
2134
2135 SG_LOG(SG_AUTOPILOT, SG_INFO, "legs now:" << numLegs());
2136
2137 return true;
2138}
2139
2141{
2142 std::string result;
2143 if (!_sidTransition.empty())
2144 result += _sidTransition + " ";
2145
2146 for (auto l : _legs) {
2147 const auto wpt = l->waypoint();
2148
2149 AirwayRef nextLegAirway;
2150 if (l->nextLeg() && l->nextLeg()->waypoint()->flag(WPT_VIA)) {
2151 nextLegAirway = static_cast<Airway*>(l->nextLeg()->waypoint()->owner());
2152 }
2153
2154 if (wpt->flag(WPT_GENERATED)) {
2155 if (wpt->flag(WPT_VIA)) {
2156 AirwayRef awy = static_cast<Airway*>(wpt->owner());
2157 if (awy == nextLegAirway) {
2158 // skipepd entirely, next leg will output the airway
2159 continue;
2160 }
2161
2162 result += awy->ident() + " ";
2163 }
2164 } else if (wpt->type() == "navaid") {
2165 // technically we need DCT unless both ends of the leg are
2166 // defined geographically
2167 result += "DCT ";
2168 }
2169 result += wpt->icaoDescription() + " ";
2170 }
2171
2172 if (!_starTransition.empty())
2173 result += _starTransition;
2174
2175 return result;
2176}
2177
2179{
2180 _flightRules = rules;
2181}
2182
2184{
2185 return _flightRules;
2186}
2187
2188void FlightPlan::setCallsign(const std::string& callsign)
2189{
2190 _callsign = callsign;
2191}
2192
2193void FlightPlan::setRemarks(const std::string& remarks)
2194{
2195 _remarks = remarks;
2196}
2197
2199{
2200 _flightType = type;
2201}
2202
2204{
2205 return _flightType;
2206}
2207
2209{
2210 lockDelegates();
2211 _cruiseDataChanged = true;
2212 _cruiseAirspeedKnots = kts;
2213 _cruiseAirspeedMach = 0.0;
2214 _cruiseAirspeedKph = 0;
2215 unlockDelegates();
2216}
2217
2219{
2220 return _cruiseAirspeedKnots;
2221}
2222
2224{
2225 lockDelegates();
2226 _cruiseDataChanged = true;
2227 _cruiseAirspeedKnots = 0;
2228 _cruiseAirspeedMach = mach;
2229 _cruiseAirspeedKph = 0;
2230 unlockDelegates();
2231}
2232
2234{
2235 return _cruiseAirspeedMach;
2236}
2237
2239{
2240 lockDelegates();
2241 _cruiseDataChanged = true;
2242 _cruiseAirspeedKnots = 0;
2243 _cruiseAirspeedMach = 0.0;
2244 _cruiseAirspeedKph = kph;
2245 unlockDelegates();
2246}
2247
2249{
2250 return _cruiseAirspeedKph;
2251}
2252
2254{
2255 lockDelegates();
2256 _cruiseDataChanged = true;
2257 _cruiseAltitudeFt = 0;
2258 _cruiseAltitudeM = 0;
2259 _cruiseFlightLevel = flightLevel;
2260 unlockDelegates();
2261}
2262
2264{
2265 return _cruiseFlightLevel;
2266}
2267
2269{
2270 lockDelegates();
2271 _cruiseDataChanged = true;
2272 _cruiseAltitudeFt = altFt;
2273 _cruiseAltitudeM = 0;
2274 _cruiseFlightLevel = 0;
2275 unlockDelegates();
2276}
2277
2279{
2280 return _cruiseAltitudeFt;
2281}
2282
2284{
2285 lockDelegates();
2286 _cruiseDataChanged = true;
2287 _cruiseAltitudeFt = 0;
2288 _cruiseAltitudeM = altM;
2289 _cruiseFlightLevel = 0;
2290 unlockDelegates();
2291}
2292
2294{
2295 return _cruiseAltitudeM;
2296}
2297
2299{
2300 std::for_each(_legs.begin(), _legs.end(), lv);
2301}
2302
2303
2305{
2306 const auto numLegs = _legs.size();
2307 for (size_t i = 0; i < numLegs; ++i) {
2308 if (!(_legs.at(i)->waypoint()->flags() & WPT_DEPARTURE))
2309 return static_cast<int>(i);
2310 }
2311
2312 // all waypoints are marked as departure
2313 return -1;
2314}
2315
2317{
2318 const auto numLegs = _legs.size();
2319 for (size_t i = 0; i < numLegs; ++i) {
2320 if (_legs.at(i)->waypoint()->flags() & WPT_ARRIVAL)
2321 return static_cast<int>(i);
2322 }
2323
2324 // no waypoints are marked as arrival
2325 return -1;
2326}
2327
2329{
2330 const auto numLegs = _legs.size();
2331 for (size_t i = 0; i < numLegs; ++i) {
2332 if (_legs.at(i)->waypoint()->flags() & WPT_APPROACH)
2333 return static_cast<int>(i);
2334 }
2335
2336 // no waypoints are marked as arrival
2337 return -1;
2338}
2339
2341{
2342 if (!_destinationRunway)
2343 return -1;
2344
2345 // work backwards in case the departure and destination match
2346 // this way we'll find the one we want
2347 for (int i = numLegs() - 1; i >= 0; i--) {
2348 if (_legs.at(i)->waypoint()->source() == _destinationRunway) {
2349 return i;
2350 }
2351 }
2352
2353 return -1;
2354}
2355
2357{
2358 // mimic old behaviour before destroyFlightPlanDelegate was added
2359 SG_UNUSED(fp);
2360 delete d;
2361}
2362
2363
2364} // of namespace flightgear
double altitude
Definition ADA.cxx:46
#define p(x)
#define i(x)
const FGAirport * fgFindAirportID(const std::string &id)
Definition airport.cxx:523
SGSharedPtr< FGAirport > FGAirportRef
SGSharedPtr< FGPositioned > FGPositionedRef
Definition airways.hxx:30
static FGAirportRef findByIdent(const std::string &aIdent)
Helper to look up an FGAirport instance by unique ident.
Definition airport.cxx:489
static FGPositionedRef findClosestWithIdent(const std::string &aIdent, const SGGeod &aPos, Filter *aFilter=NULL)
Top level route manager class.
Definition route_mgr.hxx:27
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
Definition airways.cxx:576
static Network * lowLevel()
Definition airways.cxx:93
static Network * highLevel()
Definition airways.cxx:105
std::string ident() const override
Definition airways.hxx:52
static AirwayRef findByIdentAndVia(const std::string &aIdent, const WayptRef &from, const WayptRef &to)
Find the airway based on its ident.
Definition airways.cxx:356
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
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.
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
void setCruiseSpeedMach(double mach)
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
LegRef nextLeg() 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
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
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.
Definition waypoint.hxx:63
virtual ProcedureType type() const =0
virtual std::string ident() const
Definition procedure.hxx:53
Encapsulate a transition segment.
Definition procedure.hxx:70
Procedure * parent() const
Definition procedure.hxx:76
WayptVec expandToWaypoints(WayptRef aPreceeding) const
Definition waypoint.cxx:625
Abstract base class for waypoints (and things that are treated similarly by navigation systems).
Definition route.hxx:105
virtual unsigned int flags() const
Definition route.hxx:156
static WayptRef createFromString(RouteBase *aOwner, const std::string &s, const SGGeod &vicinity)
Create a waypoint from the route manager's standard string format:
Definition route.cxx:491
virtual bool flag(WayptFlag aFlag) const
Test if the specified flag is set for this element.
Definition route.cxx:204
FGGlobals * globals
Definition globals.cxx:142
std::vector< std::string > string_list
Definition globals.hxx:36
FlightPlan.hxx - defines a full flight-plan object, including departure, cruise, arrival information ...
Definition Addon.cxx:53
SGSharedPtr< FlightPlan > FlightPlanRef
double convertAltitudeUnits(RouteUnits aSrc, RouteUnits aDest, double aValue)
Definition route.cxx:164
@ SPEED_KPH
Definition route.hxx:89
@ ALTITUDE_FLIGHTLEVEL
Definition route.hxx:86
@ DEFAULT_UNITS
Definition route.hxx:83
@ ALTITUDE_METER
Definition route.hxx:85
@ SPEED_KNOTS
Definition route.hxx:87
@ SPEED_MACH
Definition route.hxx:88
@ ALTITUDE_FEET
Definition route.hxx:84
std::vector< FlightPlan::DelegateFactoryRef > FPDelegateFactoryVec
static FPDelegateFactoryVec static_delegateFactories
SGSharedPtr< FGPositioned > FGPositionedRef
Definition airways.cxx:49
static WayptVec copyWaypointsExpandingVias(WayptRef preceeding, const WayptVec &wps)
SGSharedPtr< Waypt > WayptRef
SGSharedPtr< Airway > AirwayRef
Definition airways.hxx:40
const char * name
double convertSpeedUnits(RouteUnits aSrc, RouteUnits aDest, double aAltitudeFt, double aValue)
Definition route.cxx:158
const char * restrictionToString(RouteRestriction aRestrict)
Definition route.cxx:366
const char ICAO_AIRCRAFT_CATEGORY_E
bool isMachRestrict(RouteRestriction rr)
Definition route.cxx:59
static bool anyWaypointsWithFlag(FlightPlan *plan, WayptFlag flag)
@ WPT_DEPARTURE
Definition route.hxx:54
@ WPT_MISS
segment is part of missed approach
Definition route.hxx:46
@ WPT_APPROACH
Definition route.hxx:60
@ WPT_ARRIVAL
Definition route.hxx:55
@ WPT_GENERATED
waypoint was created automatically (not manually entered/loaded) for example waypoints from airway ro...
Definition route.hxx:52
@ WPT_VIA
waypoint prodcued by expanding a VIA segment
Definition route.hxx:63
const char ICAO_AIRCRAFT_CATEGORY_C
const char ICAO_AIRCRAFT_CATEGORY_A
std::vector< WayptRef > WayptVec
RouteRestriction
Definition route.hxx:70
@ RESTRICT_NONE
Definition route.hxx:71
@ RESTRICT_AT
Definition route.hxx:72
static double atof(const string &str)
Definition options.cxx:107