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