24#include <simgear/structure/exception.hxx>
25#include <simgear/magvar/magvar.hxx>
26#include <simgear/timing/sg_time.hxx>
41 double crs13 = r1 * SG_DEGREES_TO_RADIANS;
42 double crs23 = r2 * SG_DEGREES_TO_RADIANS;
43 double dst12 = SGGeodesy::distanceRad(a, b);
53 double sinLat1 = sin(a.getLatitudeRad());
54 double cosLat1 = cos(a.getLatitudeRad());
55 double sinDst12 = sin(dst12);
56 double cosDst12 = cos(dst12);
58 double crs12 = SGGeodesy::courseRad(a, b),
59 crs21 = SGGeodesy::courseRad(b, a);
63 double ang1 = SGMiscd::normalizeAngle(crs13-crs12);
64 double ang2 = SGMiscd::normalizeAngle(crs21-crs23);
66 if ((sin(ang1) == 0.0) && (sin(ang2) == 0.0)) {
67 SG_LOG(SG_INSTR, SG_INFO,
"geocRadialIntersection: infinity of intersections");
71 if ((sin(ang1)*sin(ang2))<0.0) {
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));
90 SGGeodesy::advanceRadM(a, crs13, dst13 * SG_RAD_TO_NM * SG_NM_TO_METER, pt3);
92 double lat3 = asin(sinLat1 * cos(dst13) + cosLat1 * sin(dst13) * cos(crs13));
95 double dlon = atan2(sin(crs13)*sin(dst13)*cosLat1, cos(dst13)- (sinLat1 * sin(lat3)));
96 double lon3 = SGMiscd::normalizeAngle(-a.getLongitudeRad()-dlon);
98 result = SGGeoc::fromRadM(-lon3, lat3, a.getRadiusM());
108static double sqr(
const double x)
116 double A = SGGeodesy::courseRad(a, d) - SGGeodesy::courseRad(a, b);
117 double bDist = SGGeodesy::distanceRad(a, d);
120 double r = pow(
sqr(cos(bDist)) +
sqr(sin(bDist)) *
sqr(cos(
A)), 0.5);
122 double p = atan2(sin(bDist)*cos(
A), cos(bDist));
124 if (
sqr(cos(dist)) >
sqr(r)) {
125 SG_LOG(SG_NAVAID, SG_INFO,
"pointsKnownDistanceFromGC, no points exist");
129 double dp1 =
p + acos(cos(dist)/r);
130 double dp2 =
p - acos(cos(dist)/r);
132 double dp1Nm = fabs(dp1 * SG_RAD_TO_NM);
133 double dp2Nm = fabs(dp2 * SG_RAD_TO_NM);
135 return SGMiscd::min(dp1Nm, dp2Nm);
142Intermediate points {lat,lon} lie on the great circle connecting points 1 and 2 when:
144lat=atan((sin(lat1)*cos(lat2)*sin(lon-lon2)
145 -sin(lat2)*cos(lat1)*sin(lon-lon1))/(cos(lat1)*cos(lat2)*sin(lon1-lon2)))
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);
159 double jd =
globals->get_time_params()->getJD();
160 return sgGetMagVar(geod, jd) * SG_RADIANS_TO_DEGREES;
164 double turnAngleDeg,
double turnRadiusM)
166 double p = copysign(90.0, turnAngleDeg);
167 return SGGeodesy::direct(pt, inHeadingDeg +
p, turnRadiusM);
171 double turnAngleDeg,
double turnRadiusM)
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);
180 double turnAngleDeg,
double turnRadiusM)
182 double p = copysign(90.0, turnAngleDeg);
183 return SGGeodesy::direct(pt, outHeadingDeg +
p, turnRadiusM);
200 double turnRadiusM,
const SGGeod&origin)
202 double bearingToExit = SGGeodesy::courseDeg(origin, pt);
205 double turnAngle = outHeadingDeg - bearingToExit;
206 SG_NORMALIZE_RANGE(turnAngle, -180.0, 180.0);
207 double p = copysign(90.0, turnAngle);
210 r.
turnCenter = SGGeodesy::direct(pt, outHeadingDeg +
p, turnRadiusM);
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");
221 double theta = asin(turnRadiusM / distanceToTC) * SG_RADIANS_TO_DEGREES;
223 theta = -copysign(theta, turnAngle);
268 const std::string& ty(
wpt->type());
271 if (ty ==
"hdgToAlt") {
274 }
else if (ty ==
"discontinuity") {
287 const std::string& ty(
wpt->type());
288 return (ty ==
"hdgToAlt") || (ty ==
"radialIntercept") || (ty ==
"dmeIntercept");
294 const auto ty =
wpt->type();
295 if ((ty ==
"vectors") || (ty ==
"discontinuity")) {
303 const bool previousPosValid = previous && previous->
posValid;
308 const auto prevWP = previous->
wpt;
310 if (prevWP->matches(
wpt)) {
315 if (prevWP->type() ==
"runway") {
319 }
else if (ty ==
"discontinuity") {
323 }
else if (ty !=
"runway") {
337 if ((
wpt->type() ==
"via") || (
wpt->type() ==
"discontinuty"))
344 if (!previous || !previous->
posValid) {
346 SG_LOG(SG_NAVAID, SG_WARN,
"RoutePath: Asked to compute leg course, but, previous is invalid");
350 if (
wpt->type() ==
"runway") {
356 radiusM, previous->
pos);
372 SGGeod previousPos = previous->
pos;
375 if (previous->
wpt->type() ==
"runway") {
377 previousPos = rwy->
end();
405 return turnRadius * fabs(angleDeg) * SG_DEGREES_TO_RADIANS;
412 bool isRunway = (
wpt->type() ==
"runway");
494 double nextLegDistance = SGGeodesy::distanceM(
pos, next.
pos) - distAlongPath;
495 double increaseAngle = atan2(xtk, nextLegDistance) * SG_RADIANS_TO_DEGREES;
535 int steps = std::max(SGMiscd::roundToInt(fabs(
turnEntryAngle) / 3.0), 1);
538 for (
int s=0; s<steps; ++s) {
551 int steps = std::max(SGMiscd::roundToInt(fabs(
turnExitAngle) / 3.0), 1);
556 if (
wpt->type() ==
"runway") {
561 for (
int s=0; s<steps; ++s) {
579 SGGeod
p = path.back();
580 double stepDist = (fabs(stepIncrement) / 360.0) * SGMiscd::twopi() *
turnRadius;
582 for (
int s=0; s<steps; ++s) {
584 p = SGGeodesy::direct(
p, h, stepDist);
595 if (distanceM > basicTurnDistance) {
600 double distanceAlongCompensationTurn = distanceM - basicTurnDistance;
601 double theta = (distanceAlongCompensationTurn /
turnRadius) * SG_RADIANS_TO_DEGREES;
610 return SGGeodesy::direct(tc2,
614 double theta = (distanceM /
turnRadius) * SG_RADIANS_TO_DEGREES;
623 double theta = (distanceM /
turnRadius) * SG_RADIANS_TO_DEGREES;
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");
662 const std::string& ty(wpt->type());
663 if (ty ==
"hdgToAlt") {
667 double distanceM =
perf.distanceNmBetween(altFt, h->
altitudeFt()) * SG_NM_TO_METER;
669 waypoints[index].pos = SGGeodesy::direct(previous->turnExitPos, hdg, distanceM);
671 }
else if (ty ==
"radialIntercept") {
675 SGGeoc prevGc = SGGeoc::fromGeod(previous->turnExitPos);
676 SGGeoc navid = SGGeoc::fromGeod(wpt->position());
678 double magVar =
magVarFor(previous->pos);
680 double radial =
i->radialDegMagnetic() + magVar;
681 double track =
i->courseDegMagnetic() + magVar;
686 SGGeoc navidAdjusted = SGGeodesy::advanceDegM(navid, radial, -10 * SG_NM_TO_METER);
691 SG_LOG(SG_NAVAID, SG_WARN,
"couldn't compute interception for radial:" << previous->turnExitPos <<
" / " << track <<
"/" << wpt->position() <<
"/" << radial);
695 waypoints[index].pos = SGGeod::fromGeoc(rGc);
698 waypoints[index].pos = SGGeod::fromGeoc(rGc);
702 }
else if (ty ==
"dmeIntercept") {
705 SGGeoc prevGc = SGGeoc::fromGeod(previous->turnExitPos);
706 SGGeoc navid = SGGeoc::fromGeod(wpt->position());
711 SGGeoc bPt = SGGeodesy::advanceDegM(prevGc, crs, 1e5);
715 SG_LOG(SG_NAVAID, SG_WARN,
"dmeIntercept failed");
718 waypoints[index].pos = SGGeodesy::direct(previous->turnExitPos, crs, dNm * SG_NM_TO_METER);
722 }
else if (ty ==
"vectors") {
723 waypoints[index].legCourseTrue = SGGeodesy::courseDeg(previous->turnExitPos,
waypoints[index].pos);
741 return perf.computePreviousAltitude(distanceM, fixedAlt);
751 return perf.computeNextAltitude(distanceM, fixedAlt);
763 const std::string& ty(w.
wpt->type());
764 if (ty ==
"runway") {
769 SG_LOG(SG_NAVAID, SG_WARN,
"findPreceedingKnownAltitude: no preceding altitude value found");
780 SG_LOG(SG_NAVAID, SG_WARN,
"findNextKnownAltitude: no next altitude value found");
790 const std::string& ty(w.
wpt->type());
791 if (ty ==
"runway") {
796 SG_LOG(SG_NAVAID, SG_WARN,
"findNextKnownAltitude: no next altitude value found");
807 return w.
wpt->altitudeFt();
810 const std::string& ty(w.
wpt->type());
811 if (ty ==
"runway") {
813 return rwy->
threshold().getElevationFt();
816 SG_LOG(SG_NAVAID, SG_WARN,
"altitudeForIndex: waypoint has no explicit altitude");
824 for (
int i=from+1;
i<= to; ++
i) {
839 }
while (
waypoints.at(index).skipped || (
waypoints.at(index).wpt->type() ==
"discontinuity"));
862 while ((it !=
waypoints.end()) && (it->skipped || (it->wpt->type() ==
"discontinuity"))) {
872 for (
int l=0; l<fp->
numLegs(); ++l) {
875 SG_LOG(SG_NAVAID, SG_DEV_ALERT,
"Waypoint " << l <<
" of " << fp->
numLegs() <<
"is NULL");
901void RoutePath::commonInit()
903 for (
auto& w : d->waypoints) {
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);
914 for (
unsigned int i=0;
i<d->waypoints.size(); ++
i) {
915 if (d->waypoints[
i].skipped) {
920 double radiusM = d->perf.turnRadiusMForAltitude(alt);
923 auto prevIt = d->previousValidWaypoint(
i);
924 WayptData* prevPtr = (prevIt == d->waypoints.end()) ? nullptr : &(*prevIt);
927 d->computeDynamicPosition(
i);
930 auto nextIt = d->nextValidWaypoint(
i);
931 if (nextIt != d->waypoints.end()) {
932 nextIt->computeLegCourse(&(d->waypoints[
i]), radiusM);
934 if (nextIt->legCourseValid) {
935 d->waypoints[
i].computeTurn(radiusM, d->constrainLegCourses, d->maxFlyByTurnAngleDeg, *nextIt);
939 d->waypoints[
i].turnEntryPos = d->waypoints[
i].pos;
940 d->waypoints[
i].turnExitPos = d->waypoints[
i].pos;
944 d->waypoints[
i].turnExitPos = d->waypoints[
i].pos;
945 d->waypoints[
i].turnEntryPos = d->waypoints[
i].pos;
949 d->waypoints[
i].pathDistanceM = computeDistanceForIndex(
i);
955 if ((index < 0) || (index >=
static_cast<int>(d->waypoints.size()))) {
960 const std::string& ty(w.
wpt->type());
963 if (d->waypoints[index].skipped) {
973 auto prevIt = d->previousValidWaypoint(index);
974 if (prevIt != d->waypoints.end()) {
980 if (ty ==
"vectors") {
985 if (ty ==
"discontinuity") {
990 return pathForVia(
static_cast<Via*
>(d->waypoints[index].wpt.get()), index);
994 if (prevIt != d->waypoints.end()) {
995 prevIt->turnExitPath(r);
997 SGGeod from = prevIt->turnExitPos,
1002 double lonDelta = to.getLongitudeDeg() - from.getLongitudeDeg();
1003 if (fabs(lonDelta) > 0.5) {
1004 interpolateGreatCircle(from, to, r);
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());
1017 if (ty ==
"runway") {
1021 r.push_back(rwy->
end());
1027void RoutePath::interpolateGreatCircle(
const SGGeod& aFrom,
const SGGeod& aTo,
SGGeodVec& r)
const
1029 SGGeoc gcFrom = SGGeoc::fromGeod(aFrom),
1030 gcTo = SGGeoc::fromGeod(aTo);
1032 double lonDelta = gcTo.getLongitudeRad() - gcFrom.getLongitudeRad();
1033 if (fabs(lonDelta) < 1e-3) {
1037 lonDelta = SGMiscd::normalizeAngle(lonDelta);
1038 int steps =
static_cast<int>(fabs(lonDelta) * SG_RADIANS_TO_DEGREES * 2);
1039 double lonStep = (lonDelta / steps);
1041 double lon = gcFrom.getLongitudeRad() + lonStep;
1042 for (
int s=0; s < (steps - 1); ++s) {
1043 lon = SGMiscd::normalizeAngle(lon);
1045 r.push_back(SGGeod::fromGeoc(SGGeoc::fromRadM(lon, lat, SGGeodesy::EQURAD)));
1053 if ((index < 0) || (index >=
static_cast<int>(d->waypoints.size()))) {
1057 return d->waypoints[index].pos;
1060SGGeodVec RoutePath::pathForVia(
Via* via,
int index)
const
1063 auto prevIt = d->previousValidWaypoint(index);
1064 if (prevIt == d->waypoints.end()) {
1071 WayptVec::const_iterator it;
1072 SGGeod legStart = prevIt->wpt->position();
1073 for (it = enrouteWaypoints.begin(); it != enrouteWaypoints.end(); ++it) {
1075 interpolateGreatCircle(legStart, (*it)->position(), r);
1076 legStart = (*it)->position();
1086 double turnDelta = 180.0 / turnSteps;
1088 double gsKts = d->perf.groundSpeedForAltitudeKnots(altFt);
1092 double stepTime = turnDelta / 3.0;
1093 double stepDist = gsKts * SG_KT_TO_MPS * stepTime;
1097 legDist *= SG_NM_TO_METER;
1100 turnDelta = -turnDelta;
1106 for (
int j=0; j < 2; ++j) {
1108 for (
int i=0;
i<turnSteps; ++
i) {
1110 SGGeodesy::direct(pos, hdg, stepDist, pos, az2);
1115 SGGeodesy::direct(pos, hdg, legDist, pos, az2);
1122double RoutePath::computeDistanceForIndex(
int index)
const
1124 if ((index < 0) || (index >=
static_cast<int>(d->waypoints.size()))) {
1128 auto it = d->waypoints.begin() + index;
1129 if ((index == 0) || it->skipped) {
1134 const auto ty = it->wpt->type();
1136 return distanceForVia(
static_cast<Via*
>(it->wpt.get()), index);
1139 if (ty ==
"discontinuity") {
1143 auto prevIt = d->previousValidWaypoint(index);
1144 if (prevIt == d->waypoints.end()) {
1148 double dist = SGGeodesy::distanceM(prevIt->turnExitPos, it->turnEntryPos);
1149 dist += prevIt->turnDistanceM();
1153 dist += it->turnDistanceM();
1159double RoutePath::distanceForVia(
Via* via,
int index)
const
1161 auto prevIt = d->previousValidWaypoint(index);
1162 if (prevIt == d->waypoints.end()) {
1169 SGGeod legStart = prevIt->wpt->position();
1170 for (
auto wp : enrouteWaypoints) {
1171 dist += SGGeodesy::distanceM(legStart, wp->position());
1172 legStart = wp->position();
1180 const auto sz =
static_cast<int>(d->waypoints.size());
1181 if ((index < 0) || (index >= sz)) {
1185 if (d->waypoints[index].skipped)
1188 const WayptData& wd(d->waypoints[index]);
1197 const auto sz =
static_cast<int>(d->waypoints.size());
1198 if ((index < 0) || (index >= sz)) {
1202 return d->waypoints[index].pathDistanceM;
1211 const auto sz =
static_cast<int>(d->waypoints.size());
1216 return d->distanceBetweenIndices(from, to);
1221 int sz = (int) d->waypoints.size();
1226 if ((index < 0) || (index >= sz)) {
1227 throw sg_range_exception(
"waypt index out of range",
1228 "RoutePath::positionForDistanceFrom");
1232 if (distanceM < 0.0) {
1234 while ((index > 0) && (distanceM < 0.0)) {
1241 distanceM += d->waypoints[index].pathDistanceM;
1245 if (distanceM < 0.0) {
1247 return d->waypoints[0].pos;
1252 int nextIndex = index + 1;
1253 while ((nextIndex < sz) && (d->waypoints[nextIndex].pathDistanceM < distanceM)) {
1254 distanceM -= d->waypoints[nextIndex].pathDistanceM;
1255 index = nextIndex++;
1259 auto nextIt = d->nextValidWaypoint(index);
1260 if (nextIt == d->waypoints.end()) {
1262 return d->waypoints[sz - 1].pos;
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);
1276 if (next.
wpt->type() ==
"via") {
1277 return positionAlongVia(
static_cast<Via*
>(next.
wpt.get()), index, distanceM);
1288 if (next.
hasEntry && (distanceM > corePathDistance)) {
1297SGGeod RoutePath::positionAlongVia(
Via* via,
int previousIndex,
double distanceM)
const
1299 SG_LOG(SG_NAVAID, SG_ALERT,
"RoutePath::positionAlongVia not implemented");
std::vector< SGGeod > SGGeodVec
double headingDeg() const
Runway heading in degrees.
SGGeod end() const
Get the 'far' end - this is equivalent to calling pointOnCenterline(lengthFt());.
SGGeod threshold() const
Get the (possibly displaced) threshold point.
WayptDataVec::iterator nextValidWaypoint(int index)
double distanceBetweenIndices(int from, int to) const
int findPreceedingKnownAltitude(int index) const
void computeDynamicPosition(int index)
int findNextKnownAltitude(unsigned int index) const
WayptDataVec::iterator nextValidWaypoint(WayptDataVec::iterator it)
WayptDataVec::iterator previousValidWaypoint(unsigned int index)
double computeVNAVAltitudeFt(int index)
double maxFlyByTurnAngleDeg
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 pathDistanceForTurnAngle(double angleDeg) const
double overflightCompensationAngle
SGGeod pointAlongExitPath(double distanceM) const
SGGeod pointOnEntryTurnFromHeading(double headingDeg) const
bool isCourseConstrained() const
test if course of this leg can be adjusted or is contrained to an exact value
void turnEntryPath(SGGeodVec &path) const
SGGeod pointAlongEntryPath(double distanceM) const
void computeLegCourse(const WayptData *previous, double radiusM)
void turnExitPath(SGGeodVec &path) const
virtual SGGeod position() const
double dmeDistanceNm() const
double courseDegMagnetic() const
bool followLegTrackToFixes() const
LegRef legAtIndex(int index) const
double headingDegMagnetic() const
double timeOrDistance() const
bool isLeftHanded() const
double inboundRadial() const
Waypoint based upon a runway.
FGRunway * runway() const
WayptVec expandToWaypoints(WayptRef aPreceeding) const
double altitudeFt() const
FlightPlan.hxx - defines a full flight-plan object, including departure, cruise, arrival information ...
std::vector< SGGeod > SGGeodVec
SGSharedPtr< Waypt > WayptRef
bool geocRadialIntersection(const SGGeoc &a, double r1, const SGGeoc &b, double r2, SGGeoc &result)
@ WPT_DYNAMIC
waypoint position is dynamic, i.e moves based on other criteria, such as altitude,...
@ WPT_MISS
segment is part of missed approach
@ WPT_HIDDEN
waypoint should not be shown in UI displays, etc this is used to implement FMSs which delete waypoint...
@ WPT_OVERFLIGHT
must overfly the point directly
std::vector< WayptRef > WayptVec
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)