]> git.mxchange.org Git - flightgear.git/blob - src/Instrumentation/rnav_waypt_controller.cxx
Merge branch 'next' of gitorious.org:fg/flightgear into next
[flightgear.git] / src / Instrumentation / rnav_waypt_controller.cxx
1 // rnav_waypt_controller.cxx - Waypoint-specific behaviours for RNAV systems
2 // Written by James Turner, started 2009.
3 //
4 // Copyright (C) 2009  Curtis L. Olson
5 //
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License as
8 // published by the Free Software Foundation; either version 2 of the
9 // License, or (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful, but
12 // WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 // General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19
20 #include "rnav_waypt_controller.hxx"
21
22 #include <cassert>
23
24 #include <simgear/sg_inlines.h>
25 #include <simgear/structure/exception.hxx>
26
27 #include <Airports/runways.hxx>
28
29 namespace flightgear
30 {
31
32 const double KNOTS_TO_METRES_PER_SECOND = SG_NM_TO_METER / 3600.0;
33
34 double pmod(double x, double y)
35 {
36   if (x < 0.0) {
37     return -fmod(x, y);
38   } else {
39     return fmod(x,y);
40   }
41 }
42
43 // implementation of
44 // http://williams.best.vwh.net/avform.htm#Intersection
45 bool geocRadialIntersection(const SGGeoc& a, double r1, const SGGeoc& b, double r2, SGGeoc& result)
46 {
47   double crs13 = r1 * SG_DEGREES_TO_RADIANS;
48   double crs23 = r2 * SG_DEGREES_TO_RADIANS;
49   double dst12 = SGGeodesy::distanceRad(a, b);
50   
51   //IF sin(lon2-lon1)<0
52   // crs12=acos((sin(lat2)-sin(lat1)*cos(dst12))/(sin(dst12)*cos(lat1)))
53   // crs21=2.*pi-acos((sin(lat1)-sin(lat2)*cos(dst12))/(sin(dst12)*cos(lat2)))
54   // ELSE
55   // crs12=2.*pi-acos((sin(lat2)-sin(lat1)*cos(dst12))/(sin(dst12)*cos(lat1)))
56   // crs21=acos((sin(lat1)-sin(lat2)*cos(dst12))/(sin(dst12)*cos(lat2)))
57   // ENDIF
58   
59   
60  // double diffLon = b.getLongitudeRad() - a.getLongitudeRad();
61   
62   double sinLat1 = sin(a.getLatitudeRad());
63   double cosLat1 = cos(a.getLatitudeRad());
64  // double sinLat2 = sin(b.getLatitudeRad());
65   //double cosLat2 = cos(b.getLatitudeRad());
66   double sinDst12 = sin(dst12);
67   double cosDst12 = cos(dst12);
68   
69   double crs12 = SGGeodesy::courseRad(a, b),
70     crs21 = SGGeodesy::courseRad(b, a);
71     
72   //double degCrs12 = crs12 * SG_RADIANS_TO_DEGREES;
73   //double degCrs21 = crs21 * SG_RADIANS_TO_DEGREES;
74     
75  /* 
76   if (sin(diffLon) < 0.0) {
77     crs12 = acos((sinLat2 - sinLat1 * cosDst12) / (sinDst12 * cosLat1));
78     crs21 = SGMiscd::twopi() - acos((sinLat1 - sinLat2*cosDst12)/(sinDst12*cosLat2));
79   } else {
80     crs12 = SGMiscd::twopi() - acos((sinLat2 - sinLat1 * cosDst12)/(sinDst12 * cosLat1));
81     crs21 = acos((sinLat1 - sinLat2 * cosDst12)/(sinDst12 * cosLat2));
82   }
83   */
84   
85   double ang1 = SGMiscd::normalizeAngle2(crs13-crs12);
86   double ang2 = SGMiscd::normalizeAngle2(crs21-crs23);
87     
88   if ((sin(ang1) == 0.0) && (sin(ang2) == 0.0)) {
89     SG_LOG(SG_INSTR, SG_WARN, "geocRadialIntersection: infinity of intersections");
90     return false;
91   }
92   
93   if ((sin(ang1)*sin(ang2))<0.0) {
94     SG_LOG(SG_INSTR, SG_WARN, "geocRadialIntersection: intersection ambiguous");
95     return false;
96   }
97   
98   ang1 = fabs(ang1);
99   ang2 = fabs(ang2);
100
101   //ang3=acos(-cos(ang1)*cos(ang2)+sin(ang1)*sin(ang2)*cos(dst12)) 
102   //dst13=atan2(sin(dst12)*sin(ang1)*sin(ang2),cos(ang2)+cos(ang1)*cos(ang3))
103   //lat3=asin(sin(lat1)*cos(dst13)+cos(lat1)*sin(dst13)*cos(crs13))
104   
105   //lon3=mod(lon1-dlon+pi,2*pi)-pi
106
107   double ang3 = acos(-cos(ang1) * cos(ang2) + sin(ang1) * sin(ang2) * cosDst12);
108   double dst13 = atan2(sinDst12 * sin(ang1) * sin(ang2), cos(ang2) + cos(ang1)*cos(ang3));
109
110   SGGeoc pt3;
111   SGGeodesy::advanceRadM(a, crs13, dst13 * SG_RAD_TO_NM * SG_NM_TO_METER, pt3);
112
113   double lat3 = asin(sinLat1 * cos(dst13) + cosLat1 * sin(dst13) * cos(crs13));
114   
115   //dlon=atan2(sin(crs13)*sin(dst13)*cos(lat1),cos(dst13)-sin(lat1)*sin(lat3))
116   double dlon = atan2(sin(crs13)*sin(dst13)*cosLat1, cos(dst13)- (sinLat1 * sin(lat3)));
117   double lon3 = SGMiscd::normalizeAngle(-a.getLongitudeRad()-dlon);
118   
119   result = SGGeoc::fromRadM(-lon3, lat3, a.getRadiusM());
120   //result = pt3;
121   return true;
122 }
123
124 ////////////////////////////////////////////////////////////////////////////
125
126 WayptController::~WayptController()
127 {
128 }
129
130 void WayptController::init()
131 {
132 }
133
134 void WayptController::setDone()
135 {
136   if (_isDone) {
137     SG_LOG(SG_AUTOPILOT, SG_WARN, "already done @ WayptController::setDone");
138   }
139   
140   _isDone = true;
141 }
142
143 double WayptController::timeToWaypt() const
144 {
145   double gs = _rnav->groundSpeedKts();
146   if (gs < 1.0) {
147     return -1.0; // stationary
148   }
149   
150   gs*= KNOTS_TO_METRES_PER_SECOND;
151   return (distanceToWayptM() / gs);
152 }
153
154 //////////////
155
156 class BasicWayptCtl : public WayptController
157 {
158 public:
159   BasicWayptCtl(RNAV* aRNAV, const WayptRef& aWpt) :
160     WayptController(aRNAV, aWpt),
161     _distanceM(0.0),
162     _courseDev(0.0)
163   {
164     if (aWpt->flag(WPT_DYNAMIC)) {
165       throw sg_exception("BasicWayptCtrl doesn't work with dynamic waypoints");
166     }
167   }
168   
169   virtual void init()
170   {
171     _targetTrack = SGGeodesy::courseDeg(_rnav->position(), _waypt->position());
172   }
173
174   virtual void update()
175   {
176     double brg, az2;
177     SGGeodesy::inverse(_rnav->position(), _waypt->position(), brg, az2, _distanceM); 
178     _courseDev = brg - _targetTrack;
179     SG_NORMALIZE_RANGE(_courseDev, -180.0, 180.0);
180     
181     if ((fabs(_courseDev) > 90.0) && (_distanceM < _rnav->overflightArmDistanceM())) {
182       setDone();
183     }
184   } 
185
186   virtual double distanceToWayptM() const
187   {
188     return _distanceM;
189   }
190   
191   virtual double xtrackErrorNm() const
192   {
193     double x = sin(courseDeviationDeg() * SG_DEGREES_TO_RADIANS) * _distanceM;
194     return x * SG_METER_TO_NM;
195   }
196   
197   virtual bool toFlag() const
198   {
199     return (fabs(_courseDev) < 90.0);
200   }
201   
202   virtual double courseDeviationDeg() const
203   {
204     return _courseDev;
205   }
206   
207   virtual double trueBearingDeg() const
208   {
209     return SGGeodesy::courseDeg(_rnav->position(), _waypt->position());
210   }
211   
212   virtual SGGeod position() const
213   {
214     return _waypt->position();
215   }
216
217 private:
218   double _distanceM;
219   double _courseDev;
220 };
221
222 /**
223  * Special controller for runways. For runways, we want very narrow deviation
224  * constraints, and to understand that any point along the paved area is
225  * equivalent to being 'at' the runway.
226  */
227 class RunwayCtl : public WayptController
228 {
229 public:
230   RunwayCtl(RNAV* aRNAV, const WayptRef& aWpt) :
231     WayptController(aRNAV, aWpt),
232     _runway(NULL),
233     _distanceM(0.0),
234     _courseDev(0.0)
235   {
236   }
237   
238   virtual void init()
239   {
240     _runway = static_cast<RunwayWaypt*>(_waypt.get())->runway();
241     _targetTrack = _runway->headingDeg();
242   }
243
244   virtual void update()
245   {
246     double brg, az2;
247     // use the far end of the runway for course deviation calculations. 
248     // this should do the correct thing both for takeoffs (including entering 
249     // the runway at a taxiway after the threshold) and also landings.
250     // seperately compute the distance to the threshold for timeToWaypt calc
251     SGGeodesy::inverse(_rnav->position(), _runway->end(), brg, az2, _distanceM); 
252     double _courseDev = brg - _targetTrack;
253     SG_NORMALIZE_RANGE(_courseDev, -180.0, 180.0);
254     
255     if ((fabs(_courseDev) > 90.0) && (_distanceM < _rnav->overflightArmDistanceM())) {
256       setDone();
257     }
258   } 
259   
260   virtual double distanceToWayptM() const
261   {
262     return SGGeodesy::distanceM(_rnav->position(), _runway->threshold());
263   }
264   
265   virtual double xtrackErrorNm() const
266   {
267     double x = sin(_courseDev * SG_RADIANS_TO_DEGREES) * _distanceM;
268     return x * SG_METER_TO_NM;
269   }
270
271   virtual double courseDeviationDeg() const
272   {
273     return _courseDev;
274   }
275
276   virtual double trueBearingDeg() const
277   {
278     // as in update(), use runway->end here, so the value remains
279     // sensible whether taking off or landing.
280     return SGGeodesy::courseDeg(_rnav->position(), _runway->end());
281   }
282   
283   virtual SGGeod position() const
284   {
285     return _runway->threshold();
286   }
287 private:
288   FGRunway* _runway;
289   double _distanceM;
290   double _courseDev;
291 };
292
293 class ConstHdgToAltCtl : public WayptController
294 {
295 public:
296   ConstHdgToAltCtl(RNAV* aRNAV, const WayptRef& aWpt) :
297     WayptController(aRNAV, aWpt)
298     
299   {
300     if (_waypt->type() != "hdgToAlt") {
301       throw sg_exception("invalid waypoint type", "ConstHdgToAltCtl ctor");
302     }
303     
304     if (_waypt->altitudeRestriction() == RESTRICT_NONE) {
305       throw sg_exception("invalid waypoint alt restriction", "ConstHdgToAltCtl ctor");
306     }
307   }
308
309   virtual void init()
310   {
311     HeadingToAltitude* w = (HeadingToAltitude*) _waypt.get();
312     _targetTrack = w->headingDegMagnetic() + _rnav->magvarDeg();
313   }
314   
315   virtual void update()
316   {
317     double curAlt = _rnav->position().getElevationFt();
318     
319     switch (_waypt->altitudeRestriction()) {
320     case RESTRICT_AT: {
321       double d = curAlt - _waypt->altitudeFt();
322       if (fabs(d) < 50.0) {
323         SG_LOG(SG_INSTR, SG_INFO, "ConstHdgToAltCtl, reached target altitude " << _waypt->altitudeFt());
324         setDone();
325       }
326     } break;
327       
328     case RESTRICT_ABOVE:
329       if (curAlt >= _waypt->altitudeFt()) {
330         SG_LOG(SG_INSTR, SG_INFO, "ConstHdgToAltCtl, above target altitude " << _waypt->altitudeFt());
331         setDone();
332       }
333       break;
334       
335     case RESTRICT_BELOW:
336       if (curAlt <= _waypt->altitudeFt()) {
337         SG_LOG(SG_INSTR, SG_INFO, "ConstHdgToAltCtl, below target altitude " << _waypt->altitudeFt());
338         setDone();
339       }
340       break;
341     
342     case RESTRICT_NONE:
343       assert(false);
344       break;
345     case SPEED_RESTRICT_MACH:
346       assert(false);
347       break;
348     }
349   }
350   
351   virtual double timeToWaypt() const
352   {
353     double d = fabs(_rnav->position().getElevationFt() - _waypt->altitudeFt());
354     return (d / _rnav->vspeedFPM()) * 60.0; // low pass filter here, probably
355   }
356   
357   virtual double distanceToWayptM() const
358   {
359     double gsMsec = _rnav->groundSpeedKts() * KNOTS_TO_METRES_PER_SECOND;
360     return timeToWaypt() * gsMsec;
361   }
362   
363   virtual SGGeod position() const
364   {
365     SGGeod p;
366     double az2;
367     SGGeodesy::direct(_rnav->position(), _targetTrack, distanceToWayptM(), p, az2);
368     return p;
369   }
370 };
371
372 class InterceptCtl : public WayptController
373 {
374 public:
375   InterceptCtl(RNAV* aRNAV, const WayptRef& aWpt) :
376     WayptController(aRNAV, aWpt),
377     _trueRadial(0.0)
378   {
379     if (_waypt->type() != "radialIntercept") {
380       throw sg_exception("invalid waypoint type", "InterceptCtl ctor");
381     }
382   }
383
384   virtual void init()
385   {
386     RadialIntercept* w = (RadialIntercept*) _waypt.get();
387     _trueRadial = w->radialDegMagnetic() + _rnav->magvarDeg();
388     _targetTrack = w->courseDegMagnetic() + _rnav->magvarDeg();
389   }
390   
391   virtual void update()
392   {
393     // note we want the outbound radial from the waypt, hence the ordering
394     // of arguments to courseDeg
395     double r = SGGeodesy::courseDeg(_waypt->position(), _rnav->position());
396     SG_LOG(SG_AUTOPILOT, SG_INFO, "current radial=" << r);
397     if (fabs(r - _trueRadial) < 0.5) {
398       SG_LOG(SG_INSTR, SG_INFO, "InterceptCtl, intercepted radial " << _trueRadial);
399       setDone();
400     }
401   }
402   
403   virtual double distanceToWayptM() const
404   {
405     return SGGeodesy::distanceM(_rnav->position(), position());
406   }
407
408   virtual SGGeod position() const
409   {
410     SGGeoc c;
411     geocRadialIntersection(SGGeoc::fromGeod(_rnav->position()), _rnav->trackDeg(), 
412       SGGeoc::fromGeod(_waypt->position()), _trueRadial, c);
413     return SGGeod::fromGeoc(c);
414   }
415 private:
416   double _trueRadial;
417 };
418
419 class DMEInterceptCtl : public WayptController
420 {
421 public:
422   DMEInterceptCtl(RNAV* aRNAV, const WayptRef& aWpt) :
423     WayptController(aRNAV, aWpt),
424     _dme(NULL),
425     _distanceNm(0.0)
426   {
427     if (_waypt->type() != "dmeIntercept") {
428       throw sg_exception("invalid waypoint type", "DMEInterceptCtl ctor");
429     }
430   }
431
432   virtual void init()
433   {
434     _dme  = (DMEIntercept*) _waypt.get();
435     _targetTrack = _dme->courseDegMagnetic() + _rnav->magvarDeg();
436   }
437   
438   virtual void update()
439   {
440     _distanceNm = SGGeodesy::distanceNm(_rnav->position(), _dme->position());
441     double d = fabs(_distanceNm - _dme->dmeDistanceNm());
442     if (d < 0.1) {
443       SG_LOG(SG_INSTR, SG_INFO, "DMEInterceptCtl, intercepted DME " << _dme->dmeDistanceNm());
444       setDone();
445     }
446   }
447   
448   virtual double distanceToWayptM() const
449   {
450     return fabs(_distanceNm - _dme->dmeDistanceNm()) * SG_NM_TO_METER;
451   }
452   
453   virtual SGGeod position() const
454   {
455     SGGeod p;
456     double az2;
457     SGGeodesy::direct(_rnav->position(), _targetTrack, distanceToWayptM(), p, az2);
458     return p;
459   }
460
461 private:
462   DMEIntercept* _dme;
463   double _distanceNm;
464 };
465
466 class HoldCtl : public WayptController
467 {
468 public:
469   HoldCtl(RNAV* aRNAV, const WayptRef& aWpt) :
470     WayptController(aRNAV, aWpt)
471     
472   {
473
474   }
475
476   virtual void init()
477   {
478   }
479   
480   virtual void update()
481   {
482     // fly inbound / outbound sides, or execute the turn
483   #if 0
484     if (inTurn) {
485     
486       targetTrack += dt * turnRateSec * turnDirection;
487       if (inbound) {
488         if .. targetTrack has passed inbound radial, doen with this turn
489       } else {
490         if target track has passed reciprocal radial done with turn
491       }
492     } else {
493       check time / distance elapsed
494       
495       if (sideDone) {
496         inTurn = true;
497         inbound = !inbound;
498         nextHeading = inbound;
499         if (!inbound) {
500           nextHeading += 180.0;
501           SG_NORMALIZE_RANGE(nextHeading, 0.0, 360.0);
502         }
503       }
504     
505     }
506   
507   #endif
508     setDone();
509   }
510   
511   virtual double distanceToWayptM() const
512   {
513     return -1.0;
514   }
515
516   virtual SGGeod position() const
517   {
518     return _waypt->position();
519   }
520 };
521
522 class VectorsCtl : public WayptController
523 {
524 public:
525   VectorsCtl(RNAV* aRNAV, const WayptRef& aWpt) :
526     WayptController(aRNAV, aWpt)
527     
528   {
529   }
530
531   virtual void init()
532   {
533  
534   }
535   
536   virtual void update()
537   {
538     setDone();
539   }
540   
541   virtual double distanceToWayptM() const
542   {
543     return -1.0;
544   }
545   
546   virtual SGGeod position() const
547   {
548     return _waypt->position();
549   }
550
551 private:
552 };
553
554 ///////////////////////////////////////////////////////////////////////////////
555
556 DirectToController::DirectToController(RNAV* aRNAV, const WayptRef& aWpt, const SGGeod& aOrigin) :
557   WayptController(aRNAV, aWpt),
558   _origin(aOrigin),
559   _distanceM(0.0),
560   _courseDev(0.0)
561 {
562 }
563
564 void DirectToController::init()
565 {
566   if (_waypt->flag(WPT_DYNAMIC)) {
567     throw sg_exception("can't direct-to a dynamic waypoint");
568   }
569   
570   _targetTrack = SGGeodesy::courseDeg(_origin, _waypt->position());
571 }
572
573 void DirectToController::update()
574 {
575   double brg, az2;
576   SGGeodesy::inverse(_rnav->position(), _waypt->position(), brg, az2, _distanceM); 
577   _courseDev = brg - _targetTrack;
578   SG_NORMALIZE_RANGE(_courseDev, -180.0, 180.0);
579     
580   if ((fabs(_courseDev) > 90.0) && (_distanceM < _rnav->overflightArmDistanceM())) {
581     setDone();
582   }
583 }
584
585 double DirectToController::distanceToWayptM() const
586 {
587   return _distanceM;
588 }
589
590 double DirectToController::xtrackErrorNm() const
591 {
592   double x = sin(courseDeviationDeg() * SG_DEGREES_TO_RADIANS) * _distanceM;
593   return x * SG_METER_TO_NM;
594 }
595
596 double DirectToController::courseDeviationDeg() const
597 {
598   return _courseDev;
599 }
600
601 double DirectToController::trueBearingDeg() const
602 {
603   return SGGeodesy::courseDeg(_rnav->position(), _waypt->position());
604 }
605
606 SGGeod DirectToController::position() const
607 {
608   return _waypt->position();
609 }
610
611 ///////////////////////////////////////////////////////////////////////////////
612
613 OBSController::OBSController(RNAV* aRNAV, const WayptRef& aWpt) :
614   WayptController(aRNAV, aWpt),
615   _distanceM(0.0),
616   _courseDev(0.0)
617 {
618 }
619
620 void OBSController::init()
621 {
622   if (_waypt->flag(WPT_DYNAMIC)) {
623     throw sg_exception("can't use a dynamic waypoint for OBS mode");
624   }
625   
626   _targetTrack = _rnav->selectedMagCourse() + _rnav->magvarDeg();
627 }
628
629 void OBSController::update()
630 {
631   _targetTrack = _rnav->selectedMagCourse() + _rnav->magvarDeg();
632   double brg, az2;
633   SGGeodesy::inverse(_rnav->position(), _waypt->position(), brg, az2, _distanceM); 
634   _courseDev = brg - _targetTrack;
635   SG_NORMALIZE_RANGE(_courseDev, -180.0, 180.0);
636 }
637
638 bool OBSController::toFlag() const
639 {
640   return (fabs(_courseDev) < 90.0);
641 }
642
643 double OBSController::distanceToWayptM() const
644 {
645   return _distanceM;
646 }
647
648 double OBSController::xtrackErrorNm() const
649 {
650   double x = sin(_courseDev * SG_DEGREES_TO_RADIANS) * _distanceM;
651   return x * SG_METER_TO_NM;
652 }
653
654 double OBSController::courseDeviationDeg() const
655 {
656 //  if (fabs(_courseDev) > 90.0) {
657  //   double d = -_courseDev;
658  //   SG_NORMALIZE_RANGE(d, -90.0, 90.0);
659   //  return d;
660   //}
661   
662   return _courseDev;
663 }
664
665 double OBSController::trueBearingDeg() const
666 {
667   return SGGeodesy::courseDeg(_rnav->position(), _waypt->position());
668 }
669
670 SGGeod OBSController::position() const
671 {
672   return _waypt->position();
673 }
674
675 ///////////////////////////////////////////////////////////////////////////////
676
677 WayptController* WayptController::createForWaypt(RNAV* aRNAV, const WayptRef& aWpt)
678 {
679   if (!aWpt) {
680     throw sg_exception("Passed null waypt", "WayptController::createForWaypt");
681   }
682   
683   const std::string& wty(aWpt->type());
684   if (wty == "runway") {
685     return new RunwayCtl(aRNAV, aWpt);
686   }
687   
688   if (wty == "radialIntercept") {
689     return new InterceptCtl(aRNAV, aWpt);
690   }
691   
692   if (wty == "dmeIntercept") {
693     return new DMEInterceptCtl(aRNAV, aWpt);
694   }
695   
696   if (wty == "hdgToAlt") {
697     return new ConstHdgToAltCtl(aRNAV, aWpt);
698   }
699   
700   if (wty == "vectors") {
701     return new VectorsCtl(aRNAV, aWpt);
702   }
703   
704   if (wty == "hold") {
705     return new HoldCtl(aRNAV, aWpt);
706   }
707   
708   return new BasicWayptCtl(aRNAV, aWpt);
709 }
710
711 } // of namespace flightgear
712