]> git.mxchange.org Git - flightgear.git/blob - src/Navaids/FlightPlan.cxx
Break FlightPlan out into its own file.
[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
31 // Boost
32 #include <boost/algorithm/string/case_conv.hpp>
33 #include <boost/algorithm/string.hpp>
34 #include <boost/foreach.hpp>
35
36 // SimGear
37 #include <simgear/structure/exception.hxx>
38 #include <simgear/misc/sg_path.hxx>
39 #include <simgear/magvar/magvar.hxx>
40 #include <simgear/timing/sg_time.hxx>
41 #include <simgear/misc/sgstream.hxx>
42 #include <simgear/misc/strutils.hxx>
43 #include <simgear/props/props_io.hxx>
44
45 // FlightGear
46 #include <Main/globals.hxx>
47 #include "Main/fg_props.hxx"
48 #include <Navaids/procedure.hxx>
49 #include <Navaids/waypoint.hxx>
50
51 using std::string;
52 using std::vector;
53 using std::endl;
54 using std::fstream;
55
56 namespace flightgear {
57
58 FlightPlan::FlightPlan() :
59   _currentIndex(-1),
60   _departureRunway(NULL),
61   _destinationRunway(NULL),
62   _sid(NULL),
63   _star(NULL),
64   _approach(NULL),
65   _delegate(NULL)
66 {
67   
68 }
69   
70 FlightPlan::~FlightPlan()
71 {
72   
73 }
74   
75 FlightPlan* FlightPlan::clone(const string& newIdent) const
76 {
77   FlightPlan* c = new FlightPlan();
78   c->_ident = newIdent.empty() ? _ident : newIdent;
79   
80 // copy destination / departure data.
81   c->setDeparture(_departure);
82   c->setDeparture(_departureRunway);
83   
84   if (_approach) {
85     c->setApproach(_approach);
86   } else if (_destinationRunway) {
87     c->setDestination(_destinationRunway);
88   } else if (_destination) {
89     c->setDestination(_destination);
90   }
91   
92   c->setSTAR(_star);
93   c->setSID(_sid);
94   
95 // copy legs
96   for (int l=0; l < numLegs(); ++l) {
97     c->_legs.push_back(_legs[l]->cloneFor(c));
98   }
99   
100   return c;
101 }
102
103 void FlightPlan::setIdent(const string& s)
104 {
105   _ident = s;
106 }
107   
108 string FlightPlan::ident() const
109 {
110   return _ident;
111 }
112   
113 FlightPlan::Leg* FlightPlan::insertWayptAtIndex(Waypt* aWpt, int aIndex)
114 {
115   if (!aWpt) {
116     return NULL;
117   }
118   
119   WayptVec wps;
120   wps.push_back(aWpt);
121   
122   int index = aIndex;
123   if ((aIndex == -1) || (aIndex > (int) _legs.size())) {
124     index = _legs.size();
125   }
126   
127   insertWayptsAtIndex(wps, index);
128   return legAtIndex(aIndex);
129 }
130   
131 void FlightPlan::insertWayptsAtIndex(const WayptVec& wps, int aIndex)
132 {
133   if (wps.empty()) {
134     return;
135   }
136   
137   int index = aIndex;
138   if ((aIndex == -1) || (aIndex > (int) _legs.size())) {
139     index = _legs.size();
140   }
141   
142   LegVec::iterator it = _legs.begin();
143   it += index;
144   
145   int endIndex = index + wps.size() - 1;
146   if (_currentIndex >= endIndex) {
147     _currentIndex += wps.size();
148   }
149  
150   LegVec newLegs;
151   BOOST_FOREACH(WayptRef wp, wps) {
152     newLegs.push_back(new Leg(this, wp));
153   }
154   
155   _legs.insert(it, newLegs.begin(), newLegs.end());
156   rebuildLegData();
157   
158   if (_delegate) {
159     _delegate->runWaypointsChanged();
160   }
161 }
162
163 void FlightPlan::deleteIndex(int aIndex)
164 {
165   int index = aIndex;
166   if (aIndex < 0) { // negative indices count the the end
167     index = _legs.size() + index;
168   }
169   
170   if ((index < 0) || (index >= numLegs())) {
171     SG_LOG(SG_AUTOPILOT, SG_WARN, "removeAtIndex with invalid index:" << aIndex);
172     return;
173   }
174   LegVec::iterator it = _legs.begin();
175   it += index;
176   Leg* l = *it;
177   _legs.erase(it);
178   delete l;
179   
180   bool curChanged = false;
181   if (_currentIndex == index) {
182     // current waypoint was removed
183     curChanged = true;
184   } else if (_currentIndex > index) {
185     --_currentIndex; // shift current index down if necessary
186   }
187
188   rebuildLegData();
189   if (_delegate) {
190     _delegate->runWaypointsChanged();
191     if (curChanged) {
192       _delegate->runCurrentWaypointChanged();
193     }
194   }
195 }
196   
197 void FlightPlan::clear()
198 {
199   _currentIndex = -1;
200   BOOST_FOREACH(Leg* l, _legs) {
201     delete l;
202   }
203   _legs.clear();  
204   rebuildLegData();
205   if (_delegate) {
206     _delegate->runDepartureChanged();
207     _delegate->runArrivalChanged();
208     _delegate->runWaypointsChanged();
209     _delegate->runCurrentWaypointChanged();
210   }
211 }
212   
213 int FlightPlan::clearWayptsWithFlag(WayptFlag flag)
214 {
215   int count = 0;
216   for (unsigned int i=0; i<_legs.size(); ++i) {
217     Leg* l = _legs[i];
218     if (!l->waypoint()->flag(flag)) {
219       continue;
220     }
221     
222   // okay, we're going to clear this leg
223     ++count;
224     if (_currentIndex > (int) i) {
225       --_currentIndex;
226     }
227     
228     delete l;
229     LegVec::iterator it = _legs.begin();
230     it += i;
231     _legs.erase(it);
232   }
233
234   if (count == 0) {
235     return 0; // nothing was cleared, don't fire the delegate
236   }
237   
238   rebuildLegData();
239   if (_delegate) {
240     _delegate->runWaypointsChanged();
241     _delegate->runCurrentWaypointChanged();
242   }
243   
244   return count;
245 }
246   
247 void FlightPlan::setCurrentIndex(int index)
248 {
249   if ((index < -1) || (index >= numLegs())) {
250     throw sg_range_exception("invalid leg index", "FlightPlan::setCurrentIndex");
251   }
252   
253   if (index == _currentIndex) {
254     return;
255   }
256   
257   _currentIndex = index;
258   if (_delegate) {
259     _delegate->runCurrentWaypointChanged();
260   }
261 }
262   
263 int FlightPlan::findWayptIndex(const SGGeod& aPos) const
264 {  
265   for (int i=0; i<numLegs(); ++i) {
266     if (_legs[i]->waypoint()->matches(aPos)) {
267       return i;
268     }
269   }
270   
271   return -1;
272 }
273
274 FlightPlan::Leg* FlightPlan::currentLeg() const
275 {
276   if ((_currentIndex < 0) || (_currentIndex >= numLegs()))
277     return NULL;
278   return legAtIndex(_currentIndex);
279 }
280
281 FlightPlan::Leg* FlightPlan::previousLeg() const
282 {
283   if (_currentIndex == 0) {
284     return NULL;
285   }
286   
287   return legAtIndex(_currentIndex - 1);
288 }
289
290 FlightPlan::Leg* FlightPlan::nextLeg() const
291 {
292   if ((_currentIndex < 0) || ((_currentIndex + 1) >= numLegs())) {
293     return NULL;
294   }
295   
296   return legAtIndex(_currentIndex + 1);
297 }
298
299 FlightPlan::Leg* FlightPlan::legAtIndex(int index) const
300 {
301   if ((index < 0) || (index >= numLegs())) {
302     throw sg_range_exception("index out of range", "FlightPlan::legAtIndex");
303   }
304   
305   return _legs[index];
306 }
307   
308 int FlightPlan::findLegIndex(const Leg *l) const
309 {
310   for (unsigned int i=0; i<_legs.size(); ++i) {
311     if (_legs[i] == l) {
312       return i;
313     }
314   }
315   
316   return -1;
317 }
318
319 void FlightPlan::setDeparture(FGAirport* apt)
320 {
321   if (apt == _departure) {
322     return;
323   }
324   
325   _departure = apt;
326   _departureRunway = NULL;
327   setSID((SID*)NULL);
328   
329   if (_delegate) {
330     _delegate->runDepartureChanged();
331   }
332 }
333   
334 void FlightPlan::setDeparture(FGRunway* rwy)
335 {
336   if (_departureRunway == rwy) {
337     return;
338   }
339   
340   _departureRunway = rwy;
341   if (rwy->airport() != _departure) {
342     _departure = rwy->airport();
343     setSID((SID*)NULL);
344   }
345   
346   if (_delegate) {
347     _delegate->runDepartureChanged();
348   }
349 }
350   
351 void FlightPlan::setSID(SID* sid, const std::string& transition)
352 {
353   if (sid == _sid) {
354     return;
355   }
356   
357   _sid = sid;
358   _sidTransition = transition;
359   
360   if (_delegate) {
361     _delegate->runDepartureChanged();
362   }
363 }
364   
365 void FlightPlan::setSID(Transition* trans)
366 {
367   if (!trans) {
368     setSID((SID*) NULL);
369     return;
370   }
371   
372   if (trans->parent()->type() != PROCEDURE_SID)
373     throw sg_exception("FlightPlan::setSID: transition does not belong to a SID");
374   
375   setSID((SID*) trans->parent(), trans->ident());
376 }
377   
378 Transition* FlightPlan::sidTransition() const
379 {
380   if (!_sid || _sidTransition.empty()) {
381     return NULL;
382   }
383   
384   return _sid->findTransitionByName(_sidTransition);
385 }
386
387 void FlightPlan::setDestination(FGAirport* apt)
388 {
389   if (apt == _destination) {
390     return;
391   }
392   
393   _destination = apt;
394   _destinationRunway = NULL;
395   setSTAR((STAR*)NULL);
396
397   if (_delegate) {
398     _delegate->runArrivalChanged();
399   }
400 }
401     
402 void FlightPlan::setDestination(FGRunway* rwy)
403 {
404   if (_destinationRunway == rwy) {
405     return;
406   }
407   
408   _destinationRunway = rwy;
409   if (_destination != rwy->airport()) {
410     _destination = rwy->airport();
411     setSTAR((STAR*)NULL);
412   }
413   
414   if (_delegate) {
415     _delegate->runArrivalChanged();
416   }
417 }
418   
419 void FlightPlan::setSTAR(STAR* star, const std::string& transition)
420 {
421   if (_star == star) {
422     return;
423   }
424   
425   _star = star;
426   _starTransition = transition;
427   
428   if (_delegate) {
429     _delegate->runArrivalChanged();
430   }
431 }
432   
433 void FlightPlan::setSTAR(Transition* trans)
434 {
435   if (!trans) {
436     setSTAR((STAR*) NULL);
437     return;
438   }
439   
440   if (trans->parent()->type() != PROCEDURE_STAR)
441     throw sg_exception("FlightPlan::setSTAR: transition does not belong to a STAR");
442   
443   setSTAR((STAR*) trans->parent(), trans->ident());
444 }
445
446 Transition* FlightPlan::starTransition() const
447 {
448   if (!_star || _starTransition.empty()) {
449     return NULL;
450   }
451   
452   return _star->findTransitionByName(_starTransition);
453 }
454   
455 void FlightPlan::setApproach(flightgear::Approach *app)
456 {
457   if (_approach == app) {
458     return;
459   }
460   
461   _approach = app;
462   if (app) {
463     // keep runway + airport in sync
464     if (_destinationRunway != _approach->runway()) {
465       _destinationRunway = _approach->runway();
466     }
467     
468     if (_destination != _destinationRunway->airport()) {
469       _destination = _destinationRunway->airport();
470     }
471   }
472
473   if (_delegate) {
474     _delegate->runArrivalChanged();
475   }
476 }
477   
478 bool FlightPlan::save(const SGPath& path)
479 {
480   SG_LOG(SG_IO, SG_INFO, "Saving route to " << path.str());
481   try {
482     SGPropertyNode_ptr d(new SGPropertyNode);
483     d->setIntValue("version", 2);
484     
485     if (_departure) {
486       d->setStringValue("departure/airport", _departure->ident());
487       if (_sid) {
488         d->setStringValue("departure/sid", _sid->ident());
489       }
490       
491       if (_departureRunway) {
492         d->setStringValue("departure/runway", _departureRunway->ident());
493       }
494     }
495     
496     if (_destination) {
497       d->setStringValue("destination/airport", _destination->ident());
498       if (_star) {
499         d->setStringValue("destination/star", _star->ident());
500       }
501       
502       if (_approach) {
503         d->setStringValue("destination/approach", _approach->ident());
504       }
505       
506       //d->setStringValue("destination/transition", destination->getStringValue("transition"));
507       
508       if (_destinationRunway) {
509         d->setStringValue("destination/runway", _destinationRunway->ident());
510       }
511     }
512     
513     // route nodes
514     SGPropertyNode* routeNode = d->getChild("route", 0, true);
515     for (unsigned int i=0; i<_legs.size(); ++i) {
516       Waypt* wpt = _legs[i]->waypoint();
517       wpt->saveAsNode(routeNode->getChild("wp", i, true));
518     } // of waypoint iteration
519     writeProperties(path.str(), d, true /* write-all */);
520     return true;
521   } catch (sg_exception& e) {
522     SG_LOG(SG_IO, SG_ALERT, "Failed to save flight-plan '" << path.str() << "'. " << e.getMessage());
523     return false;
524   }
525 }
526   
527 bool FlightPlan::load(const SGPath& path)
528 {
529   if (!path.exists())
530   {
531     SG_LOG(SG_IO, SG_ALERT, "Failed to load flight-plan '" << path.str()
532            << "'. The file does not exist.");
533     return false;
534   }
535   
536   SGPropertyNode_ptr routeData(new SGPropertyNode);
537   SG_LOG(SG_IO, SG_INFO, "going to read flight-plan from:" << path.str());
538   
539   bool Status = false;
540   try {
541     readProperties(path.str(), routeData);
542   } catch (sg_exception& ) {
543     // if XML parsing fails, the file might be simple textual list of waypoints
544     Status = loadPlainTextRoute(path);
545     routeData = 0;
546   }
547   
548   if (routeData.valid())
549   {
550     try {
551       int version = routeData->getIntValue("version", 1);
552       if (version == 1) {
553         loadVersion1XMLRoute(routeData);
554       } else if (version == 2) {
555         loadVersion2XMLRoute(routeData);
556       } else {
557         throw sg_io_exception("unsupported XML route version");
558       }
559       Status = true;
560     } catch (sg_exception& e) {
561       SG_LOG(SG_IO, SG_ALERT, "Failed to load flight-plan '" << e.getOrigin()
562              << "'. " << e.getMessage());
563       Status = false;
564     }
565   }
566   
567   rebuildLegData();
568   if (_delegate) {
569     _delegate->runWaypointsChanged();
570   }
571   
572   return Status;
573 }
574
575 void FlightPlan::loadXMLRouteHeader(SGPropertyNode_ptr routeData)
576 {
577   // departure nodes
578   SGPropertyNode* dep = routeData->getChild("departure");
579   if (dep) {
580     string depIdent = dep->getStringValue("airport");
581     setDeparture((FGAirport*) fgFindAirportID(depIdent));
582     if (_departure) {
583       if (dep->hasChild("runway")) {
584         setDeparture(_departure->getRunwayByIdent(dep->getStringValue("runway")));
585       }
586     
587       if (dep->hasChild("sid")) {
588         setSID(_departure->findSIDWithIdent(dep->getStringValue("sid")));
589       }
590    // departure->setStringValue("transition", dep->getStringValue("transition"));
591     }
592   }
593   
594   // destination
595   SGPropertyNode* dst = routeData->getChild("destination");
596   if (dst) {
597     setDestination((FGAirport*) fgFindAirportID(dst->getStringValue("airport")));
598     if (_destination) {
599       if (dst->hasChild("runway")) {
600         setDestination(_destination->getRunwayByIdent(dst->getStringValue("runway")));
601       }
602       
603       if (dst->hasChild("star")) {
604         setSTAR(_destination->findSTARWithIdent(dst->getStringValue("star")));
605       }
606       
607       if (dst->hasChild("approach")) {
608         setApproach(_destination->findApproachWithIdent(dst->getStringValue("approach")));
609       }
610     }
611     
612    // destination->setStringValue("transition", dst->getStringValue("transition"));
613   }
614   
615   // alternate
616   SGPropertyNode* alt = routeData->getChild("alternate");
617   if (alt) {
618     //alternate->setStringValue(alt->getStringValue("airport"));
619   }
620   
621   // cruise
622   SGPropertyNode* crs = routeData->getChild("cruise");
623   if (crs) {
624  //   cruise->setDoubleValue("speed-kts", crs->getDoubleValue("speed-kts"));
625    // cruise->setDoubleValue("mach", crs->getDoubleValue("mach"));
626    // cruise->setDoubleValue("altitude-ft", crs->getDoubleValue("altitude-ft"));
627   } // of cruise data loading
628   
629 }
630
631 void FlightPlan::loadVersion2XMLRoute(SGPropertyNode_ptr routeData)
632 {
633   loadXMLRouteHeader(routeData);
634   
635   // route nodes
636   _legs.clear();
637   SGPropertyNode_ptr routeNode = routeData->getChild("route", 0);    
638   for (int i=0; i<routeNode->nChildren(); ++i) {
639     SGPropertyNode_ptr wpNode = routeNode->getChild("wp", i);
640     Leg* l = new Leg(this, Waypt::createFromProperties(NULL, wpNode));
641     _legs.push_back(l);
642   } // of route iteration
643 }
644
645 void FlightPlan::loadVersion1XMLRoute(SGPropertyNode_ptr routeData)
646 {
647   loadXMLRouteHeader(routeData);
648   
649   // _legs nodes
650   _legs.clear();
651   SGPropertyNode_ptr routeNode = routeData->getChild("route", 0);    
652   for (int i=0; i<routeNode->nChildren(); ++i) {
653     SGPropertyNode_ptr wpNode = routeNode->getChild("wp", i);
654     Leg* l = new Leg(this, parseVersion1XMLWaypt(wpNode));
655     _legs.push_back(l);
656   } // of route iteration
657
658 }
659
660 WayptRef FlightPlan::parseVersion1XMLWaypt(SGPropertyNode* aWP)
661 {
662   SGGeod lastPos;
663   if (!_legs.empty()) {
664     lastPos = _legs.back()->waypoint()->position();
665   } else if (_departure) {
666     lastPos = _departure->geod();
667   }
668   
669   WayptRef w;
670   string ident(aWP->getStringValue("ident"));
671   if (aWP->hasChild("longitude-deg")) {
672     // explicit longitude/latitude
673     w = new BasicWaypt(SGGeod::fromDeg(aWP->getDoubleValue("longitude-deg"), 
674                                        aWP->getDoubleValue("latitude-deg")), ident, NULL);
675     
676   } else {
677     string nid = aWP->getStringValue("navid", ident.c_str());
678     FGPositionedRef p = FGPositioned::findClosestWithIdent(nid, lastPos);
679     if (!p) {
680       throw sg_io_exception("bad route file, unknown navid:" + nid);
681     }
682     
683     SGGeod pos(p->geod());
684     if (aWP->hasChild("offset-nm") && aWP->hasChild("offset-radial")) {
685       double radialDeg = aWP->getDoubleValue("offset-radial");
686       // convert magnetic radial to a true radial!
687       radialDeg += magvarDegAt(pos);
688       double offsetNm = aWP->getDoubleValue("offset-nm");
689       double az2;
690       SGGeodesy::direct(p->geod(), radialDeg, offsetNm * SG_NM_TO_METER, pos, az2);
691     }
692     
693     w = new BasicWaypt(pos, ident, NULL);
694   }
695   
696   double altFt = aWP->getDoubleValue("altitude-ft", -9999.9);
697   if (altFt > -9990.0) {
698     w->setAltitude(altFt, RESTRICT_AT);
699   }
700   
701   return w;
702 }
703
704 bool FlightPlan::loadPlainTextRoute(const SGPath& path)
705 {
706   try {
707     sg_gzifstream in(path.str().c_str());
708     if (!in.is_open()) {
709       throw sg_io_exception("Cannot open file for reading.");
710     }
711     
712     _legs.clear();
713     while (!in.eof()) {
714       string line;
715       getline(in, line, '\n');
716       // trim CR from end of line, if found
717       if (line[line.size() - 1] == '\r') {
718         line.erase(line.size() - 1, 1);
719       }
720       
721       line = simgear::strutils::strip(line);
722       if (line.empty() || (line[0] == '#')) {
723         continue; // ignore empty/comment lines
724       }
725       
726       WayptRef w = waypointFromString(line);
727       if (!w) {
728         throw sg_io_exception("Failed to create waypoint from line '" + line + "'.");
729       }
730       
731       _legs.push_back(new Leg(this, w));
732     } // of line iteration
733   } catch (sg_exception& e) {
734     SG_LOG(SG_IO, SG_ALERT, "Failed to load route from: '" << path.str() << "'. " << e.getMessage());
735     _legs.clear();
736     return false;
737   }
738   
739   return true;
740 }  
741
742 double FlightPlan::magvarDegAt(const SGGeod& pos) const
743 {
744   double jd = globals->get_time_params()->getJD();
745   return sgGetMagVar(pos, jd) * SG_RADIANS_TO_DEGREES;
746 }
747   
748 WayptRef FlightPlan::waypointFromString(const string& tgt )
749 {
750   string target(boost::to_upper_copy(tgt));
751   WayptRef wpt;
752   
753   // extract altitude
754   double altFt = 0.0;
755   RouteRestriction altSetting = RESTRICT_NONE;
756   
757   size_t pos = target.find( '@' );
758   if ( pos != string::npos ) {
759     altFt = atof( target.c_str() + pos + 1 );
760     target = target.substr( 0, pos );
761     if ( !strcmp(fgGetString("/sim/startup/units"), "meter") )
762       altFt *= SG_METER_TO_FEET;
763     altSetting = RESTRICT_AT;
764   }
765   
766   // check for lon,lat
767   pos = target.find( ',' );
768   if ( pos != string::npos ) {
769     double lon = atof( target.substr(0, pos).c_str());
770     double lat = atof( target.c_str() + pos + 1);
771     char buf[32];
772     char ew = (lon < 0.0) ? 'W' : 'E';
773     char ns = (lat < 0.0) ? 'S' : 'N';
774     snprintf(buf, 32, "%c%03d%c%03d", ew, (int) fabs(lon), ns, (int)fabs(lat));
775     
776     wpt = new BasicWaypt(SGGeod::fromDeg(lon, lat), buf, NULL);
777     if (altSetting != RESTRICT_NONE) {
778       wpt->setAltitude(altFt, altSetting);
779     }
780     return wpt;
781   }
782   
783   SGGeod basePosition;
784   if (_legs.empty()) {
785     // route is empty, use current position
786     basePosition = globals->get_aircraft_position();
787   } else {
788     basePosition = _legs.back()->waypoint()->position();
789   }
790   
791   string_list pieces(simgear::strutils::split(target, "/"));
792   FGPositionedRef p = FGPositioned::findClosestWithIdent(pieces.front(), basePosition);
793   if (!p) {
794     SG_LOG( SG_AUTOPILOT, SG_INFO, "Unable to find FGPositioned with ident:" << pieces.front());
795     return NULL;
796   }
797   
798   double magvar = magvarDegAt(basePosition);
799   
800   if (pieces.size() == 1) {
801     wpt = new NavaidWaypoint(p, NULL);
802   } else if (pieces.size() == 3) {
803     // navaid/radial/distance-nm notation
804     double radial = atof(pieces[1].c_str()),
805     distanceNm = atof(pieces[2].c_str());
806     radial += magvar;
807     wpt = new OffsetNavaidWaypoint(p, NULL, radial, distanceNm);
808   } else if (pieces.size() == 2) {
809     FGAirport* apt = dynamic_cast<FGAirport*>(p.ptr());
810     if (!apt) {
811       SG_LOG(SG_AUTOPILOT, SG_INFO, "Waypoint is not an airport:" << pieces.front());
812       return NULL;
813     }
814     
815     if (!apt->hasRunwayWithIdent(pieces[1])) {
816       SG_LOG(SG_AUTOPILOT, SG_INFO, "No runway: " << pieces[1] << " at " << pieces[0]);
817       return NULL;
818     }
819     
820     FGRunway* runway = apt->getRunwayByIdent(pieces[1]);
821     wpt = new NavaidWaypoint(runway, NULL);
822   } else if (pieces.size() == 4) {
823     // navid/radial/navid/radial notation     
824     FGPositionedRef p2 = FGPositioned::findClosestWithIdent(pieces[2], basePosition);
825     if (!p2) {
826       SG_LOG( SG_AUTOPILOT, SG_INFO, "Unable to find FGPositioned with ident:" << pieces[2]);
827       return NULL;
828     }
829     
830     double r1 = atof(pieces[1].c_str()),
831     r2 = atof(pieces[3].c_str());
832     r1 += magvar;
833     r2 += magvar;
834     
835     SGGeod intersection;
836     bool ok = SGGeodesy::radialIntersection(p->geod(), r1, p2->geod(), r2, intersection);
837     if (!ok) {
838       SG_LOG(SG_AUTOPILOT, SG_INFO, "no valid intersection for:" << target);
839       return NULL;
840     }
841     
842     std::string name = p->ident() + "-" + p2->ident();
843     wpt = new BasicWaypt(intersection, name, NULL);
844   }
845   
846   if (!wpt) {
847     SG_LOG(SG_AUTOPILOT, SG_INFO, "Unable to parse waypoint:" << target);
848     return NULL;
849   }
850   
851   if (altSetting != RESTRICT_NONE) {
852     wpt->setAltitude(altFt, altSetting);
853   }
854   return wpt;
855 }
856   
857 FlightPlan::Leg::Leg(FlightPlan* owner, WayptRef wpt) :
858   _parent(owner),
859   _speedRestrict(RESTRICT_NONE),
860   _altRestrict(RESTRICT_NONE),
861   _waypt(wpt)
862 {
863   if (!wpt.valid()) {
864     throw sg_exception("can't create FlightPlan::Leg without underlying waypoint");
865   }
866   _speed = _altitudeFt = 0;
867 }
868
869 FlightPlan::Leg* FlightPlan::Leg::cloneFor(FlightPlan* owner) const
870 {
871   Leg* c = new Leg(owner, _waypt);
872 // clone local data
873   c->_speed = _speed;
874   c->_speedRestrict = _speedRestrict;
875   c->_altitudeFt = _altitudeFt;
876   c->_altRestrict = _altRestrict;
877   
878   return c;
879 }
880   
881 FlightPlan::Leg* FlightPlan::Leg::nextLeg() const
882 {
883   return _parent->legAtIndex(index() + 1);
884 }
885
886 unsigned int FlightPlan::Leg::index() const
887 {
888   return _parent->findLegIndex(this);
889 }
890
891 int FlightPlan::Leg::altitudeFt() const
892 {
893   if (_altRestrict != RESTRICT_NONE) {
894     return _altitudeFt;
895   }
896   
897   return _waypt->altitudeFt();
898 }
899
900 int FlightPlan::Leg::speed() const
901 {
902   if (_speedRestrict != RESTRICT_NONE) {
903     return _speed;
904   }
905   
906   return _waypt->speed();
907 }
908
909 int FlightPlan::Leg::speedKts() const
910 {
911   return speed();
912 }
913   
914 double FlightPlan::Leg::speedMach() const
915 {
916   if (!isMachRestrict(_speedRestrict)) {
917     return 0.0;
918   }
919   
920   return -(_speed / 100.0);
921 }
922
923 RouteRestriction FlightPlan::Leg::altitudeRestriction() const
924 {
925   if (_altRestrict != RESTRICT_NONE) {
926     return _altRestrict;
927   }
928   
929   return _waypt->altitudeRestriction();
930 }
931   
932 RouteRestriction FlightPlan::Leg::speedRestriction() const
933 {
934   if (_speedRestrict != RESTRICT_NONE) {
935     return _speedRestrict;
936   }
937   
938   return _waypt->speedRestriction();
939 }
940   
941 void FlightPlan::Leg::setSpeed(RouteRestriction ty, double speed)
942 {
943   _speedRestrict = ty;
944   if (isMachRestrict(ty)) {
945     _speed = (speed * -100); 
946   } else {
947     _speed = speed;
948   }
949 }
950   
951 void FlightPlan::Leg::setAltitude(RouteRestriction ty, int altFt)
952 {
953   _altRestrict = ty;
954   _altitudeFt = altFt;
955 }
956
957 double FlightPlan::Leg::courseDeg() const
958 {
959   return _courseDeg;
960 }
961   
962 double FlightPlan::Leg::distanceNm() const
963 {
964   return _pathDistance;
965 }
966   
967 double FlightPlan::Leg::distanceAlongRoute() const
968 {
969   return _distanceAlongPath;
970 }
971   
972 void FlightPlan::rebuildLegData()
973 {
974   _totalDistance = 0.0;
975   int lastLeg = static_cast<int>(_legs.size()) - 1;
976   for (int l=0; l<lastLeg; ++l) {
977     Leg* cur = _legs[l];
978     Leg* next = _legs[l + 1];
979     
980     std::pair<double, double> crsDist =
981       next->waypoint()->courseAndDistanceFrom(cur->waypoint()->position());
982     _legs[l]->_courseDeg = crsDist.first;
983     _legs[l]->_pathDistance = crsDist.second * SG_METER_TO_NM;
984     _legs[l]->_distanceAlongPath = _totalDistance;
985     _totalDistance += crsDist.second * SG_METER_TO_NM;
986   } // of legs iteration
987 }
988   
989 void FlightPlan::setDelegate(Delegate* d)
990 {
991   // wrap any existing delegate(s) in the new one
992   d->_inner = _delegate;
993   _delegate = d;
994 }
995
996 void FlightPlan::removeDelegate(Delegate* d)
997 {
998   if (d == _delegate) {
999     _delegate = _delegate->_inner;
1000   } else if (_delegate) {
1001     _delegate->removeInner(d);
1002   }
1003 }
1004   
1005 FlightPlan::Delegate::Delegate() :
1006   _inner(NULL)
1007 {
1008   
1009 }
1010
1011 FlightPlan::Delegate::~Delegate()
1012 {
1013   
1014 }
1015
1016 void FlightPlan::Delegate::removeInner(Delegate* d)
1017 {
1018   if (!_inner) {
1019     return;
1020   }
1021   
1022   if (_inner == d) {
1023     // replace with grand-child
1024     _inner = d->_inner;
1025   } else { // recurse downwards
1026     _inner->removeInner(d);
1027   }
1028 }
1029
1030 void FlightPlan::Delegate::runDepartureChanged()
1031 {
1032   if (_inner) _inner->runDepartureChanged();
1033   departureChanged();
1034 }
1035
1036 void FlightPlan::Delegate::runArrivalChanged()
1037 {
1038   if (_inner) _inner->runArrivalChanged();
1039   arrivalChanged();
1040 }
1041
1042 void FlightPlan::Delegate::runWaypointsChanged()
1043 {
1044   if (_inner) _inner->runWaypointsChanged();
1045   waypointsChanged();
1046 }
1047   
1048 void FlightPlan::Delegate::runCurrentWaypointChanged()
1049 {
1050   if (_inner) _inner->runCurrentWaypointChanged();
1051   currentWaypointChanged();
1052 }
1053   
1054 } // of namespace flightgear