FlightGear next
routePath.cxx
Go to the documentation of this file.
1// routePath.hxx - compute data about planned route
2//
3// Copyright (C) 2018 James Turner <james@flightgear.org>
4// This program is free software; you can redistribute it and/or
5// modify it under the terms of the GNU General Public License as
6// published by the Free Software Foundation; either version 2 of the
7// License, or (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program; if not, write to the Free Software
16// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18#include "config.h"
19
20#include <algorithm>
21
22#include <Navaids/routePath.hxx>
23
24#include <simgear/structure/exception.hxx>
25#include <simgear/magvar/magvar.hxx>
26#include <simgear/timing/sg_time.hxx>
27
28#include <Main/globals.hxx>
29#include <Airports/runways.hxx>
30#include <Navaids/waypoint.hxx>
34
35namespace flightgear {
36
37 // implementation of
38 // http://williams.best.vwh.net/avform.htm#Intersection
39 bool geocRadialIntersection(const SGGeoc& a, double r1, const SGGeoc& b, double r2, SGGeoc& result)
40 {
41 double crs13 = r1 * SG_DEGREES_TO_RADIANS;
42 double crs23 = r2 * SG_DEGREES_TO_RADIANS;
43 double dst12 = SGGeodesy::distanceRad(a, b);
44
45 //IF sin(lon2-lon1)<0
46 // crs12=acos((sin(lat2)-sin(lat1)*cos(dst12))/(sin(dst12)*cos(lat1)))
47 // crs21=2.*pi-acos((sin(lat1)-sin(lat2)*cos(dst12))/(sin(dst12)*cos(lat2)))
48 // ELSE
49 // crs12=2.*pi-acos((sin(lat2)-sin(lat1)*cos(dst12))/(sin(dst12)*cos(lat1)))
50 // crs21=acos((sin(lat1)-sin(lat2)*cos(dst12))/(sin(dst12)*cos(lat2)))
51 // ENDIF
52
53 double sinLat1 = sin(a.getLatitudeRad());
54 double cosLat1 = cos(a.getLatitudeRad());
55 double sinDst12 = sin(dst12);
56 double cosDst12 = cos(dst12);
57
58 double crs12 = SGGeodesy::courseRad(a, b),
59 crs21 = SGGeodesy::courseRad(b, a);
60
61
62 // normalise to -pi .. pi range
63 double ang1 = SGMiscd::normalizeAngle(crs13-crs12);
64 double ang2 = SGMiscd::normalizeAngle(crs21-crs23);
65
66 if ((sin(ang1) == 0.0) && (sin(ang2) == 0.0)) {
67 SG_LOG(SG_INSTR, SG_INFO, "geocRadialIntersection: infinity of intersections");
68 return false;
69 }
70
71 if ((sin(ang1)*sin(ang2))<0.0) {
72 // SG_LOG(SG_INSTR, SG_INFO, "geocRadialIntersection: intersection ambiguous:"
73 // << ang1 << " " << ang2 << " sin1 " << sin(ang1) << " sin2 " << sin(ang2));
74 return false;
75 }
76
77 ang1 = fabs(ang1);
78 ang2 = fabs(ang2);
79
80 //ang3=acos(-cos(ang1)*cos(ang2)+sin(ang1)*sin(ang2)*cos(dst12))
81 //dst13=atan2(sin(dst12)*sin(ang1)*sin(ang2),cos(ang2)+cos(ang1)*cos(ang3))
82 //lat3=asin(sin(lat1)*cos(dst13)+cos(lat1)*sin(dst13)*cos(crs13))
83
84 //lon3=mod(lon1-dlon+pi,2*pi)-pi
85
86 double ang3 = acos(-cos(ang1) * cos(ang2) + sin(ang1) * sin(ang2) * cosDst12);
87 double dst13 = atan2(sinDst12 * sin(ang1) * sin(ang2), cos(ang2) + cos(ang1)*cos(ang3));
88
89 SGGeoc pt3;
90 SGGeodesy::advanceRadM(a, crs13, dst13 * SG_RAD_TO_NM * SG_NM_TO_METER, pt3);
91
92 double lat3 = asin(sinLat1 * cos(dst13) + cosLat1 * sin(dst13) * cos(crs13));
93
94 //dlon=atan2(sin(crs13)*sin(dst13)*cos(lat1),cos(dst13)-sin(lat1)*sin(lat3))
95 double dlon = atan2(sin(crs13)*sin(dst13)*cosLat1, cos(dst13)- (sinLat1 * sin(lat3)));
96 double lon3 = SGMiscd::normalizeAngle(-a.getLongitudeRad()-dlon);
97
98 result = SGGeoc::fromRadM(-lon3, lat3, a.getRadiusM());
99 //result = pt3;
100 return true;
101 }
102}
103
104using namespace flightgear;
105
106// implement Point(s) known distance from a great circle
107
108static double sqr(const double x)
109{
110 return x * x;
111}
112
113// http://williams.best.vwh.net/avform.htm#POINTDME
114double pointsKnownDistanceFromGC(const SGGeoc& a, const SGGeoc&b, const SGGeoc& d, double dist)
115{
116 double A = SGGeodesy::courseRad(a, d) - SGGeodesy::courseRad(a, b);
117 double bDist = SGGeodesy::distanceRad(a, d);
118
119 // r=(cos(b)^2+sin(b)^2*cos(A)^2)^(1/2)
120 double r = pow(sqr(cos(bDist)) + sqr(sin(bDist)) * sqr(cos(A)), 0.5);
121
122 double p = atan2(sin(bDist)*cos(A), cos(bDist));
123
124 if (sqr(cos(dist)) > sqr(r)) {
125 SG_LOG(SG_NAVAID, SG_INFO, "pointsKnownDistanceFromGC, no points exist");
126 return -1.0;
127 }
128
129 double dp1 = p + acos(cos(dist)/r);
130 double dp2 = p - acos(cos(dist)/r);
131
132 double dp1Nm = fabs(dp1 * SG_RAD_TO_NM);
133 double dp2Nm = fabs(dp2 * SG_RAD_TO_NM);
134
135 return SGMiscd::min(dp1Nm, dp2Nm);
136}
137
138// http://williams.best.vwh.net/avform.htm#Int
139double latitudeForGCLongitude(const SGGeoc& a, const SGGeoc& b, double lon)
140{
141#if 0
142Intermediate points {lat,lon} lie on the great circle connecting points 1 and 2 when:
143
144lat=atan((sin(lat1)*cos(lat2)*sin(lon-lon2)
145 -sin(lat2)*cos(lat1)*sin(lon-lon1))/(cos(lat1)*cos(lat2)*sin(lon1-lon2)))
146#endif
147 double lonDiff = a.getLongitudeRad() - b.getLongitudeRad();
148 double cosLat1 = cos(a.getLatitudeRad()),
149 cosLat2 = cos(b.getLatitudeRad());
150 double x = sin(a.getLatitudeRad()) * cosLat2 * sin(lon - b.getLongitudeRad());
151 double y = sin(b.getLatitudeRad()) * cosLat1 * sin(lon - a.getLongitudeRad());
152 double denom = cosLat1 * cosLat2 * sin(lonDiff);
153 double lat = atan((x - y) / denom);
154 return lat;
155}
156
157static double magVarFor(const SGGeod& geod)
158{
159 double jd = globals->get_time_params()->getJD();
160 return sgGetMagVar(geod, jd) * SG_RADIANS_TO_DEGREES;
161}
162
163SGGeod turnCenterOverflight(const SGGeod& pt, double inHeadingDeg,
164 double turnAngleDeg, double turnRadiusM)
165{
166 double p = copysign(90.0, turnAngleDeg);
167 return SGGeodesy::direct(pt, inHeadingDeg + p, turnRadiusM);
168}
169
170SGGeod turnCenterFlyBy(const SGGeod& pt, double inHeadingDeg,
171 double turnAngleDeg, double turnRadiusM)
172{
173 double halfAngle = turnAngleDeg * 0.5;
174 double turnCenterOffset = turnRadiusM / cos(halfAngle * SG_DEGREES_TO_RADIANS);
175 double p = copysign(90.0, turnAngleDeg);
176 return SGGeodesy::direct(pt, inHeadingDeg + halfAngle + p, turnCenterOffset);
177}
178
179SGGeod turnCenterFromExit(const SGGeod& pt, double outHeadingDeg,
180 double turnAngleDeg, double turnRadiusM)
181{
182 double p = copysign(90.0, turnAngleDeg);
183 return SGGeodesy::direct(pt, outHeadingDeg + p, turnRadiusM);
184}
185
187{
189 double inboundCourseDeg = 0.0;
190 double turnAngleDeg = 0.0;
191 bool valid = false;
192};
193
199TurnInfo turnCenterAndAngleFromExit(const SGGeod& pt, double outHeadingDeg,
200 double turnRadiusM, const SGGeod&origin)
201{
202 double bearingToExit = SGGeodesy::courseDeg(origin, pt);
203 // not the final turn angle, but we need to know which side of the
204 // exit course we're on, to decide if it's a left-hand or right-hand turn
205 double turnAngle = outHeadingDeg - bearingToExit;
206 SG_NORMALIZE_RANGE(turnAngle, -180.0, 180.0);
207 double p = copysign(90.0, turnAngle);
208
209 TurnInfo r;
210 r.turnCenter = SGGeodesy::direct(pt, outHeadingDeg + p, turnRadiusM);
211
212 double courseToTC, distanceToTC, az2;
213 SGGeodesy::inverse(origin, r.turnCenter, courseToTC, az2, distanceToTC);
214 if (distanceToTC < turnRadiusM) {
215 SG_LOG(SG_NAVAID, SG_WARN, "turnCenterAndAngleFromExit: origin point too close to turn center");
216 return r;
217 }
218
219 // find additional course angle away from the exit pos to intersect
220 // the turn circle.
221 double theta = asin(turnRadiusM / distanceToTC) * SG_RADIANS_TO_DEGREES;
222 // invert angle sign so we increase the turn angle
223 theta = -copysign(theta, turnAngle);
224
225 r.inboundCourseDeg = courseToTC + theta;
226 SG_NORMALIZE_RANGE(r.inboundCourseDeg, 0.0, 360.0);
227
228 // turn angle must have same direction (sign) as turnAngle above, even if
229 // the turn radius means the sign would cross over (happens if origin point
230 // is close by
231 r.turnAngleDeg = outHeadingDeg - r.inboundCourseDeg;
232 if (r.turnAngleDeg > 0.0) {
233 if (turnAngle < 0.0) r.turnAngleDeg -= 360.0;
234 } else {
235 if (turnAngle > 0.0) r.turnAngleDeg += 360.0;
236 }
237
238 r.valid = true;
239 return r;
240}
241
242class WayptData;
243using WayptDataVec = std::vector<WayptData>;
244using WpDataIt = WayptDataVec::iterator;
245
247{
248public:
249 explicit WayptData(WayptRef w) :
250 wpt(w),
251 hasEntry(false),
252 posValid(false),
253 legCourseValid(false),
254 skipped(false),
255 turnEntryAngle(0.0),
256 turnExitAngle(0.0),
257 turnRadius(0.0),
258 legCourseTrue(0.0),
259 pathDistanceM(0.0),
262 flyOver(w->flag(WPT_OVERFLIGHT))
263 {
264 }
265
267 {
268 const std::string& ty(wpt->type());
269 if (wpt->flag(WPT_DYNAMIC)) {
270 // presumption is that we always overfly such a waypoint
271 if (ty == "hdgToAlt") {
272 flyOver = true;
273 }
274 } else if (ty == "discontinuity") {
275
276 } else {
277 pos = wpt->position();
278 posValid = true;
279 } // of static waypt
280 }
281
286 {
287 const std::string& ty(wpt->type());
288 return (ty == "hdgToAlt") || (ty == "radialIntercept") || (ty == "dmeIntercept");
289 }
290
291 // compute leg courses for all static legs (both ends are fixed)
292 void initPass1(const WayptData* previous, WayptData* next)
293 {
294 const auto ty = wpt->type();
295 if ((ty == "vectors") || (ty == "discontinuity")) {
296 // relying on the fact vectors will be followed by a static fix/wpt
297 if (next && next->posValid) {
298 posValid = true;
299 pos = next->pos;
300 }
301 }
302
303 const bool previousPosValid = previous && previous->posValid;
304 if (ty == "via") {
305 // even though both ends may be known, we don't
306 // want to compute a leg course for a VIA
307 } else if (posValid && !legCourseValid && previousPosValid) {
308 const auto prevWP = previous->wpt;
309 // check for duplicate points now
310 if (prevWP->matches(wpt)) {
311 skipped = true;
312 }
313
314 // we can compute leg course now
315 if (prevWP->type() == "runway") {
316 // use the runway departure end pos
317 FGRunway* rwy = static_cast<RunwayWaypt*>(prevWP.get())->runway();
318 legCourseTrue = SGGeodesy::courseDeg(rwy->end(), pos);
319 } else if (ty == "discontinuity") {
320 // we want to keep the path across DISCONs sharp (no turn entry/exit)
321 // so we keep the leg course invalid, even though we actually
322 // could compute it
323 } else if (ty != "runway") {
324 // need to wait to compute runway leg course
325 legCourseTrue = SGGeodesy::courseDeg(previous->pos, pos);
326 legCourseValid = true;
327 }
328 } // of not a VIA
329 }
330
331 void computeLegCourse(const WayptData* previous, double radiusM)
332 {
333 if (legCourseValid) {
334 return;
335 }
336
337 if ((wpt->type() == "via") || (wpt->type() == "discontinuty"))
338 {
339 // do nothing, we can't compute a valid leg course for these types
340 // we'll generate sharp turns in the path but that's no problem.
341 return;
342 }
343
344 if (!previous || !previous->posValid) {
345 // all the cases below assume previous pas exists and has a valid position
346 SG_LOG(SG_NAVAID, SG_WARN, "RoutePath: Asked to compute leg course, but, previous is invalid");
347 return;
348 }
349
350 if (wpt->type() == "runway") {
351 FGRunway* rwy = static_cast<RunwayWaypt*>(wpt.get())->runway();
352 flyOver = true;
353
355 rwy->headingDeg(),
356 radiusM, previous->pos);
357 if (ti.valid) {
361 turnRadius = radiusM;
362 hasEntry = true;
364 } else {
365 // couldn't compute entry, never mind
366 legCourseTrue = SGGeodesy::courseDeg(previous->pos, rwy->threshold());
367 }
368 legCourseValid = true;
369 return;
370 }
371
372 SGGeod previousPos = previous->pos;
373 // use correct location for runway, otherwise use threshold
374 // and get weird courses
375 if (previous->wpt->type() == "runway") {
376 FGRunway* rwy = static_cast<RunwayWaypt*>(previous->wpt.get())->runway();
377 previousPos = rwy->end();
378 }
379
380 if (posValid) {
381 legCourseTrue = SGGeodesy::courseDeg(previousPos, pos);
382 legCourseValid = true;
383 } else if (isCourseConstrained()) {
384 double magVar = magVarFor(previousPos);
385 legCourseTrue = wpt->headingRadialDeg() + magVar;
386 legCourseValid = true;
387 } // of pos not valid
388 }
389
390 SGGeod pointOnEntryTurnFromHeading(double headingDeg) const
391 {
392 assert(hasEntry);
393 double p = copysign(90.0, turnEntryAngle);
394 return SGGeodesy::direct(turnEntryCenter, headingDeg - p, turnRadius);
395 }
396
397 SGGeod pointOnExitTurnFromHeading(double headingDeg) const
398 {
399 double p = copysign(90.0, turnExitAngle);
400 return SGGeodesy::direct(turnExitCenter, headingDeg - p, turnRadius);
401 }
402
403 double pathDistanceForTurnAngle(double angleDeg) const
404 {
405 return turnRadius * fabs(angleDeg) * SG_DEGREES_TO_RADIANS;
406 }
407
408 void computeTurn(double radiusM, bool constrainLegCourse, double maxFlyByTurnAngleDeg, WayptData& next)
409 {
410 assert(!skipped);
411 assert(next.legCourseValid);
412 bool isRunway = (wpt->type() == "runway");
413
414 if (legCourseValid) {
415 if (isRunway) {
416 FGRunway* rwy = static_cast<RunwayWaypt*>(wpt.get())->runway();
417 turnExitAngle = next.legCourseTrue - rwy->headingDeg();
418 } else {
420 }
421 } else {
422 // happens for first leg
423 if (isRunway) {
424 legCourseValid = true;
425 FGRunway* rwy = static_cast<RunwayWaypt*>(wpt.get())->runway();
426 turnExitAngle = next.legCourseTrue - rwy->headingDeg();
427 legCourseTrue = rwy->headingDeg();
428 flyOver = true;
429 // fall through
430 } else {
431 legCourseValid = true;
433 turnExitAngle = 0.0;
435 flyOver = true;
436 return;
437 }
438 }
439
440 SG_NORMALIZE_RANGE(turnExitAngle, -180.0, 180.0);
441 turnRadius = radiusM;
442
443 if (!flyOver && fabs(turnExitAngle) > maxFlyByTurnAngleDeg) {
444 // flyBy logic blows up for sharp turns - due to the tan() term
445 // heading towards infinity. By converting to flyOver we do something
446 // closer to what was requested. This matches logic in the RNAV
447 // Leg controller
448 flyOver = true;
449 }
450
451 if (flyOver) {
452 if (isRunway) {
453 FGRunway* rwy = static_cast<RunwayWaypt*>(wpt.get())->runway();
455 } else {
458 }
460
461 if (!next.wpt->flag(WPT_DYNAMIC)) {
462 // distance perpendicular to next leg course, after turning
463 // through turnAngle
464 double xtk = turnRadius * (1 - cos(turnExitAngle * SG_DEGREES_TO_RADIANS));
465
466 if (constrainLegCourse || next.isCourseConstrained()) {
467 // next leg course is constrained. We need to swing back onto the
468 // desired course, using a compensation turn
469
470 // compensation angle to turn back on course
471 double theta = acos((turnRadius - (xtk * 0.5)) / turnRadius) * SG_RADIANS_TO_DEGREES;
472 theta = copysign(theta, turnExitAngle);
473 turnExitAngle += theta;
474
475 double p = copysign(90, turnExitAngle);
476 const double offsetAngle = legCourseTrue + turnExitAngle - p;
477 SGGeod tc2 = SGGeodesy::direct(turnExitCenter,
478 offsetAngle,
479 turnRadius * 2.0);
480
481
482 turnExitPos = SGGeodesy::direct(tc2, next.legCourseTrue + p , turnRadius);
484
485 // sign of angles will differ, so compute distances seperately
488 } else {
489 // next leg course can be adjusted. increase the turn angle
490 // and modify the next leg's course accordingly.
491
492 // hypotenuse of triangle, opposite edge has length turnRadius
493 double distAlongPath = std::min(1.0, sin(fabs(turnExitAngle) * SG_DEGREES_TO_RADIANS)) * turnRadius;
494 double nextLegDistance = SGGeodesy::distanceM(pos, next.pos) - distAlongPath;
495 double increaseAngle = atan2(xtk, nextLegDistance) * SG_RADIANS_TO_DEGREES;
496 increaseAngle = copysign(increaseAngle, turnExitAngle);
497
498 turnExitAngle += increaseAngle;
500 // modify next leg course
501 next.legCourseTrue = SGGeodesy::courseDeg(turnExitPos, next.pos);
503 } // of next leg isn't course constrained
504 } else {
505 // next point is dynamic
506 // no compensation needed
508 }
509 } else {
510 hasEntry = true;
512
515 turnExitCenter = turnEntryCenter; // important that these match
516
520 }
521 }
522
523 double turnDistanceM() const
524 {
525 return turnPathDistanceM;
526 }
527
528 void turnEntryPath(SGGeodVec& path) const
529 {
530 if (!hasEntry || fabs(turnEntryAngle) < 0.5 ) {
531 path.push_back(pos);
532 return;
533 }
534
535 int steps = std::max(SGMiscd::roundToInt(fabs(turnEntryAngle) / 3.0), 1);
536 double stepIncrement = turnEntryAngle / steps;
537 double h = legCourseTrue;
538 for (int s=0; s<steps; ++s) {
539 path.push_back(pointOnEntryTurnFromHeading(h));
540 h += stepIncrement;
541 }
542 }
543
544 void turnExitPath(SGGeodVec& path) const
545 {
546 if (fabs(turnExitAngle) < 0.5) {
547 path.push_back(turnExitPos);
548 return;
549 }
550
551 int steps = std::max(SGMiscd::roundToInt(fabs(turnExitAngle) / 3.0), 1);
552 double stepIncrement = turnExitAngle / steps;
553
554 // initial exit heading
555 double h = legCourseTrue + (flyOver ? 0.0 : turnEntryAngle);
556 if (wpt->type() == "runway") {
557 FGRunway* rwy = static_cast<RunwayWaypt*>(wpt.get())->runway();
558 h = rwy->headingDeg();
559 }
560
561 for (int s=0; s<steps; ++s) {
562 path.push_back(pointOnExitTurnFromHeading(h));
563 h += stepIncrement;
564 }
565
566 // we can use an exact check on the compensation angle, because we
567 // initialise it directly. Depending on the next element we might be
568 // doing compensation or adjusting the next leg's course; if we did
569 // adjust the course everything 'just works' above.
570
571 if (flyOver && (overflightCompensationAngle != 0.0)) {
572 // skew by compensation angle back
573 steps = std::max(SGMiscd::roundToInt(fabs(overflightCompensationAngle) / 3.0), 1);
574
575 // step in opposite direction to the turn angle to swing back onto
576 // the next leg course
577 stepIncrement = overflightCompensationAngle / steps;
578
579 SGGeod p = path.back();
580 double stepDist = (fabs(stepIncrement) / 360.0) * SGMiscd::twopi() * turnRadius;
581
582 for (int s=0; s<steps; ++s) {
583 h += stepIncrement;
584 p = SGGeodesy::direct(p, h, stepDist);
585 path.push_back(p);
586
587 }
588 } // of overflight compensation turn
589 }
590
591 SGGeod pointAlongExitPath(double distanceM) const
592 {
593 double basicTurnDistance = pathDistanceForTurnAngle(turnExitAngle);
594
595 if (distanceM > basicTurnDistance) {
596 assert(flyOver);
597 assert(overflightCompensationAngle != 0.0);
598 double p = copysign(90, turnExitAngle);
599
600 double distanceAlongCompensationTurn = distanceM - basicTurnDistance;
601 double theta = (distanceAlongCompensationTurn / turnRadius) * SG_RADIANS_TO_DEGREES;
602
603 // compute the compensation turn center - twice the turn radius
604 // from turnCenter
605 SGGeod tc2 = SGGeodesy::direct(turnExitCenter,
607 turnRadius * 2.0);
608
609 theta = copysign(theta, overflightCompensationAngle);
610 return SGGeodesy::direct(tc2,
612 }
613
614 double theta = (distanceM / turnRadius) * SG_RADIANS_TO_DEGREES;
615 theta = copysign(theta, turnExitAngle);
616 double inboundCourse = legCourseTrue + (flyOver ? 0.0 : turnExitAngle);
617 return pointOnExitTurnFromHeading(inboundCourse + theta);
618 }
619
620 SGGeod pointAlongEntryPath(double distanceM) const
621 {
622 assert(hasEntry);
623 double theta = (distanceM / turnRadius) * SG_RADIANS_TO_DEGREES;
624 theta = copysign(theta, turnEntryAngle);
626 }
627
633 double turnPathDistanceM; // for flyBy, this is half the distance; for flyOver it's the complete distance
636};
637
639{
640 return (wpt->flag(WPT_APPROACH) && !wpt->flag(WPT_MISS)) || wpt->flag(WPT_ARRIVAL);
641}
642
644{
645public:
647
650 double maxFlyByTurnAngleDeg = 90.0;
651
653 {
654 auto previous(previousValidWaypoint(index));
655 if ((previous == waypoints.end()) || !previous->posValid) {
656 SG_LOG(SG_NAVAID, SG_WARN, "couldn't compute position for dynamic waypoint: no preceeding valid waypoint");
657 return;
658 }
659
660 WayptRef wpt = waypoints[index].wpt;
661
662 const std::string& ty(wpt->type());
663 if (ty == "hdgToAlt") {
664 HeadingToAltitude* h = (HeadingToAltitude*)wpt.get();
665
666 double altFt = computeVNAVAltitudeFt(index - 1);
667 double distanceM = perf.distanceNmBetween(altFt, h->altitudeFt()) * SG_NM_TO_METER;
668 double hdg = h->headingDegMagnetic() + magVarFor(previous->pos);
669 waypoints[index].pos = SGGeodesy::direct(previous->turnExitPos, hdg, distanceM);
670 waypoints[index].posValid = true;
671 } else if (ty == "radialIntercept") {
672 // start from previous.turnExit
673 RadialIntercept* i = (RadialIntercept*)wpt.get();
674
675 SGGeoc prevGc = SGGeoc::fromGeod(previous->turnExitPos);
676 SGGeoc navid = SGGeoc::fromGeod(wpt->position());
677 SGGeoc rGc;
678 double magVar = magVarFor(previous->pos);
679
680 double radial = i->radialDegMagnetic() + magVar;
681 double track = i->courseDegMagnetic() + magVar;
682 bool ok = geocRadialIntersection(prevGc, track, navid, radial, rGc);
683 if (!ok) {
684 // try pulling backward along the radial in case we're too close.
685 // suggests bad procedure construction if this is happening!
686 SGGeoc navidAdjusted = SGGeodesy::advanceDegM(navid, radial, -10 * SG_NM_TO_METER);
687
688 // try again
689 ok = geocRadialIntersection(prevGc, track, navidAdjusted, radial, rGc);
690 if (!ok) {
691 SG_LOG(SG_NAVAID, SG_WARN, "couldn't compute interception for radial:" << previous->turnExitPos << " / " << track << "/" << wpt->position() << "/" << radial);
692 waypoints[index].pos = wpt->position(); // horrible fallback
693
694 } else {
695 waypoints[index].pos = SGGeod::fromGeoc(rGc);
696 }
697 } else {
698 waypoints[index].pos = SGGeod::fromGeoc(rGc);
699 }
700
701 waypoints[index].posValid = true;
702 } else if (ty == "dmeIntercept") {
703 DMEIntercept* di = (DMEIntercept*)wpt.get();
704
705 SGGeoc prevGc = SGGeoc::fromGeod(previous->turnExitPos);
706 SGGeoc navid = SGGeoc::fromGeod(wpt->position());
707 double distRad = di->dmeDistanceNm() * SG_NM_TO_RAD;
708 SGGeoc rGc;
709
710 double crs = di->courseDegMagnetic() + magVarFor(wpt->position());
711 SGGeoc bPt = SGGeodesy::advanceDegM(prevGc, crs, 1e5);
712
713 double dNm = pointsKnownDistanceFromGC(prevGc, bPt, navid, distRad);
714 if (dNm < 0.0) {
715 SG_LOG(SG_NAVAID, SG_WARN, "dmeIntercept failed");
716 waypoints[index].pos = wpt->position(); // horrible fallback
717 } else {
718 waypoints[index].pos = SGGeodesy::direct(previous->turnExitPos, crs, dNm * SG_NM_TO_METER);
719 }
720
721 waypoints[index].posValid = true;
722 } else if (ty == "vectors") {
723 waypoints[index].legCourseTrue = SGGeodesy::courseDeg(previous->turnExitPos, waypoints[index].pos);
724 waypoints[index].legCourseValid = true;
725 // no turn data
726 }
727 }
728
729 double computeVNAVAltitudeFt(int index)
730 {
731 WayptRef w = waypoints[index].wpt;
732 if ((w->flag(WPT_APPROACH) && !w->flag(WPT_MISS)) || w->flag(WPT_ARRIVAL)) {
733 // descent
734 int next = findNextKnownAltitude(index);
735 if (next < 0) {
736 return 0.0;
737 }
738
739 double fixedAlt = altitudeForIndex(next);
740 double distanceM = distanceBetweenIndices(index, next);
741 return perf.computePreviousAltitude(distanceM, fixedAlt);
742 } else {
743 // climb
744 int prev = findPreceedingKnownAltitude(index);
745 if (prev < 0) {
746 return 0.0;
747 }
748
749 double fixedAlt = altitudeForIndex(prev);
750 double distanceM = distanceBetweenIndices(prev, index);
751 return perf.computeNextAltitude(distanceM, fixedAlt);
752 }
753 }
754
755 int findPreceedingKnownAltitude(int index) const
756 {
757 const WayptData& w(waypoints[index]);
758 if (w.wpt->altitudeRestriction() == RESTRICT_AT) {
759 return index;
760 }
761
762 // principal base case is runways.
763 const std::string& ty(w.wpt->type());
764 if (ty == "runway") {
765 return index; // runway always has a known elevation
766 }
767
768 if (index == 0) {
769 SG_LOG(SG_NAVAID, SG_WARN, "findPreceedingKnownAltitude: no preceding altitude value found");
770 return -1;
771 }
772
773 // recurse earlier in the route
774 return findPreceedingKnownAltitude(index - 1);
775 }
776
777 int findNextKnownAltitude(unsigned int index) const
778 {
779 if (index >= waypoints.size()) {
780 SG_LOG(SG_NAVAID, SG_WARN, "findNextKnownAltitude: no next altitude value found");
781 return -1;
782 }
783
784 const WayptData& w(waypoints[index]);
785 if (w.wpt->altitudeRestriction() == RESTRICT_AT) {
786 return index;
787 }
788
789 // principal base case is runways.
790 const std::string& ty(w.wpt->type());
791 if (ty == "runway") {
792 return index; // runway always has a known elevation
793 }
794
795 if (index == waypoints.size() - 1) {
796 SG_LOG(SG_NAVAID, SG_WARN, "findNextKnownAltitude: no next altitude value found");
797 return -1;
798 }
799
800 return findNextKnownAltitude(index + 1);
801 }
802
803 double altitudeForIndex(int index) const
804 {
805 const WayptData& w(waypoints[index]);
806 if (w.wpt->altitudeRestriction() != RESTRICT_NONE) {
807 return w.wpt->altitudeFt();
808 }
809
810 const std::string& ty(w.wpt->type());
811 if (ty == "runway") {
812 FGRunway* rwy = static_cast<RunwayWaypt*>(w.wpt.get())->runway();
813 return rwy->threshold().getElevationFt();
814 }
815
816 SG_LOG(SG_NAVAID, SG_WARN, "altitudeForIndex: waypoint has no explicit altitude");
817 return 0.0;
818 }
819
820 double distanceBetweenIndices(int from, int to) const
821 {
822 double total = 0.0;
823
824 for (int i=from+1; i<= to; ++i) {
825 total += waypoints[i].pathDistanceM;
826 }
827
828 return total;
829 }
830
831 WayptDataVec::iterator previousValidWaypoint(unsigned int index)
832 {
833 do {
834 if (index == 0) {
835 return waypoints.end();
836 }
837
838 --index;
839 } while (waypoints.at(index).skipped || (waypoints.at(index).wpt->type() == "discontinuity"));
840
841 return waypoints.begin() + index;
842 }
843
844 WayptDataVec::iterator previousValidWaypoint(WayptDataVec::iterator it)
845 {
846 return previousValidWaypoint(std::distance(waypoints.begin(), it));
847 }
848
849
850 WayptDataVec::iterator nextValidWaypoint(int index)
851 {
852 return nextValidWaypoint(waypoints.begin() + index);
853 }
854
855 WayptDataVec::iterator nextValidWaypoint(WayptDataVec::iterator it)
856 {
857 if (it == waypoints.end()) {
858 return it;
859 }
860
861 ++it;
862 while ((it != waypoints.end()) && (it->skipped || (it->wpt->type() == "discontinuity"))) {
863 ++it;
864 }
865 return it;
866 }
867}; // of RoutePathPrivate class
868
870 d(new RoutePathPrivate)
871{
872 for (int l=0; l<fp->numLegs(); ++l) {
873 WayptRef wpt = fp->legAtIndex(l)->waypoint();
874 if (!wpt) {
875 SG_LOG(SG_NAVAID, SG_DEV_ALERT, "Waypoint " << l << " of " << fp->numLegs() << "is NULL");
876 break;
877 }
878 d->waypoints.push_back(WayptData(wpt));
879 }
880
881 d->constrainLegCourses = fp->followLegTrackToFixes();
882 commonInit();
883}
884
885
887{
888 d.reset(new RoutePathPrivate(*other.d));
889}
890
892{
893 d.reset(new RoutePathPrivate(*other.d));
894 return *this;
895}
896
900
901void RoutePath::commonInit()
902{
903 for (auto& w : d->waypoints) {
904 w.initPass0();
905 }
906
907 for (unsigned int i=1; i<d->waypoints.size(); ++i) {
908 WayptData* nextPtr = ((i + 1) < d->waypoints.size()) ? &d->waypoints[i+1] : nullptr;
909 auto prev = d->previousValidWaypoint(i);
910 WayptData* prevPtr = (prev == d->waypoints.end()) ? nullptr : &(*prev);
911 d->waypoints[i].initPass1(prevPtr, nextPtr);
912 }
913
914 for (unsigned int i=0; i<d->waypoints.size(); ++i) {
915 if (d->waypoints[i].skipped) {
916 continue;
917 }
918
919 double alt = 0.0; // FIXME
920 double radiusM = d->perf.turnRadiusMForAltitude(alt);
921
922 if (i > 0) {
923 auto prevIt = d->previousValidWaypoint(i);
924 WayptData* prevPtr = (prevIt == d->waypoints.end()) ? nullptr : &(*prevIt);
925
926 d->waypoints[i].computeLegCourse(prevPtr, radiusM);
927 d->computeDynamicPosition(i);
928 }
929
930 auto nextIt = d->nextValidWaypoint(i);
931 if (nextIt != d->waypoints.end()) {
932 nextIt->computeLegCourse(&(d->waypoints[i]), radiusM);
933
934 if (nextIt->legCourseValid) {
935 d->waypoints[i].computeTurn(radiusM, d->constrainLegCourses, d->maxFlyByTurnAngleDeg, *nextIt);
936 } else {
937 // next waypoint has indeterminate course. Let's create a sharp turn
938 // this can happen when the following point is ATC vectors, for example.
939 d->waypoints[i].turnEntryPos = d->waypoints[i].pos;
940 d->waypoints[i].turnExitPos = d->waypoints[i].pos;
941 }
942 } else {
943 // final waypt, fix up some data
944 d->waypoints[i].turnExitPos = d->waypoints[i].pos;
945 d->waypoints[i].turnEntryPos = d->waypoints[i].pos;
946 }
947
948 // now turn is computed, can resolve distances
949 d->waypoints[i].pathDistanceM = computeDistanceForIndex(i);
950 }
951}
952
954{
955 if ((index < 0) || (index >= static_cast<int>(d->waypoints.size()))) {
956 return {};
957 }
958
959 const WayptData& w(d->waypoints[index]);
960 const std::string& ty(w.wpt->type());
961 SGGeodVec r;
962
963 if (d->waypoints[index].skipped) {
964 return {};
965 }
966
967 // don't show any path
968 if (w.wpt->flag(WPT_HIDDEN)) {
969 return {};
970 }
971
972 // also hide the path if the prev WP is hidden
973 auto prevIt = d->previousValidWaypoint(index);
974 if (prevIt != d->waypoints.end()) {
975 if (prevIt->wpt->flag(WPT_HIDDEN)) {
976 return {};
977 }
978 }
979
980 if (ty == "vectors") {
981 // ideally we'd show a stippled line to connect the route?
982 return {};
983 }
984
985 if (ty == "discontinuity") {
986 return {}; // no points for a discontinuity of course
987 }
988
989 if (ty == "via") {
990 return pathForVia(static_cast<Via*>(d->waypoints[index].wpt.get()), index);
991 }
992
993
994 if (prevIt != d->waypoints.end()) {
995 prevIt->turnExitPath(r);
996
997 SGGeod from = prevIt->turnExitPos,
998 to = w.turnEntryPos;
999
1000 // compute rounding offset, we want to round towards the direction of travel
1001 // which depends on the east/west sign of the longitude change
1002 double lonDelta = to.getLongitudeDeg() - from.getLongitudeDeg();
1003 if (fabs(lonDelta) > 0.5) {
1004 interpolateGreatCircle(from, to, r);
1005 }
1006 } // of have previous waypoint
1007
1008 w.turnEntryPath(r);
1009
1010 // hold is the normal leg and then the hold waypoints as well
1011 if (ty== "hold") {
1012 const auto h = static_cast<Hold*>(d->waypoints[index].wpt.get());
1013 const auto holdPath = pathForHold(h);
1014 r.insert(r.end(), holdPath.begin(), holdPath.end());
1015 }
1016
1017 if (ty == "runway") {
1018 // runways get an extra point, at the end. this is particularly
1019 // important so missed approach segments draw correctly
1020 FGRunway* rwy = static_cast<RunwayWaypt*>(w.wpt.get())->runway();
1021 r.push_back(rwy->end());
1022 }
1023
1024 return r;
1025}
1026
1027void RoutePath::interpolateGreatCircle(const SGGeod& aFrom, const SGGeod& aTo, SGGeodVec& r) const
1028{
1029 SGGeoc gcFrom = SGGeoc::fromGeod(aFrom),
1030 gcTo = SGGeoc::fromGeod(aTo);
1031
1032 double lonDelta = gcTo.getLongitudeRad() - gcFrom.getLongitudeRad();
1033 if (fabs(lonDelta) < 1e-3) {
1034 return;
1035 }
1036
1037 lonDelta = SGMiscd::normalizeAngle(lonDelta);
1038 int steps = static_cast<int>(fabs(lonDelta) * SG_RADIANS_TO_DEGREES * 2);
1039 double lonStep = (lonDelta / steps);
1040
1041 double lon = gcFrom.getLongitudeRad() + lonStep;
1042 for (int s=0; s < (steps - 1); ++s) {
1043 lon = SGMiscd::normalizeAngle(lon);
1044 double lat = latitudeForGCLongitude(gcFrom, gcTo, lon);
1045 r.push_back(SGGeod::fromGeoc(SGGeoc::fromRadM(lon, lat, SGGeodesy::EQURAD)));
1046 //SG_LOG(SG_GENERAL, SG_INFO, "lon:" << lon * SG_RADIANS_TO_DEGREES << " gives lat " << lat * SG_RADIANS_TO_DEGREES);
1047 lon += lonStep;
1048 }
1049}
1050
1051SGGeod RoutePath::positionForIndex(int index) const
1052{
1053 if ((index < 0) || (index >= static_cast<int>(d->waypoints.size()))) {
1054 return {};
1055 }
1056
1057 return d->waypoints[index].pos;
1058}
1059
1060SGGeodVec RoutePath::pathForVia(Via* via, int index) const
1061{
1062 // previous waypoint must be valid for a VIA
1063 auto prevIt = d->previousValidWaypoint(index);
1064 if (prevIt == d->waypoints.end()) {
1065 return SGGeodVec();
1066 }
1067
1068 WayptVec enrouteWaypoints = via->expandToWaypoints(prevIt->wpt);
1069 SGGeodVec r;
1070
1071 WayptVec::const_iterator it;
1072 SGGeod legStart = prevIt->wpt->position();
1073 for (it = enrouteWaypoints.begin(); it != enrouteWaypoints.end(); ++it) {
1074 // interpolate directly into the result vector
1075 interpolateGreatCircle(legStart, (*it)->position(), r);
1076 legStart = (*it)->position();
1077 }
1078
1079 return r;
1080}
1081
1082SGGeodVec RoutePath::pathForHold(Hold* hold) const
1083{
1084 int turnSteps = 16;
1085 double hdg = hold->inboundRadial();
1086 double turnDelta = 180.0 / turnSteps;
1087 double altFt = 0.0; // FIXME
1088 double gsKts = d->perf.groundSpeedForAltitudeKnots(altFt);
1089
1090 SGGeodVec r;
1091 double az2;
1092 double stepTime = turnDelta / 3.0; // 3.0 is degrees/sec for standard rate turn
1093 double stepDist = gsKts * SG_KT_TO_MPS * stepTime;
1094 double legDist = hold->isDistance() ?
1095 hold->timeOrDistance()
1096 : gsKts * (hold->timeOrDistance() / 3600.0);
1097 legDist *= SG_NM_TO_METER;
1098
1099 if (hold->isLeftHanded()) {
1100 turnDelta = -turnDelta;
1101 }
1102 SGGeod pos = hold->position();
1103 r.push_back(pos);
1104
1105 // turn+leg sides are a mirror
1106 for (int j=0; j < 2; ++j) {
1107 // turn
1108 for (int i=0;i<turnSteps; ++i) {
1109 hdg += turnDelta;
1110 SGGeodesy::direct(pos, hdg, stepDist, pos, az2);
1111 r.push_back(pos);
1112 }
1113
1114 // leg
1115 SGGeodesy::direct(pos, hdg, legDist, pos, az2);
1116 r.push_back(pos);
1117 } // of leg+turn duplication
1118
1119 return r;
1120}
1121
1122double RoutePath::computeDistanceForIndex(int index) const
1123{
1124 if ((index < 0) || (index >= static_cast<int>(d->waypoints.size()))) {
1125 return 0.0;
1126 }
1127
1128 auto it = d->waypoints.begin() + index;
1129 if ((index == 0) || it->skipped) {
1130 // first waypoint, distance is 0
1131 return 0.0;
1132 }
1133
1134 const auto ty = it->wpt->type();
1135 if (ty == "via") {
1136 return distanceForVia(static_cast<Via*>(it->wpt.get()), index);
1137 }
1138
1139 if (ty == "discontinuity") {
1140 return 0.0;
1141 }
1142
1143 auto prevIt = d->previousValidWaypoint(index);
1144 if (prevIt == d->waypoints.end()) {
1145 return 0.0;
1146 }
1147
1148 double dist = SGGeodesy::distanceM(prevIt->turnExitPos, it->turnEntryPos);
1149 dist += prevIt->turnDistanceM();
1150
1151 if (!it->flyOver) {
1152 // add entry distance
1153 dist += it->turnDistanceM();
1154 }
1155
1156 return dist;
1157}
1158
1159double RoutePath::distanceForVia(Via* via, int index) const
1160{
1161 auto prevIt = d->previousValidWaypoint(index);
1162 if (prevIt == d->waypoints.end()) {
1163 return 0.0;
1164 }
1165
1166 WayptVec enrouteWaypoints = via->expandToWaypoints(prevIt->wpt);
1167 double dist = 0.0;
1168
1169 SGGeod legStart = prevIt->wpt->position();
1170 for (auto wp : enrouteWaypoints) {
1171 dist += SGGeodesy::distanceM(legStart, wp->position());
1172 legStart = wp->position();
1173 }
1174
1175 return dist;
1176}
1177
1178double RoutePath::trackForIndex(int index) const
1179{
1180 const auto sz = static_cast<int>(d->waypoints.size());
1181 if ((index < 0) || (index >= sz)) {
1182 return 0.0;
1183 }
1184
1185 if (d->waypoints[index].skipped)
1186 return trackForIndex(index - 1);
1187
1188 const WayptData& wd(d->waypoints[index]);
1189 if (!wd.legCourseValid)
1190 return 0.0;
1191
1192 return wd.legCourseTrue;
1193}
1194
1195double RoutePath::distanceForIndex(int index) const
1196{
1197 const auto sz = static_cast<int>(d->waypoints.size());
1198 if ((index < 0) || (index >= sz)) {
1199 return 0.0;
1200 }
1201
1202 return d->waypoints[index].pathDistanceM;
1203}
1204
1205double RoutePath::distanceBetweenIndices(int from, int to) const
1206{
1207 if (from < 0) {
1208 from = 0;
1209 }
1210
1211 const auto sz = static_cast<int>(d->waypoints.size());
1212 if (to >= sz) {
1213 to = sz - 1;
1214 }
1215
1216 return d->distanceBetweenIndices(from, to);
1217}
1218
1219SGGeod RoutePath::positionForDistanceFrom(int index, double distanceM) const
1220{
1221 int sz = (int) d->waypoints.size();
1222 if (index < 0) {
1223 index = sz - 1; // map negative values to end of the route
1224 }
1225
1226 if ((index < 0) || (index >= sz)) {
1227 throw sg_range_exception("waypt index out of range",
1228 "RoutePath::positionForDistanceFrom");
1229 }
1230
1231 // find the actual leg we're within
1232 if (distanceM < 0.0) {
1233 // scan backwards
1234 while ((index > 0) && (distanceM < 0.0)) {
1235 // we are looking at index n, say 4, but with a negative distance.
1236 // we want to look at index n-1 (so, 3), and see if this makes
1237 // distance positive. We need to offset by distance from 3 -> 4,
1238 // which is waypoint 4's path distance.
1239
1240 // note pathDistanceM is 0 for skipped waypoints, so this works out
1241 distanceM += d->waypoints[index].pathDistanceM;
1242 --index;
1243 }
1244
1245 if (distanceM < 0.0) {
1246 // still negative, return route start
1247 return d->waypoints[0].pos;
1248 }
1249
1250 } else {
1251 // scan forwards
1252 int nextIndex = index + 1;
1253 while ((nextIndex < sz) && (d->waypoints[nextIndex].pathDistanceM < distanceM)) {
1254 distanceM -= d->waypoints[nextIndex].pathDistanceM;
1255 index = nextIndex++;
1256 }
1257 }
1258
1259 auto nextIt = d->nextValidWaypoint(index);
1260 if (nextIt == d->waypoints.end()) {
1261 // past route end, just return final position
1262 return d->waypoints[sz - 1].pos;
1263 }
1264
1265 // this is important so we start from a valid WP if we're
1266 // working either side of a DISCON
1267 auto curIt = d->previousValidWaypoint(nextIt);
1268 if (curIt == d->waypoints.end()) {
1269 SG_LOG(SG_NAVAID, SG_WARN, "Couldn't find valid preceeding waypoint " << index);
1270 return nextIt->pos;
1271 }
1272
1273 const WayptData& wpt = *curIt;
1274 const WayptData& next = *nextIt;
1275
1276 if (next.wpt->type() == "via") {
1277 return positionAlongVia(static_cast<Via*>(next.wpt.get()), index, distanceM);
1278 }
1279
1280 if (wpt.turnPathDistanceM > distanceM) {
1281 // on the exit path of current wpt
1282 return wpt.pointAlongExitPath(distanceM);
1283 } else {
1284 distanceM -= wpt.turnPathDistanceM;
1285 }
1286
1287 double corePathDistance = next.pathDistanceM - next.turnPathDistanceM;
1288 if (next.hasEntry && (distanceM > corePathDistance)) {
1289 // on the entry path of next waypoint
1290 return next.pointAlongEntryPath(distanceM - corePathDistance);
1291 }
1292
1293 // linear between turn exit and turn entry points
1294 return SGGeodesy::direct(wpt.turnExitPos, next.legCourseTrue, distanceM);
1295}
1296
1297SGGeod RoutePath::positionAlongVia(Via* via, int previousIndex, double distanceM) const
1298{
1299 SG_LOG(SG_NAVAID, SG_ALERT, "RoutePath::positionAlongVia not implemented");
1300 return SGGeod();
1301}
#define p(x)
std::vector< SGGeod > SGGeodVec
#define i(x)
double headingDeg() const
Runway heading in degrees.
SGGeod end() const
Get the 'far' end - this is equivalent to calling pointOnCenterline(lengthFt());.
Definition runways.cxx:94
SGGeod threshold() const
Get the (possibly displaced) threshold point.
Definition runways.cxx:99
WayptDataVec::iterator nextValidWaypoint(int index)
double distanceBetweenIndices(int from, int to) const
int findPreceedingKnownAltitude(int index) const
void computeDynamicPosition(int index)
AircraftPerformance perf
int findNextKnownAltitude(unsigned int index) const
WayptDataVec::iterator nextValidWaypoint(WayptDataVec::iterator it)
WayptDataVec::iterator previousValidWaypoint(unsigned int index)
double computeVNAVAltitudeFt(int index)
double altitudeForIndex(int index) const
WayptDataVec::iterator previousValidWaypoint(WayptDataVec::iterator it)
flightgear::SGGeodVec pathForIndex(int index) const
SGGeod positionForIndex(int index) const
RoutePath & operator=(const RoutePath &other)
RoutePath(const flightgear::FlightPlan *fp)
SGGeod positionForDistanceFrom(int index, double distanceM) const
double distanceForIndex(int index) const
double trackForIndex(int index) const
double distanceBetweenIndices(int from, int to) const
double turnDistanceM() const
void initPass1(const WayptData *previous, WayptData *next)
SGGeod pointOnExitTurnFromHeading(double headingDeg) const
void computeTurn(double radiusM, bool constrainLegCourse, double maxFlyByTurnAngleDeg, WayptData &next)
double legCourseTrue
double turnExitAngle
double pathDistanceForTurnAngle(double angleDeg) const
double turnRadius
SGGeod pos
WayptData(WayptRef w)
SGGeod turnEntryPos
double overflightCompensationAngle
void initPass0()
double turnPathDistanceM
SGGeod pointAlongExitPath(double distanceM) const
double pathDistanceM
SGGeod turnEntryCenter
SGGeod pointOnEntryTurnFromHeading(double headingDeg) const
bool posValid
WayptRef wpt
SGGeod turnExitCenter
bool hasEntry
double turnEntryAngle
bool isCourseConstrained() const
test if course of this leg can be adjusted or is contrained to an exact value
SGGeod turnExitPos
void turnEntryPath(SGGeodVec &path) const
SGGeod pointAlongEntryPath(double distanceM) const
bool legCourseValid
void computeLegCourse(const WayptData *previous, double radiusM)
void turnExitPath(SGGeodVec &path) const
Calculate flight parameter based on aircraft performance data.
virtual SGGeod position() const
Definition waypoint.hxx:39
double dmeDistanceNm() const
Definition waypoint.hxx:241
double courseDegMagnetic() const
Definition waypoint.hxx:238
bool followLegTrackToFixes() const
LegRef legAtIndex(int index) const
double headingDegMagnetic() const
Definition waypoint.hxx:205
double timeOrDistance() const
Definition waypoint.hxx:167
bool isLeftHanded() const
Definition waypoint.hxx:161
double inboundRadial() const
Definition waypoint.hxx:158
bool isDistance() const
Definition waypoint.hxx:164
Waypoint based upon a runway.
Definition waypoint.hxx:117
FGRunway * runway() const
Definition waypoint.hxx:129
WayptVec expandToWaypoints(WayptRef aPreceeding) const
Definition waypoint.cxx:625
double altitudeFt() const
Definition route.cxx:289
FGGlobals * globals
Definition globals.cxx:142
#define A
FlightPlan.hxx - defines a full flight-plan object, including departure, cruise, arrival information ...
Definition Addon.cxx:53
std::vector< SGGeod > SGGeodVec
Definition PolyLine.hxx:36
SGSharedPtr< Waypt > WayptRef
bool geocRadialIntersection(const SGGeoc &a, double r1, const SGGeoc &b, double r2, SGGeoc &result)
Definition routePath.cxx:39
@ WPT_DYNAMIC
waypoint position is dynamic, i.e moves based on other criteria, such as altitude,...
Definition route.hxx:49
@ WPT_MISS
segment is part of missed approach
Definition route.hxx:46
@ WPT_HIDDEN
waypoint should not be shown in UI displays, etc this is used to implement FMSs which delete waypoint...
Definition route.hxx:67
@ WPT_APPROACH
Definition route.hxx:60
@ WPT_ARRIVAL
Definition route.hxx:55
@ WPT_OVERFLIGHT
must overfly the point directly
Definition route.hxx:44
std::vector< WayptRef > WayptVec
@ RESTRICT_NONE
Definition route.hxx:71
@ RESTRICT_AT
Definition route.hxx:72
double pointsKnownDistanceFromGC(const SGGeoc &a, const SGGeoc &b, const SGGeoc &d, double dist)
SGGeod turnCenterFlyBy(const SGGeod &pt, double inHeadingDeg, double turnAngleDeg, double turnRadiusM)
double pointsKnownDistanceFromGC(const SGGeoc &a, const SGGeoc &b, const SGGeoc &d, double dist)
bool isDescentWaypoint(const WayptRef &wpt)
TurnInfo turnCenterAndAngleFromExit(const SGGeod &pt, double outHeadingDeg, double turnRadiusM, const SGGeod &origin)
given a turn exit position and heading, and an arbitrary origin position, compute the turn center / a...
std::vector< WayptData > WayptDataVec
static double magVarFor(const SGGeod &geod)
static double sqr(const double x)
SGGeod turnCenterOverflight(const SGGeod &pt, double inHeadingDeg, double turnAngleDeg, double turnRadiusM)
double latitudeForGCLongitude(const SGGeoc &a, const SGGeoc &b, double lon)
WayptDataVec::iterator WpDataIt
SGGeod turnCenterFromExit(const SGGeod &pt, double outHeadingDeg, double turnAngleDeg, double turnRadiusM)
double turnAngleDeg
double inboundCourseDeg
SGGeod turnCenter