]> git.mxchange.org Git - flightgear.git/blob - src/Navaids/FlightPlan.cxx
Fix distance-along-path computation
[flightgear.git] / src / Navaids / FlightPlan.cxx
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 #ifdef HAVE_CONFIG_H
22 # include "config.h"
23 #endif
24
25 #include "FlightPlan.hxx"
26
27 // std
28 #include <map>
29 #include <fstream>
30 #include <cassert>
31
32 // Boost
33 #include <boost/algorithm/string/case_conv.hpp>
34 #include <boost/algorithm/string.hpp>
35 #include <boost/foreach.hpp>
36
37 // SimGear
38 #include <simgear/structure/exception.hxx>
39 #include <simgear/misc/sg_path.hxx>
40 #include <simgear/magvar/magvar.hxx>
41 #include <simgear/timing/sg_time.hxx>
42 #include <simgear/misc/sgstream.hxx>
43 #include <simgear/misc/strutils.hxx>
44 #include <simgear/props/props_io.hxx>
45 #include <simgear/xml/easyxml.hxx>
46
47 // FlightGear
48 #include <Main/globals.hxx>
49 #include "Main/fg_props.hxx"
50 #include <Navaids/procedure.hxx>
51 #include <Navaids/waypoint.hxx>
52 #include <Navaids/routePath.hxx>
53
54 using std::string;
55 using std::vector;
56 using std::endl;
57 using std::fstream;
58
59 namespace flightgear {
60
61 typedef std::vector<FlightPlan::DelegateFactory*> FPDelegateFactoryVec;
62 static FPDelegateFactoryVec static_delegateFactories;
63   
64 FlightPlan::FlightPlan() :
65   _delegateLock(0),
66   _currentIndex(-1),
67   _departureRunway(NULL),
68   _destinationRunway(NULL),
69   _sid(NULL),
70   _star(NULL),
71   _approach(NULL),
72   _totalDistance(0.0),
73   _delegate(NULL)
74 {
75   _departureChanged = _arrivalChanged = _waypointsChanged = _currentWaypointChanged = false;
76   
77   BOOST_FOREACH(DelegateFactory* factory, static_delegateFactories) {
78     Delegate* d = factory->createFlightPlanDelegate(this);
79     if (d) { // factory might not always create a delegate
80       d->_deleteWithPlan = true;
81       addDelegate(d);
82     }
83   }
84 }
85   
86 FlightPlan::~FlightPlan()
87 {
88 // delete all delegates which we own.
89   Delegate* d = _delegate;
90   while (d) {
91     Delegate* cur = d;
92     d = d->_inner;
93     if (cur->_deleteWithPlan) {
94       delete cur;
95     }
96   }
97     
98 // delete legs
99     BOOST_FOREACH(Leg* l, _legs) {
100         delete l;
101     }
102 }
103   
104 FlightPlan* FlightPlan::clone(const string& newIdent) const
105 {
106   FlightPlan* c = new FlightPlan();
107   c->_ident = newIdent.empty() ? _ident : newIdent;
108   c->lockDelegate();
109   
110 // copy destination / departure data.
111   c->setDeparture(_departure);
112   c->setDeparture(_departureRunway);
113   
114   if (_approach) {
115     c->setApproach(_approach);
116   } else if (_destinationRunway) {
117     c->setDestination(_destinationRunway);
118   } else if (_destination) {
119     c->setDestination(_destination);
120   }
121   
122   c->setSTAR(_star);
123   c->setSID(_sid);
124   
125 // copy legs
126   c->_waypointsChanged = true;
127   for (int l=0; l < numLegs(); ++l) {
128     c->_legs.push_back(_legs[l]->cloneFor(c));
129   }
130   c->unlockDelegate();
131   return c;
132 }
133
134 void FlightPlan::setIdent(const string& s)
135 {
136   _ident = s;
137 }
138   
139 string FlightPlan::ident() const
140 {
141   return _ident;
142 }
143   
144 FlightPlan::Leg* FlightPlan::insertWayptAtIndex(Waypt* aWpt, int aIndex)
145 {
146   if (!aWpt) {
147     return NULL;
148   }
149   
150   WayptVec wps;
151   wps.push_back(aWpt);
152   
153   int index = aIndex;
154   if ((aIndex == -1) || (aIndex > (int) _legs.size())) {
155     index = _legs.size();
156   }
157   
158   insertWayptsAtIndex(wps, index);
159   return legAtIndex(index);
160 }
161   
162 void FlightPlan::insertWayptsAtIndex(const WayptVec& wps, int aIndex)
163 {
164   if (wps.empty()) {
165     return;
166   }
167   
168   int index = aIndex;
169   if ((aIndex == -1) || (aIndex > (int) _legs.size())) {
170     index = _legs.size();
171   }
172   
173   LegVec::iterator it = _legs.begin();
174   it += index;
175   
176   int endIndex = index + wps.size() - 1;
177   if (_currentIndex >= endIndex) {
178     _currentIndex += wps.size();
179   }
180  
181   LegVec newLegs;
182   BOOST_FOREACH(WayptRef wp, wps) {
183     newLegs.push_back(new Leg(this, wp));
184   }
185   
186   lockDelegate();
187   _waypointsChanged = true;
188   _legs.insert(it, newLegs.begin(), newLegs.end());
189   unlockDelegate();
190 }
191
192 void FlightPlan::deleteIndex(int aIndex)
193 {
194   int index = aIndex;
195   if (aIndex < 0) { // negative indices count the the end
196     index = _legs.size() + index;
197   }
198   
199   if ((index < 0) || (index >= numLegs())) {
200     SG_LOG(SG_NAVAID, SG_WARN, "removeAtIndex with invalid index:" << aIndex);
201     return;
202   }
203   
204   lockDelegate();
205   _waypointsChanged = true;
206   
207   LegVec::iterator it = _legs.begin();
208   it += index;
209   Leg* l = *it;
210   _legs.erase(it);
211   delete l;
212   
213   if (_currentIndex == index) {
214     // current waypoint was removed
215     _currentWaypointChanged = true;
216   } else if (_currentIndex > index) {
217     --_currentIndex; // shift current index down if necessary
218   }
219   
220   unlockDelegate();
221 }
222   
223 void FlightPlan::clear()
224 {
225   lockDelegate();
226   _waypointsChanged = true;
227   _currentWaypointChanged = true;
228   _arrivalChanged = true;
229   _departureChanged = true;
230   
231   _currentIndex = -1;
232   BOOST_FOREACH(Leg* l, _legs) {
233     delete l;
234   }
235   _legs.clear();  
236   
237   if (_delegate) {
238     _delegate->runCleared();
239   }
240   unlockDelegate();
241 }
242   
243 class RemoveWithFlag
244 {
245 public:
246   RemoveWithFlag(WayptFlag f) : flag(f), delCount(0) { }
247   
248   int numDeleted() const { return delCount; }
249   
250   bool operator()(FlightPlan::Leg* leg) const
251   {
252     if (leg->waypoint()->flag(flag)) {
253       delete leg;
254       ++delCount;
255       return true;
256     }
257     
258     return false;
259   }
260 private:
261   WayptFlag flag;
262   mutable int delCount;
263 };
264   
265 int FlightPlan::clearWayptsWithFlag(WayptFlag flag)
266 {
267   int count = 0;
268 // first pass, fix up currentIndex
269   for (int i=0; i<_currentIndex; ++i) {
270     Leg* l = _legs[i];
271     if (l->waypoint()->flag(flag)) {
272       ++count;
273     }
274   }
275
276   // test if the current leg will be removed
277   bool currentIsBeingCleared = false;
278   Leg* curLeg = currentLeg();
279   if (curLeg) {
280     currentIsBeingCleared = curLeg->waypoint()->flag(flag);
281   }
282   
283   _currentIndex -= count;
284     
285     // if we're clearing the current waypoint, what shall we do with the
286     // index? there's various options, but safest is to select no waypoint
287     // and let the use re-activate.
288     // http://code.google.com/p/flightgear-bugs/issues/detail?id=1134
289     if (currentIsBeingCleared) {
290         SG_LOG(SG_GENERAL, SG_INFO, "currentIsBeingCleared:" << currentIsBeingCleared);
291         _currentIndex = -1;
292     }
293   
294 // now delete and remove
295   RemoveWithFlag rf(flag);
296   LegVec::iterator it = std::remove_if(_legs.begin(), _legs.end(), rf);
297   if (it == _legs.end()) {
298     return 0; // nothing was cleared, don't fire the delegate
299   }
300   
301   lockDelegate();
302   _waypointsChanged = true;
303   if ((count > 0) || currentIsBeingCleared) {
304     _currentWaypointChanged = true;
305   }
306   
307   _legs.erase(it, _legs.end());
308     
309   if (_legs.empty()) { // maybe all legs were deleted
310     if (_delegate) {
311       _delegate->runCleared();
312     }
313   }
314   
315   unlockDelegate();
316   return rf.numDeleted();
317 }
318   
319 void FlightPlan::setCurrentIndex(int index)
320 {
321   if ((index < -1) || (index >= numLegs())) {
322     throw sg_range_exception("invalid leg index", "FlightPlan::setCurrentIndex");
323   }
324   
325   if (index == _currentIndex) {
326     return;
327   }
328   
329   lockDelegate();
330   _currentIndex = index;
331   _currentWaypointChanged = true;
332   unlockDelegate();
333 }
334     
335 void FlightPlan::finish()
336 {
337     if (_currentIndex == -1) {
338         return;
339     }
340     
341     lockDelegate();
342     _currentIndex = -1;
343     _currentWaypointChanged = true;
344     
345     if (_delegate) {
346         _delegate->runFinished();
347     }
348     
349     unlockDelegate();
350 }
351   
352 int FlightPlan::findWayptIndex(const SGGeod& aPos) const
353 {  
354   for (int i=0; i<numLegs(); ++i) {
355     if (_legs[i]->waypoint()->matches(aPos)) {
356       return i;
357     }
358   }
359   
360   return -1;
361 }
362   
363 int FlightPlan::findWayptIndex(const FGPositionedRef aPos) const
364 {
365   for (int i=0; i<numLegs(); ++i) {
366     if (_legs[i]->waypoint()->source() == aPos) {
367       return i;
368     }
369   }
370   
371   return -1;
372 }
373
374 FlightPlan::Leg* FlightPlan::currentLeg() const
375 {
376   if ((_currentIndex < 0) || (_currentIndex >= numLegs()))
377     return NULL;
378   return legAtIndex(_currentIndex);
379 }
380
381 FlightPlan::Leg* FlightPlan::previousLeg() const
382 {
383   if (_currentIndex <= 0) {
384     return NULL;
385   }
386   
387   return legAtIndex(_currentIndex - 1);
388 }
389
390 FlightPlan::Leg* FlightPlan::nextLeg() const
391 {
392   if ((_currentIndex < 0) || ((_currentIndex + 1) >= numLegs())) {
393     return NULL;
394   }
395   
396   return legAtIndex(_currentIndex + 1);
397 }
398
399 FlightPlan::Leg* FlightPlan::legAtIndex(int index) const
400 {
401   if ((index < 0) || (index >= numLegs())) {
402     throw sg_range_exception("index out of range", "FlightPlan::legAtIndex");
403   }
404   
405   return _legs[index];
406 }
407   
408 int FlightPlan::findLegIndex(const Leg *l) const
409 {
410   for (unsigned int i=0; i<_legs.size(); ++i) {
411     if (_legs[i] == l) {
412       return i;
413     }
414   }
415   
416   return -1;
417 }
418
419 void FlightPlan::setDeparture(FGAirport* apt)
420 {
421   if (apt == _departure) {
422     return;
423   }
424   
425   lockDelegate();
426   _departureChanged = true;
427   _departure = apt;
428   _departureRunway = NULL;
429   setSID((SID*)NULL);
430   unlockDelegate();
431 }
432   
433 void FlightPlan::setDeparture(FGRunway* rwy)
434 {
435   if (_departureRunway == rwy) {
436     return;
437   }
438   
439   lockDelegate();
440   _departureChanged = true;
441
442   _departureRunway = rwy;
443   if (rwy->airport() != _departure) {
444     _departure = rwy->airport();
445     setSID((SID*)NULL);
446   }
447   unlockDelegate();
448 }
449   
450 void FlightPlan::setSID(SID* sid, const std::string& transition)
451 {
452   if (sid == _sid) {
453     return;
454   }
455   
456   lockDelegate();
457   _departureChanged = true;
458   _sid = sid;
459   _sidTransition = transition;
460   unlockDelegate();
461 }
462   
463 void FlightPlan::setSID(Transition* trans)
464 {
465   if (!trans) {
466     setSID((SID*) NULL);
467     return;
468   }
469   
470   if (trans->parent()->type() != PROCEDURE_SID)
471     throw sg_exception("FlightPlan::setSID: transition does not belong to a SID");
472   
473   setSID((SID*) trans->parent(), trans->ident());
474 }
475   
476 Transition* FlightPlan::sidTransition() const
477 {
478   if (!_sid || _sidTransition.empty()) {
479     return NULL;
480   }
481   
482   return _sid->findTransitionByName(_sidTransition);
483 }
484
485 void FlightPlan::setDestination(FGAirport* apt)
486 {
487   if (apt == _destination) {
488     return;
489   }
490   
491   lockDelegate();
492   _arrivalChanged = true;
493   _destination = apt;
494   _destinationRunway = NULL;
495   setSTAR((STAR*)NULL);
496   setApproach(NULL);
497   unlockDelegate();
498 }
499     
500 void FlightPlan::setDestination(FGRunway* rwy)
501 {
502   if (_destinationRunway == rwy) {
503     return;
504   }
505   
506   lockDelegate();
507   _arrivalChanged = true;
508   _destinationRunway = rwy;
509   if (_destination != rwy->airport()) {
510     _destination = rwy->airport();
511     setSTAR((STAR*)NULL);
512   }
513   
514   unlockDelegate();
515 }
516   
517 void FlightPlan::setSTAR(STAR* star, const std::string& transition)
518 {
519   if (_star == star) {
520     return;
521   }
522   
523   lockDelegate();
524   _arrivalChanged = true;
525   _star = star;
526   _starTransition = transition;
527   unlockDelegate();
528 }
529   
530 void FlightPlan::setSTAR(Transition* trans)
531 {
532   if (!trans) {
533     setSTAR((STAR*) NULL);
534     return;
535   }
536   
537   if (trans->parent()->type() != PROCEDURE_STAR)
538     throw sg_exception("FlightPlan::setSTAR: transition does not belong to a STAR");
539   
540   setSTAR((STAR*) trans->parent(), trans->ident());
541 }
542
543 Transition* FlightPlan::starTransition() const
544 {
545   if (!_star || _starTransition.empty()) {
546     return NULL;
547   }
548   
549   return _star->findTransitionByName(_starTransition);
550 }
551   
552 void FlightPlan::setApproach(flightgear::Approach *app)
553 {
554   if (_approach == app) {
555     return;
556   }
557   
558   lockDelegate();
559   _arrivalChanged = true;
560   _approach = app;
561   if (app) {
562     // keep runway + airport in sync
563     if (_destinationRunway != _approach->runway()) {
564       _destinationRunway = _approach->runway();
565     }
566     
567     if (_destination != _destinationRunway->airport()) {
568       _destination = _destinationRunway->airport();
569     }
570   }
571   unlockDelegate();
572 }
573   
574 bool FlightPlan::save(const SGPath& path)
575 {
576   SG_LOG(SG_NAVAID, SG_INFO, "Saving route to " << path.str());
577   try {
578     SGPropertyNode_ptr d(new SGPropertyNode);
579     d->setIntValue("version", 2);
580     
581     if (_departure) {
582       d->setStringValue("departure/airport", _departure->ident());
583       if (_sid) {
584         d->setStringValue("departure/sid", _sid->ident());
585       }
586       
587       if (_departureRunway) {
588         d->setStringValue("departure/runway", _departureRunway->ident());
589       }
590     }
591     
592     if (_destination) {
593       d->setStringValue("destination/airport", _destination->ident());
594       if (_star) {
595         d->setStringValue("destination/star", _star->ident());
596       }
597       
598       if (_approach) {
599         d->setStringValue("destination/approach", _approach->ident());
600       }
601       
602       //d->setStringValue("destination/transition", destination->getStringValue("transition"));
603       
604       if (_destinationRunway) {
605         d->setStringValue("destination/runway", _destinationRunway->ident());
606       }
607     }
608     
609     // route nodes
610     SGPropertyNode* routeNode = d->getChild("route", 0, true);
611     for (unsigned int i=0; i<_legs.size(); ++i) {
612       Waypt* wpt = _legs[i]->waypoint();
613       wpt->saveAsNode(routeNode->getChild("wp", i, true));
614     } // of waypoint iteration
615     writeProperties(path.str(), d, true /* write-all */);
616     return true;
617   } catch (sg_exception& e) {
618     SG_LOG(SG_NAVAID, SG_ALERT, "Failed to save flight-plan '" << path.str() << "'. " << e.getMessage());
619     return false;
620   }
621 }
622
623 bool FlightPlan::load(const SGPath& path)
624 {
625   if (!path.exists())
626   {
627     SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load flight-plan '" << path.str()
628            << "'. The file does not exist.");
629     return false;
630   }
631
632   SG_LOG(SG_NAVAID, SG_INFO, "going to read flight-plan from:" << path.str());
633   
634   bool Status = false;
635   lockDelegate();
636
637   // try different file formats
638   if (loadGpxFormat(path)) // GPX format
639       Status = true;
640   else
641   if (loadXmlFormat(path)) // XML property data
642       Status = true;
643   else
644   if (loadPlainTextFormat(path)) // simple textual list of waypoints
645       Status = true;
646
647   _waypointsChanged = true;
648   unlockDelegate();
649
650   return Status;
651 }
652
653 /** XML loader for GPX file format */
654 class GpxXmlVisitor : public XMLVisitor
655 {
656 public:
657     GpxXmlVisitor(FlightPlan* fp) : _fp(fp), _lat(-9999), _lon(-9999) {}
658
659     virtual void startElement (const char * name, const XMLAttributes &atts);
660     virtual void endElement (const char * name);
661     virtual void data (const char * s, int length);
662
663 private:
664     FlightPlan* _fp;
665     double      _lat, _lon;
666     string      _element;
667     string      _waypoint;
668 };
669
670 void GpxXmlVisitor::startElement(const char * name, const XMLAttributes &atts)
671 {
672     _element = name;
673     if (strcmp(name, "rtept")==0)
674     {
675         _waypoint = "";
676         _lat = _lon = -9999;
677
678         const char* slat = atts.getValue("lat");
679         const char* slon = atts.getValue("lon");
680         if (slat && slon)
681         {
682             _lat = atof(slat);
683             _lon = atof(slon);
684         }
685     }
686 }
687
688 void GpxXmlVisitor::data(const char * s, int length)
689 {
690     // use "name" when given, otherwise use "cmt" (comment) as ID
691     if ((_element == "name")||
692         ((_waypoint == "")&&(_element == "cmt")))
693     {
694         char* buf = (char*) malloc(length+1);
695         memcpy(buf, s, length);
696         buf[length] = 0;
697         _waypoint = buf;
698         free(buf);
699     }
700 }
701
702 void GpxXmlVisitor::endElement(const char * name)
703 {
704     _element = "";
705     if (strcmp(name, "rtept") == 0)
706     {
707         if (_lon > -9990.0)
708         {
709             _fp->insertWayptAtIndex(new BasicWaypt(SGGeod::fromDeg(_lon, _lat), _waypoint.c_str(), NULL), -1);
710         }
711     }
712 }
713
714 /** Load a flightplan in GPX format */
715 bool FlightPlan::loadGpxFormat(const SGPath& path)
716 {
717     if (path.lower_extension() != "gpx")
718     {
719         // not a valid GPX file
720         return false;
721     }
722
723     _legs.clear();
724     GpxXmlVisitor gpxVistor(this);
725     try
726     {
727         readXML(path.str(), gpxVistor);
728     } catch (sg_exception& e)
729     {
730         // XML parsing fails => not a GPX XML file
731         SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load flight-plan in GPX format: '" << e.getOrigin()
732                      << "'. " << e.getMessage());
733         return false;
734     }
735
736     if (numLegs() == 0)
737     {
738         SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load flight-plan in GPX format. No route found.");
739         return false;
740     }
741
742     return true;
743 }
744
745 /** Load a flightplan in FlightGear XML property format */
746 bool FlightPlan::loadXmlFormat(const SGPath& path)
747 {
748   SGPropertyNode_ptr routeData(new SGPropertyNode);
749   try {
750     readProperties(path.str(), routeData);
751   } catch (sg_exception& e) {
752      SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load flight-plan '" << e.getOrigin()
753              << "'. " << e.getMessage());
754     // XML parsing fails => not a property XML file
755     return false;
756   }
757
758   if (routeData.valid())
759   {
760     try {
761       int version = routeData->getIntValue("version", 1);
762       if (version == 1) {
763         loadVersion1XMLRoute(routeData);
764       } else if (version == 2) {
765         loadVersion2XMLRoute(routeData);
766       } else {
767         throw sg_io_exception("unsupported XML route version");
768       }
769       return true;
770     } catch (sg_exception& e) {
771       SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load flight-plan '" << e.getOrigin()
772              << "'. " << e.getMessage());
773     }
774   }
775
776   return false;
777 }
778
779 void FlightPlan::loadXMLRouteHeader(SGPropertyNode_ptr routeData)
780 {
781   // departure nodes
782   SGPropertyNode* dep = routeData->getChild("departure");
783   if (dep) {
784     string depIdent = dep->getStringValue("airport");
785     setDeparture((FGAirport*) fgFindAirportID(depIdent));
786     if (_departure) {
787       string rwy(dep->getStringValue("runway"));
788       if (_departure->hasRunwayWithIdent(rwy)) {
789         setDeparture(_departure->getRunwayByIdent(rwy));
790       }
791     
792       if (dep->hasChild("sid")) {
793         setSID(_departure->findSIDWithIdent(dep->getStringValue("sid")));
794       }
795    // departure->setStringValue("transition", dep->getStringValue("transition"));
796     }
797   }
798   
799   // destination
800   SGPropertyNode* dst = routeData->getChild("destination");
801   if (dst) {
802     setDestination((FGAirport*) fgFindAirportID(dst->getStringValue("airport")));
803     if (_destination) {
804       string rwy(dst->getStringValue("runway"));
805       if (_destination->hasRunwayWithIdent(rwy)) {
806         setDestination(_destination->getRunwayByIdent(rwy));
807       }
808       
809       if (dst->hasChild("star")) {
810         setSTAR(_destination->findSTARWithIdent(dst->getStringValue("star")));
811       }
812       
813       if (dst->hasChild("approach")) {
814         setApproach(_destination->findApproachWithIdent(dst->getStringValue("approach")));
815       }
816     }
817     
818    // destination->setStringValue("transition", dst->getStringValue("transition"));
819   }
820   
821   // alternate
822   SGPropertyNode* alt = routeData->getChild("alternate");
823   if (alt) {
824     //alternate->setStringValue(alt->getStringValue("airport"));
825   }
826   
827   // cruise
828   SGPropertyNode* crs = routeData->getChild("cruise");
829   if (crs) {
830  //   cruise->setDoubleValue("speed-kts", crs->getDoubleValue("speed-kts"));
831    // cruise->setDoubleValue("mach", crs->getDoubleValue("mach"));
832    // cruise->setDoubleValue("altitude-ft", crs->getDoubleValue("altitude-ft"));
833   } // of cruise data loading
834   
835 }
836
837 void FlightPlan::loadVersion2XMLRoute(SGPropertyNode_ptr routeData)
838 {
839   loadXMLRouteHeader(routeData);
840   
841   // route nodes
842   _legs.clear();
843   SGPropertyNode_ptr routeNode = routeData->getChild("route", 0);
844   if (routeNode.valid()) {
845     for (int i=0; i<routeNode->nChildren(); ++i) {
846       SGPropertyNode_ptr wpNode = routeNode->getChild("wp", i);
847       Leg* l = new Leg(this, Waypt::createFromProperties(NULL, wpNode));
848       _legs.push_back(l);
849     } // of route iteration
850   }
851   _waypointsChanged = true;
852 }
853
854 void FlightPlan::loadVersion1XMLRoute(SGPropertyNode_ptr routeData)
855 {
856   loadXMLRouteHeader(routeData);
857   
858   // _legs nodes
859   _legs.clear();
860   SGPropertyNode_ptr routeNode = routeData->getChild("route", 0);    
861   for (int i=0; i<routeNode->nChildren(); ++i) {
862     SGPropertyNode_ptr wpNode = routeNode->getChild("wp", i);
863     Leg* l = new Leg(this, parseVersion1XMLWaypt(wpNode));
864     _legs.push_back(l);
865   } // of route iteration
866   _waypointsChanged = true;
867 }
868
869 WayptRef FlightPlan::parseVersion1XMLWaypt(SGPropertyNode* aWP)
870 {
871   SGGeod lastPos;
872   if (!_legs.empty()) {
873     lastPos = _legs.back()->waypoint()->position();
874   } else if (_departure) {
875     lastPos = _departure->geod();
876   }
877   
878   WayptRef w;
879   string ident(aWP->getStringValue("ident"));
880   if (aWP->hasChild("longitude-deg")) {
881     // explicit longitude/latitude
882     w = new BasicWaypt(SGGeod::fromDeg(aWP->getDoubleValue("longitude-deg"), 
883                                        aWP->getDoubleValue("latitude-deg")), ident, NULL);
884     
885   } else {
886     string nid = aWP->getStringValue("navid", ident.c_str());
887     FGPositionedRef p = FGPositioned::findClosestWithIdent(nid, lastPos);
888     SGGeod pos;
889     
890     if (p) {
891       pos = p->geod();
892     } else {
893       SG_LOG(SG_GENERAL, SG_WARN, "unknown navaid in flightplan:" << nid);
894       pos = SGGeod::fromDeg(aWP->getDoubleValue("longitude-deg"),
895                             aWP->getDoubleValue("latitude-deg"));
896     }
897     
898     if (aWP->hasChild("offset-nm") && aWP->hasChild("offset-radial")) {
899       double radialDeg = aWP->getDoubleValue("offset-radial");
900       // convert magnetic radial to a true radial!
901       radialDeg += magvarDegAt(pos);
902       double offsetNm = aWP->getDoubleValue("offset-nm");
903       double az2;
904       SGGeodesy::direct(pos, radialDeg, offsetNm * SG_NM_TO_METER, pos, az2);
905     }
906     
907     w = new BasicWaypt(pos, ident, NULL);
908   }
909   
910   double altFt = aWP->getDoubleValue("altitude-ft", -9999.9);
911   if (altFt > -9990.0) {
912     w->setAltitude(altFt, RESTRICT_AT);
913   }
914   
915   return w;
916 }
917
918 /** Load a flightplan in FlightGear plain-text format */
919 bool FlightPlan::loadPlainTextFormat(const SGPath& path)
920 {
921   try {
922     sg_gzifstream in(path.str().c_str());
923     if (!in.is_open()) {
924       throw sg_io_exception("Cannot open file for reading.");
925     }
926     
927     _legs.clear();
928     while (!in.eof()) {
929       string line;
930       getline(in, line, '\n');
931       // trim CR from end of line, if found
932       if (line[line.size() - 1] == '\r') {
933         line.erase(line.size() - 1, 1);
934       }
935       
936       line = simgear::strutils::strip(line);
937       if (line.empty() || (line[0] == '#')) {
938         continue; // ignore empty/comment lines
939       }
940       
941       WayptRef w = waypointFromString(line);
942       if (!w) {
943         throw sg_io_exception("Failed to create waypoint from line '" + line + "'.");
944       }
945       
946       _legs.push_back(new Leg(this, w));
947     } // of line iteration
948   } catch (sg_exception& e) {
949     SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load route from: '" << path.str() << "'. " << e.getMessage());
950     _legs.clear();
951     return false;
952   }
953   
954   return true;
955 }  
956
957 double FlightPlan::magvarDegAt(const SGGeod& pos) const
958 {
959   double jd = globals->get_time_params()->getJD();
960   return sgGetMagVar(pos, jd) * SG_RADIANS_TO_DEGREES;
961 }
962   
963 WayptRef FlightPlan::waypointFromString(const string& tgt )
964 {
965   string target(boost::to_upper_copy(tgt));
966   WayptRef wpt;
967   
968   // extract altitude
969   double altFt = 0.0;
970   RouteRestriction altSetting = RESTRICT_NONE;
971   
972   size_t pos = target.find( '@' );
973   if ( pos != string::npos ) {
974     altFt = atof( target.c_str() + pos + 1 );
975     target = target.substr( 0, pos );
976     if ( !strcmp(fgGetString("/sim/startup/units"), "meter") )
977       altFt *= SG_METER_TO_FEET;
978     altSetting = RESTRICT_AT;
979   }
980   
981   // check for lon,lat
982   pos = target.find( ',' );
983   if ( pos != string::npos ) {
984     double lon = atof( target.substr(0, pos).c_str());
985     double lat = atof( target.c_str() + pos + 1);
986     char buf[32];
987     char ew = (lon < 0.0) ? 'W' : 'E';
988     char ns = (lat < 0.0) ? 'S' : 'N';
989     snprintf(buf, 32, "%c%03d%c%03d", ew, (int) fabs(lon), ns, (int)fabs(lat));
990     
991     wpt = new BasicWaypt(SGGeod::fromDeg(lon, lat), buf, NULL);
992     if (altSetting != RESTRICT_NONE) {
993       wpt->setAltitude(altFt, altSetting);
994     }
995     return wpt;
996   }
997   
998   SGGeod basePosition;
999   if (_legs.empty()) {
1000     // route is empty, use current position
1001     basePosition = globals->get_aircraft_position();
1002   } else {
1003     basePosition = _legs.back()->waypoint()->position();
1004   }
1005   
1006   string_list pieces(simgear::strutils::split(target, "/"));
1007   FGPositionedRef p = FGPositioned::findClosestWithIdent(pieces.front(), basePosition);
1008   if (!p) {
1009     SG_LOG( SG_NAVAID, SG_INFO, "Unable to find FGPositioned with ident:" << pieces.front());
1010     return NULL;
1011   }
1012   
1013   double magvar = magvarDegAt(basePosition);
1014   
1015   if (pieces.size() == 1) {
1016     wpt = new NavaidWaypoint(p, NULL);
1017   } else if (pieces.size() == 3) {
1018     // navaid/radial/distance-nm notation
1019     double radial = atof(pieces[1].c_str()),
1020     distanceNm = atof(pieces[2].c_str());
1021     radial += magvar;
1022     wpt = new OffsetNavaidWaypoint(p, NULL, radial, distanceNm);
1023   } else if (pieces.size() == 2) {
1024     FGAirport* apt = dynamic_cast<FGAirport*>(p.ptr());
1025     if (!apt) {
1026       SG_LOG(SG_NAVAID, SG_INFO, "Waypoint is not an airport:" << pieces.front());
1027       return NULL;
1028     }
1029     
1030     if (!apt->hasRunwayWithIdent(pieces[1])) {
1031       SG_LOG(SG_NAVAID, SG_INFO, "No runway: " << pieces[1] << " at " << pieces[0]);
1032       return NULL;
1033     }
1034     
1035     FGRunway* runway = apt->getRunwayByIdent(pieces[1]);
1036     wpt = new NavaidWaypoint(runway, NULL);
1037   } else if (pieces.size() == 4) {
1038     // navid/radial/navid/radial notation     
1039     FGPositionedRef p2 = FGPositioned::findClosestWithIdent(pieces[2], basePosition);
1040     if (!p2) {
1041       SG_LOG( SG_NAVAID, SG_INFO, "Unable to find FGPositioned with ident:" << pieces[2]);
1042       return NULL;
1043     }
1044     
1045     double r1 = atof(pieces[1].c_str()),
1046     r2 = atof(pieces[3].c_str());
1047     r1 += magvar;
1048     r2 += magvar;
1049     
1050     SGGeod intersection;
1051     bool ok = SGGeodesy::radialIntersection(p->geod(), r1, p2->geod(), r2, intersection);
1052     if (!ok) {
1053       SG_LOG(SG_NAVAID, SG_INFO, "no valid intersection for:" << target);
1054       return NULL;
1055     }
1056     
1057     std::string name = p->ident() + "-" + p2->ident();
1058     wpt = new BasicWaypt(intersection, name, NULL);
1059   }
1060   
1061   if (!wpt) {
1062     SG_LOG(SG_NAVAID, SG_INFO, "Unable to parse waypoint:" << target);
1063     return NULL;
1064   }
1065   
1066   if (altSetting != RESTRICT_NONE) {
1067     wpt->setAltitude(altFt, altSetting);
1068   }
1069   return wpt;
1070 }
1071   
1072 FlightPlan::Leg::Leg(FlightPlan* owner, WayptRef wpt) :
1073   _parent(owner),
1074   _speedRestrict(RESTRICT_NONE),
1075   _altRestrict(RESTRICT_NONE),
1076   _waypt(wpt)
1077 {
1078   if (!wpt.valid()) {
1079     throw sg_exception("can't create FlightPlan::Leg without underlying waypoint");
1080   }
1081   _speed = _altitudeFt = 0;
1082 }
1083
1084 FlightPlan::Leg* FlightPlan::Leg::cloneFor(FlightPlan* owner) const
1085 {
1086   Leg* c = new Leg(owner, _waypt);
1087 // clone local data
1088   c->_speed = _speed;
1089   c->_speedRestrict = _speedRestrict;
1090   c->_altitudeFt = _altitudeFt;
1091   c->_altRestrict = _altRestrict;
1092   
1093   return c;
1094 }
1095   
1096 FlightPlan::Leg* FlightPlan::Leg::nextLeg() const
1097 {
1098   return _parent->legAtIndex(index() + 1);
1099 }
1100
1101 unsigned int FlightPlan::Leg::index() const
1102 {
1103   return _parent->findLegIndex(this);
1104 }
1105
1106 int FlightPlan::Leg::altitudeFt() const
1107 {
1108   if (_altRestrict != RESTRICT_NONE) {
1109     return _altitudeFt;
1110   }
1111   
1112   return _waypt->altitudeFt();
1113 }
1114
1115 int FlightPlan::Leg::speed() const
1116 {
1117   if (_speedRestrict != RESTRICT_NONE) {
1118     return _speed;
1119   }
1120   
1121   return _waypt->speed();
1122 }
1123
1124 int FlightPlan::Leg::speedKts() const
1125 {
1126   return speed();
1127 }
1128   
1129 double FlightPlan::Leg::speedMach() const
1130 {
1131   if (!isMachRestrict(_speedRestrict)) {
1132     return 0.0;
1133   }
1134   
1135   return -(_speed / 100.0);
1136 }
1137
1138 RouteRestriction FlightPlan::Leg::altitudeRestriction() const
1139 {
1140   if (_altRestrict != RESTRICT_NONE) {
1141     return _altRestrict;
1142   }
1143   
1144   return _waypt->altitudeRestriction();
1145 }
1146   
1147 RouteRestriction FlightPlan::Leg::speedRestriction() const
1148 {
1149   if (_speedRestrict != RESTRICT_NONE) {
1150     return _speedRestrict;
1151   }
1152   
1153   return _waypt->speedRestriction();
1154 }
1155   
1156 void FlightPlan::Leg::setSpeed(RouteRestriction ty, double speed)
1157 {
1158   _speedRestrict = ty;
1159   if (isMachRestrict(ty)) {
1160     _speed = (speed * -100); 
1161   } else {
1162     _speed = speed;
1163   }
1164 }
1165   
1166 void FlightPlan::Leg::setAltitude(RouteRestriction ty, int altFt)
1167 {
1168   _altRestrict = ty;
1169   _altitudeFt = altFt;
1170 }
1171
1172 double FlightPlan::Leg::courseDeg() const
1173 {
1174   return _courseDeg;
1175 }
1176   
1177 double FlightPlan::Leg::distanceNm() const
1178 {
1179   return _pathDistance;
1180 }
1181   
1182 double FlightPlan::Leg::distanceAlongRoute() const
1183 {
1184   return _distanceAlongPath;
1185 }
1186   
1187 void FlightPlan::rebuildLegData()
1188 {
1189   _totalDistance = 0.0;
1190   double totalDistanceIncludingMissed = 0.0;
1191   RoutePath path(this);
1192   
1193   for (unsigned int l=0; l<_legs.size(); ++l) {
1194     _legs[l]->_courseDeg = path.trackForIndex(l);
1195     _legs[l]->_pathDistance = path.distanceForIndex(l) * SG_METER_TO_NM;
1196
1197     totalDistanceIncludingMissed += _legs[l]->_pathDistance;
1198     // distance along path includes our own leg distance
1199     _legs[l]->_distanceAlongPath = totalDistanceIncludingMissed;
1200     
1201     // omit missed-approach waypoints from total distance calculation
1202     if (!_legs[l]->waypoint()->flag(WPT_MISS)) {
1203       _totalDistance += _legs[l]->_pathDistance;
1204     }
1205 } // of legs iteration
1206   
1207 }
1208   
1209 SGGeod FlightPlan::pointAlongRoute(int aIndex, double aOffsetNm) const
1210 {
1211     RoutePath rp(this);
1212     return rp.positionForDistanceFrom(aIndex, aOffsetNm * SG_NM_TO_METER);
1213 }
1214     
1215 void FlightPlan::lockDelegate()
1216 {
1217   if (_delegateLock == 0) {
1218     assert(!_departureChanged && !_arrivalChanged && 
1219            !_waypointsChanged && !_currentWaypointChanged);
1220   }
1221   
1222   ++_delegateLock;
1223 }
1224
1225 void FlightPlan::unlockDelegate()
1226 {
1227   assert(_delegateLock > 0);
1228   if (_delegateLock > 1) {
1229     --_delegateLock;
1230     return;
1231   }
1232   
1233   if (_departureChanged) {
1234     _departureChanged = false;
1235     if (_delegate) {
1236       _delegate->runDepartureChanged();
1237     }
1238   }
1239   
1240   if (_arrivalChanged) {
1241     _arrivalChanged = false;
1242     if (_delegate) {
1243       _delegate->runArrivalChanged();
1244     }
1245   }
1246   
1247   if (_waypointsChanged) {
1248     _waypointsChanged = false;
1249     rebuildLegData();
1250     if (_delegate) {
1251       _delegate->runWaypointsChanged();
1252     }
1253   }
1254   
1255   if (_currentWaypointChanged) {
1256     _currentWaypointChanged = false;
1257     if (_delegate) {
1258       _delegate->runCurrentWaypointChanged();
1259     }
1260   }
1261
1262   --_delegateLock;
1263 }
1264   
1265 void FlightPlan::registerDelegateFactory(DelegateFactory* df)
1266 {
1267   FPDelegateFactoryVec::iterator it = std::find(static_delegateFactories.begin(),
1268                                                 static_delegateFactories.end(), df);
1269   if (it != static_delegateFactories.end()) {
1270     throw  sg_exception("duplicate delegate factory registration");
1271   }
1272   
1273   static_delegateFactories.push_back(df);
1274 }
1275   
1276 void FlightPlan::unregisterDelegateFactory(DelegateFactory* df)
1277 {
1278   FPDelegateFactoryVec::iterator it = std::find(static_delegateFactories.begin(),
1279                                                 static_delegateFactories.end(), df);
1280   if (it == static_delegateFactories.end()) {
1281     return;
1282   }
1283   
1284   static_delegateFactories.erase(it);
1285 }
1286   
1287 void FlightPlan::addDelegate(Delegate* d)
1288 {
1289   // wrap any existing delegate(s) in the new one
1290   d->_inner = _delegate;
1291   _delegate = d;
1292 }
1293
1294 void FlightPlan::removeDelegate(Delegate* d)
1295 {
1296   if (d == _delegate) {
1297     _delegate = _delegate->_inner;
1298   } else if (_delegate) {
1299     _delegate->removeInner(d);
1300   }
1301 }
1302   
1303 FlightPlan::Delegate::Delegate() :
1304   _deleteWithPlan(false),
1305   _inner(NULL)
1306 {
1307 }
1308
1309 FlightPlan::Delegate::~Delegate()
1310 {  
1311 }
1312
1313 void FlightPlan::Delegate::removeInner(Delegate* d)
1314 {
1315   if (!_inner) {
1316       throw sg_exception("FlightPlan delegate not found");
1317   }
1318   
1319   if (_inner == d) {
1320     // replace with grand-child
1321     _inner = d->_inner;
1322   } else { // recurse downwards
1323     _inner->removeInner(d);
1324   }
1325 }
1326
1327 void FlightPlan::Delegate::runDepartureChanged()
1328 {
1329   if (_inner) _inner->runDepartureChanged();
1330   departureChanged();
1331 }
1332
1333 void FlightPlan::Delegate::runArrivalChanged()
1334 {
1335   if (_inner) _inner->runArrivalChanged();
1336   arrivalChanged();
1337 }
1338
1339 void FlightPlan::Delegate::runWaypointsChanged()
1340 {
1341   if (_inner) _inner->runWaypointsChanged();
1342   waypointsChanged();
1343 }
1344   
1345 void FlightPlan::Delegate::runCurrentWaypointChanged()
1346 {
1347   if (_inner) _inner->runCurrentWaypointChanged();
1348   currentWaypointChanged();
1349 }
1350
1351 void FlightPlan::Delegate::runCleared()
1352 {
1353   if (_inner) _inner->runCleared();
1354   cleared();
1355 }  
1356
1357 void FlightPlan::Delegate::runFinished()
1358 {
1359     if (_inner) _inner->runFinished();
1360     endOfFlightPlan();
1361 }
1362     
1363 } // of namespace flightgear