]> git.mxchange.org Git - flightgear.git/blobdiff - src/Navaids/routePath.cxx
Fix spelling
[flightgear.git] / src / Navaids / routePath.cxx
index 6353fb5b678711d5815de93ca7778dd4deb92b6c..721c6d26a48c680ec61455aaf89ae9c947356fe9 100644 (file)
@@ -28,6 +28,7 @@ static double sqr(const double x)
   return x * x;
 }
 
+// http://williams.best.vwh.net/avform.htm#POINTDME
 double pointsKnownDistanceFromGC(const SGGeoc& a, const SGGeoc&b, const SGGeoc& d, double dist)
 {
   double A = SGGeodesy::courseRad(a, d) - SGGeodesy::courseRad(a, b);
@@ -52,74 +53,947 @@ double pointsKnownDistanceFromGC(const SGGeoc& a, const SGGeoc&b, const SGGeoc&
   return SGMiscd::min(dp1Nm, dp2Nm);
 }
 
-RoutePath::RoutePath(const flightgear::WayptVec& wpts) :
-  _waypts(wpts)
+// http://williams.best.vwh.net/avform.htm#Int
+double latitudeForGCLongitude(const SGGeoc& a, const SGGeoc& b, double lon)
 {
-  commonInit();
+#if 0
+Intermediate points {lat,lon} lie on the great circle connecting points 1 and 2 when:
+
+lat=atan((sin(lat1)*cos(lat2)*sin(lon-lon2)
+     -sin(lat2)*cos(lat1)*sin(lon-lon1))/(cos(lat1)*cos(lat2)*sin(lon1-lon2)))
+#endif
+  double lonDiff = a.getLongitudeRad() - b.getLongitudeRad();
+  double cosLat1 = cos(a.getLatitudeRad()),
+      cosLat2 = cos(b.getLatitudeRad());
+  double x = sin(a.getLatitudeRad()) * cosLat2 * sin(lon - b.getLongitudeRad());
+  double y = sin(b.getLatitudeRad()) * cosLat1 * sin(lon - a.getLongitudeRad());
+  double denom = cosLat1 * cosLat2 * sin(lonDiff);
+  double lat = atan((x - y) / denom);
+  return lat;
 }
 
-RoutePath::RoutePath(const flightgear::FlightPlan* fp)
+static double magVarFor(const SGGeod& geod)
 {
-  for (int l=0; l<fp->numLegs(); ++l) {
-    _waypts.push_back(fp->legAtIndex(l)->waypoint());
-  }
-  commonInit();
+  double jd = globals->get_time_params()->getJD();
+  return sgGetMagVar(geod, jd) * SG_RADIANS_TO_DEGREES;
 }
 
-void RoutePath::commonInit()
+SGGeod turnCenterOverflight(const SGGeod& pt, double inHeadingDeg,
+                           double turnAngleDeg, double turnRadiusM)
 {
-  _pathClimbFPM = 1200;
-  _pathDescentFPM = 800;
-  _pathIAS = 190; 
-  _pathTurnRate = 3.0; // 3 deg/sec = 180def/min = standard rate turn  
+    double p = copysign(90.0, turnAngleDeg);
+    return SGGeodesy::direct(pt, inHeadingDeg + p, turnRadiusM);
 }
 
-SGGeodVec RoutePath::pathForIndex(int index) const
+SGGeod turnCenterFlyBy(const SGGeod& pt, double inHeadingDeg,
+                           double turnAngleDeg, double turnRadiusM)
 {
-  if (index == 0) {
-    return SGGeodVec(); // no path for first waypoint
+    double halfAngle = turnAngleDeg * 0.5;
+    double turnCenterOffset = turnRadiusM / cos(halfAngle * SG_DEGREES_TO_RADIANS);
+    double p = copysign(90.0, turnAngleDeg);
+    return SGGeodesy::direct(pt, inHeadingDeg + halfAngle + p, turnCenterOffset);
+}
+
+SGGeod turnCenterFromExit(const SGGeod& pt, double outHeadingDeg,
+                       double turnAngleDeg, double turnRadiusM)
+{
+    double p = copysign(90.0, turnAngleDeg);
+    return SGGeodesy::direct(pt, outHeadingDeg + p, turnRadiusM);
+}
+
+struct TurnInfo
+{
+    TurnInfo() : valid(false),
+    inboundCourseDeg(0.0),
+    turnAngleDeg(0.0) { }
+
+    bool valid;
+    SGGeod turnCenter;
+    double inboundCourseDeg;
+    double turnAngleDeg;
+};
+
+/**
+ * given a turn exit position and heading, and an arbitrary origin postion, 
+ * compute the turn center / angle the matches. Certain configurations may
+ * fail, especially if the origin is less than two turn radii from the exit pos.
+ */
+TurnInfo turnCenterAndAngleFromExit(const SGGeod& pt, double outHeadingDeg,
+                                double turnRadiusM, const SGGeod&origin)
+{
+    double bearingToExit = SGGeodesy::courseDeg(origin, pt);
+    // not the final turn angle, but we need to know which side of the
+    // exit course we're on, to decide if it's a left-hand or right-hand turn
+    double turnAngle = outHeadingDeg - bearingToExit;
+    SG_NORMALIZE_RANGE(turnAngle, -180.0, 180.0);
+    double p = copysign(90.0, turnAngle);
+
+    TurnInfo r;
+    r.turnCenter = SGGeodesy::direct(pt, outHeadingDeg + p, turnRadiusM);
+
+    double courseToTC, distanceToTC, az2;
+    SGGeodesy::inverse(origin, r.turnCenter, courseToTC, az2, distanceToTC);
+    if (distanceToTC < turnRadiusM) {
+        SG_LOG(SG_NAVAID, SG_WARN, "turnCenterAndAngleFromExit: origin point to close to turn center");
+        return r;
+    }
+
+    // find additional course angle away from the exit pos to intersect
+    // the turn circle.
+    double theta = asin(turnRadiusM / distanceToTC) * SG_RADIANS_TO_DEGREES;
+    // invert angle sign so we increase the turn angle
+    theta = -copysign(theta, turnAngle);
+
+    r.inboundCourseDeg = courseToTC + theta;
+    SG_NORMALIZE_RANGE(r.inboundCourseDeg, 0.0, 360.0);
+
+    // turn angle must have same direction (sign) as turnAngle above, even if
+    // the turn radius means the sign would cross over (happens if origin point
+    // is close by
+    r.turnAngleDeg = outHeadingDeg - r.inboundCourseDeg;
+    if (r.turnAngleDeg > 0.0) {
+        if (turnAngle < 0.0) r.turnAngleDeg -= 360.0;
+    } else {
+        if (turnAngle > 0.0) r.turnAngleDeg += 360.0;
+    }
+
+    r.valid = true;
+    return r;
+}
+
+class WayptData
+{
+public:
+  explicit WayptData(WayptRef w) :
+    wpt(w),
+    hasEntry(false),
+    posValid(false),
+    legCourseValid(false),
+    skipped(false),
+    turnEntryAngle(0.0),
+    turnExitAngle(0.0),
+    turnRadius(0.0),
+    pathDistanceM(0.0),
+    turnPathDistanceM(0.0),
+    overflightCompensationAngle(0.0),
+    flyOver(w->flag(WPT_OVERFLIGHT))
+  {
+  }
+  
+  void initPass0()
+  {
+    const std::string& ty(wpt->type());
+    if (wpt->flag(WPT_DYNAMIC)) {
+      // presumption is that we always overfly such a waypoint
+      if (ty == "hdgToAlt") {
+        flyOver = true;
+      }
+    } else {
+      pos = wpt->position();
+      posValid = true;
+      
+      if (ty == "hold") {
+        legCourseTrue = wpt->headingRadialDeg() + magVarFor(pos);
+        legCourseValid = true;
+      }
+    } // of static waypt
+  }
+
+  /**
+   * test if course of this leg can be adjusted or is contrained to an exact value
+   */
+  bool isCourseConstrained() const
+  {
+    const std::string& ty(wpt->type());
+    return (ty == "hdgToAlt") || (ty == "radialIntercept") || (ty == "dmeIntercept");
+  }
+
+  // compute leg courses for all static legs (both ends are fixed)
+  void initPass1(const WayptData& previous, WayptData* next)
+  {
+    if (wpt->type() == "vectors") {
+      // relying on the fact vectors will be followed by a static fix/wpt
+      if (next && next->posValid) {
+        posValid = true;
+        pos = next->pos;
+      }
+    }
+
+    if (posValid && !legCourseValid && previous.posValid) {
+    // check for duplicate points now
+      if (previous.wpt->matches(wpt)) {
+          skipped = true;
+      }
+
+      // we can compute leg course now
+        if (previous.wpt->type() == "runway") {
+            // use the runway departure end pos
+            FGRunway* rwy = static_cast<RunwayWaypt*>(previous.wpt.get())->runway();
+            legCourseTrue = SGGeodesy::courseDeg(rwy->end(), pos);
+        } else if (wpt->type() != "runway") {
+            // need to wait to compute runway leg course
+            legCourseTrue = SGGeodesy::courseDeg(previous.pos, pos);
+            legCourseValid = true;
+        }
+    }
+  }
+
+  void computeLegCourse(const WayptData& previous, double radiusM)
+  {
+      if (legCourseValid) {
+          return;
+      }
+
+      if (wpt->type() == "hold") {
+
+      } else if (wpt->type() == "runway") {
+          FGRunway* rwy = static_cast<RunwayWaypt*>(wpt.get())->runway();
+          flyOver = true;
+
+          TurnInfo ti = turnCenterAndAngleFromExit(rwy->threshold(),
+                                                   rwy->headingDeg(),
+                                                   radiusM, previous.pos);
+          if (ti.valid) {
+              legCourseTrue = ti.inboundCourseDeg;
+              turnEntryAngle = ti.turnAngleDeg;
+              turnEntryCenter = ti.turnCenter;
+              turnRadius = radiusM;
+              hasEntry = true;
+              turnEntryPos = pointOnEntryTurnFromHeading(ti.inboundCourseDeg);
+          } else {
+              // couldn't compute entry, never mind
+              legCourseTrue = SGGeodesy::courseDeg(previous.pos, rwy->threshold());
+          }
+          legCourseValid = true;
+      } else {
+          if (posValid) {
+              legCourseTrue = SGGeodesy::courseDeg(previous.pos, pos);
+              legCourseValid = true;
+          } else if (isCourseConstrained()) {
+              double magVar = magVarFor(previous.pos);
+              legCourseTrue = wpt->headingRadialDeg() + magVar;
+              legCourseValid = true;
+          } // of pos not valid
+      } // of neither hold nor runway
+  }
+
+    SGGeod pointOnEntryTurnFromHeading(double headingDeg) const
+    {
+        assert(hasEntry);
+        double p = copysign(90.0, turnEntryAngle);
+        return SGGeodesy::direct(turnEntryCenter, headingDeg - p, turnRadius);
+    }
+
+    SGGeod pointOnExitTurnFromHeading(double headingDeg) const
+    {
+        double p = copysign(90.0, turnExitAngle);
+        return SGGeodesy::direct(turnExitCenter, headingDeg - p, turnRadius);
+    }
+
+    double pathDistanceForTurnAngle(double angleDeg) const
+    {
+        return turnRadius * fabs(angleDeg) * SG_DEGREES_TO_RADIANS;
+    }
+
+  void computeTurn(double radiusM, bool constrainLegCourse, WayptData& next)
+  {
+    assert(!skipped);
+    assert(next.legCourseValid);
+      bool isRunway = (wpt->type() == "runway");
+
+      if (legCourseValid) {
+          if (isRunway) {
+              FGRunway* rwy = static_cast<RunwayWaypt*>(wpt.get())->runway();
+              turnExitAngle = next.legCourseTrue - rwy->headingDeg();
+          } else {
+              turnExitAngle = next.legCourseTrue - legCourseTrue;
+          }
+      } else {
+          // happens for first leg
+          legCourseValid = true;
+          if (isRunway) {
+              FGRunway* rwy = static_cast<RunwayWaypt*>(wpt.get())->runway();
+              turnExitAngle = next.legCourseTrue - rwy->headingDeg();
+              legCourseTrue = rwy->headingDeg();
+              flyOver = true;
+          } else {
+              turnExitAngle = 0.0;
+          }
+      }
+
+    SG_NORMALIZE_RANGE(turnExitAngle, -180.0, 180.0);
+    turnRadius = radiusM;
+
+    if (!flyOver && fabs(turnExitAngle) > 120.0) {
+        // flyBy logic blows up for sharp turns - due to the tan() term
+        // heading towards infinity. By converting to flyOver we do something
+        // closer to what was requested.
+        flyOver = true;
+    }
+
+    if (flyOver) {
+        if (isRunway) {
+            FGRunway* rwy = static_cast<RunwayWaypt*>(wpt.get())->runway();
+            turnExitCenter = turnCenterOverflight(rwy->end(), rwy->headingDeg(), turnExitAngle, turnRadius);
+        } else {
+            turnEntryPos = pos;
+            turnExitCenter = turnCenterOverflight(pos, legCourseTrue, turnExitAngle, turnRadius);
+
+        }
+      turnExitPos = pointOnExitTurnFromHeading(next.legCourseTrue);
+      
+      if (!next.wpt->flag(WPT_DYNAMIC)) {
+        // distance perpendicular to next leg course, after turning
+        // through turnAngle
+        double xtk = turnRadius * (1 - cos(turnExitAngle * SG_DEGREES_TO_RADIANS));
+
+        if (constrainLegCourse || next.isCourseConstrained()) {
+          // next leg course is constrained. We need to swing back onto the
+          // desired course, using a compensation turn
+          
+          // compensation angle to turn back on course
+          double theta = acos((turnRadius - (xtk * 0.5)) / turnRadius) * SG_RADIANS_TO_DEGREES;
+          theta = copysign(theta, turnExitAngle);
+          turnExitAngle += theta;
+        
+          // move by the distance to compensate
+          double d = turnRadius * 2.0 * sin(theta * SG_DEGREES_TO_RADIANS);
+          turnExitPos = SGGeodesy::direct(turnExitPos, next.legCourseTrue, d);
+          overflightCompensationAngle = -theta;
+
+            // sign of angles will differ, so compute distances seperately
+            turnPathDistanceM = pathDistanceForTurnAngle(turnExitAngle) +
+                pathDistanceForTurnAngle(overflightCompensationAngle);
+        } else {
+          // next leg course can be adjusted. increase the turn angle
+          // and modify the next leg's course accordingly.
+
+          // hypotenuse of triangle, opposite edge has length turnRadius
+          double distAlongPath = std::min(1.0, sin(fabs(turnExitAngle) * SG_DEGREES_TO_RADIANS)) * turnRadius;
+          double nextLegDistance = SGGeodesy::distanceM(pos, next.pos) - distAlongPath;
+          double increaseAngle = atan2(xtk, nextLegDistance) * SG_RADIANS_TO_DEGREES;
+          increaseAngle = copysign(increaseAngle, turnExitAngle);
+
+          turnExitAngle += increaseAngle;
+          turnExitPos = pointOnExitTurnFromHeading(legCourseTrue + turnExitAngle);
+          // modify next leg course
+          next.legCourseTrue = SGGeodesy::courseDeg(turnExitPos, next.pos);
+          turnPathDistanceM = pathDistanceForTurnAngle(turnExitAngle);
+        } // of next leg isn't course constrained
+      } else {
+          // next point is dynamic
+          // no compensation needed
+          turnPathDistanceM = pathDistanceForTurnAngle(turnExitAngle);
+      }
+    } else {
+      hasEntry = true;
+        turnEntryCenter = turnCenterFlyBy(pos, legCourseTrue, turnExitAngle, turnRadius);
+
+        turnExitAngle = turnExitAngle * 0.5;
+        turnEntryAngle = turnExitAngle;
+        turnExitCenter = turnEntryCenter; // important that these match
+
+      turnEntryPos = pointOnEntryTurnFromHeading(legCourseTrue);
+      turnExitPos = pointOnExitTurnFromHeading(next.legCourseTrue);
+      turnPathDistanceM = pathDistanceForTurnAngle(turnEntryAngle);
+    }
   }
   
-  if (_waypts[index]->type() == "vectors") {
-    return SGGeodVec(); // empty
+  double turnDistanceM() const
+  {
+      return turnPathDistanceM;
   }
   
-  if (_waypts[index]->type() == "hold") {
-    return pathForHold((Hold*) _waypts[index].get());
+  void turnEntryPath(SGGeodVec& path) const
+  {
+    if (!hasEntry || fabs(turnEntryAngle) < 0.5 ) {
+      path.push_back(pos);
+      return;
+    }
+
+    int steps = std::max(SGMiscd::roundToInt(fabs(turnEntryAngle) / 3.0), 1);
+    double stepIncrement = turnEntryAngle / steps;
+    double h = legCourseTrue;
+    for (int s=0; s<steps; ++s) {
+        path.push_back(pointOnEntryTurnFromHeading(h));
+        h += stepIncrement;
+    }
   }
+  
+  void turnExitPath(SGGeodVec& path) const
+  {
+    if (fabs(turnExitAngle) < 0.5) {
+      path.push_back(turnExitPos);
+      return;
+    }
+
+    int steps = std::max(SGMiscd::roundToInt(fabs(turnExitAngle) / 3.0), 1);
+    double stepIncrement = turnExitAngle / steps;
     
-  SGGeodVec r;
-  SGGeod pos;
-  if (!computedPositionForIndex(index-1, pos)) {
-    return SGGeodVec();
+    // initial exit heading
+      double h = legCourseTrue + (flyOver ? 0.0 : turnEntryAngle);
+      if (wpt->type() == "runway") {
+          FGRunway* rwy = static_cast<RunwayWaypt*>(wpt.get())->runway();
+          h = rwy->headingDeg();
+      }
+
+    for (int s=0; s<steps; ++s) {
+        path.push_back(pointOnExitTurnFromHeading(h));
+        h += stepIncrement;
+    }
+
+    // we can use an exact check on the compensation angle, because we
+    // initialise it directly. Depending on the next element we might be
+    // doing compensation or adjusting the next leg's course; if we did
+    // adjust the course everything 'just works' above.
+
+    if (flyOver && (overflightCompensationAngle != 0.0)) {
+      // skew by compensation angle back
+      steps = std::max(SGMiscd::roundToInt(fabs(overflightCompensationAngle) / 3.0), 1);
+      
+      // step in opposite direction to the turn angle to swing back onto
+      // the next leg course
+      stepIncrement = overflightCompensationAngle / steps;
+
+        SGGeod p = path.back();
+        double stepDist = (fabs(stepIncrement) / 360.0) * SGMiscd::twopi() * turnRadius;
+
+      for (int s=0; s<steps; ++s) {
+          h += stepIncrement;
+        p = SGGeodesy::direct(p, h, stepDist);
+          path.push_back(p);
+
+      }
+    } // of overflight compensation turn
+  }
+
+  SGGeod pointAlongExitPath(double distanceM) const
+  {
+      double theta = (distanceM / turnRadius) * SG_RADIANS_TO_DEGREES;
+      double p = copysign(90, turnExitAngle);
+
+      if (flyOver && (overflightCompensationAngle != 0.0)) {
+          // figure out if we're in the compensation section
+          if (theta > turnExitAngle) {
+              // compute the compensation turn center - twice the turn radius
+              // from turnCenter
+              SGGeod tc2 = SGGeodesy::direct(turnExitCenter,
+                                             legCourseTrue - overflightCompensationAngle - p,
+                                             turnRadius * 2.0);
+              theta = copysign(theta - turnExitAngle, overflightCompensationAngle);
+              return SGGeodesy::direct(tc2,
+                                       legCourseTrue - overflightCompensationAngle + theta + p, turnRadius);
+          }
+      }
+
+      theta = copysign(theta, turnExitAngle);
+      double inboundCourse = legCourseTrue + (flyOver ? 0.0 : turnExitAngle);
+      return pointOnExitTurnFromHeading(inboundCourse + theta);
+  }
+
+  SGGeod pointAlongEntryPath(double distanceM) const
+  {
+    assert(hasEntry);
+    double theta = (distanceM / turnRadius) * SG_RADIANS_TO_DEGREES;
+      theta = copysign(theta, turnEntryAngle);
+      return pointOnEntryTurnFromHeading(legCourseTrue + theta);
   }
   
-  r.push_back(pos);
-  if (!computedPositionForIndex(index, pos)) {
+  WayptRef wpt;
+  bool hasEntry, posValid, legCourseValid, skipped;
+  SGGeod pos, turnEntryPos, turnExitPos, turnEntryCenter, turnExitCenter;
+  double turnEntryAngle, turnExitAngle, turnRadius, legCourseTrue;
+  double pathDistanceM;
+  double turnPathDistanceM; // for flyBy, this is half the distance; for flyOver it's the complete distance
+  double overflightCompensationAngle;
+  bool flyOver;
+};
+
+typedef std::vector<WayptData> WayptDataVec;
+
+class PerformanceBracket
+{
+public:
+  PerformanceBracket(double atOrBelow, double climb, double descent, double speed, bool isMach = false) :
+    atOrBelowAltitudeFt(atOrBelow),
+    climbRateFPM(climb),
+    descentRateFPM(descent),
+    speedIASOrMach(speed),
+    speedIsMach(isMach)
+  { }
+  
+  double atOrBelowAltitudeFt;
+  double climbRateFPM;
+  double descentRateFPM;
+  double speedIASOrMach;
+  bool speedIsMach;
+};
+
+typedef std::vector<PerformanceBracket> PerformanceBracketVec;
+
+bool isDescentWaypoint(const WayptRef& wpt)
+{
+  return (wpt->flag(WPT_APPROACH) && !wpt->flag(WPT_MISS)) || wpt->flag(WPT_ARRIVAL);
+}
+
+class RoutePath::RoutePathPrivate
+{
+public:
+    WayptDataVec waypoints;
+
+    char aircraftCategory;
+    PerformanceBracketVec perf;
+    double pathTurnRate;
+    bool constrainLegCourses;
+  
+  PerformanceBracketVec::const_iterator
+  findPerformanceBracket(double altFt) const
+  {
+    PerformanceBracketVec::const_iterator r;
+    PerformanceBracketVec::const_iterator result = perf.begin();
+
+    for (r = perf.begin(); r != perf.end(); ++r) {
+      if (r->atOrBelowAltitudeFt > altFt) {
+        break;
+      }
+      
+      result = r;
+    } // of brackets iteration
+    
+    return result;
+  }
+  
+  void computeDynamicPosition(int index)
+  {
+    const WayptData& previous(previousValidWaypoint(index));
+    WayptRef wpt = waypoints[index].wpt;
+    assert(previous.posValid);
+
+    const std::string& ty(wpt->type());
+    if (ty == "hdgToAlt") {
+      HeadingToAltitude* h = (HeadingToAltitude*) wpt.get();
+      
+      double altFt = computeVNAVAltitudeFt(index - 1);
+      double altChange = h->altitudeFt() - altFt;
+      PerformanceBracketVec::const_iterator it = findPerformanceBracket(altFt);
+      double speedMSec = groundSpeedForAltitude(altFt) * SG_KT_TO_MPS;
+      double timeToChangeSec;
+      
+      if (isDescentWaypoint(wpt)) {
+        timeToChangeSec = (altChange / it->descentRateFPM) * 60.0;
+      } else {
+        timeToChangeSec = (altChange / it->climbRateFPM) * 60.0;
+      }
+
+      double distanceM = timeToChangeSec * speedMSec;
+      double hdg = h->headingDegMagnetic() + magVarFor(previous.pos);
+      waypoints[index].pos = SGGeodesy::direct(previous.turnExitPos, hdg, distanceM);
+      waypoints[index].posValid = true;
+    } else if (ty == "radialIntercept") {
+      // start from previous.turnExit
+      RadialIntercept* i = (RadialIntercept*) wpt.get();
+      
+      SGGeoc prevGc = SGGeoc::fromGeod(previous.turnExitPos);
+      SGGeoc navid = SGGeoc::fromGeod(wpt->position());
+      SGGeoc rGc;
+      double magVar = magVarFor(previous.pos);
+      
+      double radial = i->radialDegMagnetic() + magVar;
+      double track = i->courseDegMagnetic() + magVar;
+      bool ok = geocRadialIntersection(prevGc, track, navid, radial, rGc);
+      if (!ok) {
+        SGGeoc navidAdjusted;
+        // try pulling backward along the radial in case we're too close.
+        // suggests bad procedure construction if this is happening!
+        SGGeodesy::advanceRadM(navid, radial, SG_NM_TO_METER * -10, navidAdjusted);
+
+        // try again
+        ok = geocRadialIntersection(prevGc, track, navidAdjusted, radial, rGc);
+        if (!ok) {
+          SG_LOG(SG_NAVAID, SG_WARN, "couldn't compute interception for radial:"
+               << previous.turnExitPos << " / " << track << "/" << wpt->position()
+               << "/" << radial);
+          waypoints[index].pos = wpt->position(); // horrible fallback
+
+        } else {
+          waypoints[index].pos = SGGeod::fromGeoc(rGc);
+        }
+      } else {
+        waypoints[index].pos = SGGeod::fromGeoc(rGc);
+      }
+      
+      waypoints[index].posValid = true;
+    } else if (ty == "dmeIntercept") {
+      DMEIntercept* di = (DMEIntercept*) wpt.get();
+      
+      SGGeoc prevGc = SGGeoc::fromGeod(previous.turnExitPos);
+      SGGeoc navid = SGGeoc::fromGeod(wpt->position());
+      double distRad = di->dmeDistanceNm() * SG_NM_TO_RAD;
+      SGGeoc rGc;
+      
+      SGGeoc bPt;
+      double crs = di->courseDegMagnetic() + magVarFor(wpt->position());
+      SGGeodesy::advanceRadM(prevGc, crs, 100 * SG_NM_TO_RAD, bPt);
+      
+      double dNm = pointsKnownDistanceFromGC(prevGc, bPt, navid, distRad);
+      if (dNm < 0.0) {
+        SG_LOG(SG_NAVAID, SG_WARN, "dmeIntercept failed");
+        waypoints[index].pos = wpt->position(); // horrible fallback
+      } else {
+        waypoints[index].pos = SGGeodesy::direct(previous.turnExitPos, crs, dNm * SG_NM_TO_METER);
+      }
+      
+      waypoints[index].posValid = true;
+    } else if (ty == "vectors") {
+      waypoints[index].legCourseTrue = SGGeodesy::courseDeg(previous.turnExitPos, waypoints[index].pos);
+      waypoints[index].legCourseValid = true;
+      // no turn data
+    }
+  }
+  
+  double computeVNAVAltitudeFt(int index)
+  {
+    WayptRef w = waypoints[index].wpt;
+    if ((w->flag(WPT_APPROACH) && !w->flag(WPT_MISS)) || w->flag(WPT_ARRIVAL)) {
+      // descent
+      int next = findNextKnownAltitude(index);
+      if (next < 0) {
+        return 0.0;
+      }
+      
+      double fixedAlt = altitudeForIndex(next);
+      double distanceM = distanceBetweenIndices(index, next);
+      double speedMSec = groundSpeedForAltitude(fixedAlt) * SG_KT_TO_MPS;
+      double minutes = (distanceM / speedMSec) / 60.0;
+      
+      PerformanceBracketVec::const_iterator it = findPerformanceBracket(fixedAlt);
+      return fixedAlt + (it->descentRateFPM * minutes);
+
+    } else {
+      // climb
+      int prev = findPreceedingKnownAltitude(index);
+      if (prev < 0) {
+        return 0.0;
+      }
+      
+      double fixedAlt = altitudeForIndex(prev);
+      double distanceM = distanceBetweenIndices(prev, index);
+      double speedMSec = groundSpeedForAltitude(fixedAlt) * SG_KT_TO_MPS;
+      double minutes = (distanceM / speedMSec) / 60.0;
+      
+      PerformanceBracketVec::const_iterator it = findPerformanceBracket(fixedAlt);
+      return fixedAlt + (it->climbRateFPM * minutes);
+    }
+    
+  }
+  
+  int findPreceedingKnownAltitude(int index) const
+  {
+    const WayptData& w(waypoints[index]);
+    if (w.wpt->altitudeRestriction() == RESTRICT_AT) {
+      return index;
+    }
+    
+    // principal base case is runways.
+    const std::string& ty(w.wpt->type());
+    if (ty == "runway") {
+      return index; // runway always has a known elevation
+    }
+    
+    if (index == 0) {
+      SG_LOG(SG_NAVAID, SG_WARN, "findPreceedingKnownAltitude: no preceding altitude value found");
+      return -1;
+    }
+    
+    // recurse earlier in the route
+    return findPreceedingKnownAltitude(index - 1);
+  }
+  
+  int findNextKnownAltitude(unsigned int index) const
+  {
+    if (index >= waypoints.size()) {
+      SG_LOG(SG_NAVAID, SG_WARN, "findNextKnownAltitude: no next altitude value found");
+      return -1;
+    }
+    
+    const WayptData& w(waypoints[index]);
+    if (w.wpt->altitudeRestriction() == RESTRICT_AT) {
+      return index;
+    }
+    
+    // principal base case is runways.
+    const std::string& ty(w.wpt->type());
+    if (ty == "runway") {
+      return index; // runway always has a known elevation
+    }
+    
+    if (index == waypoints.size() - 1) {
+      SG_LOG(SG_NAVAID, SG_WARN, "findNextKnownAltitude: no next altitude value found");
+      return -1;
+    }
+    
+    return findNextKnownAltitude(index + 1);
+  }
+  
+  double altitudeForIndex(int index) const
+  {
+    const WayptData& w(waypoints[index]);
+    if (w.wpt->altitudeRestriction() != RESTRICT_NONE) {
+      return w.wpt->altitudeFt();
+    }
+    
+    const std::string& ty(w.wpt->type());
+    if (ty == "runway") {
+      FGRunway* rwy = static_cast<RunwayWaypt*>(w.wpt.get())->runway();
+      return rwy->threshold().getElevationFt();
+    }
+    
+    SG_LOG(SG_NAVAID, SG_WARN, "altitudeForIndex: waypoint has no explicit altitude");
+    return 0.0;
+  }
+  
+  double groundSpeedForAltitude(double altitude) const
+  {
+      PerformanceBracketVec::const_iterator it = findPerformanceBracket(altitude);
+      if (it->speedIsMach) {
+          return 300.0;
+      } else {
+          // FIXME - convert IAS to ground-speed, using standard atmosphere / temperature model
+          return it->speedIASOrMach;
+      }
+
+#if 0
+    if (0) {
+      double mach;
+      
+      if (it->speedIsMach) {
+        mach = it->speedIASOrMach; // easy
+      } else {
+        const double Cs_0 = 661.4786; // speed of sound at sea level, knots
+        const double P_0 = 29.92126;
+        const double P = P_0 * pow(, );
+        // convert IAS (which we will treat as CAS) to Mach based on altitude
+      }
+      
+      double oatK;
+      double Cs = sqrt(SG_gamma * SG_R_m2_p_s2_p_K * oatK);
+      
+      double tas = mach * Cs;
+      
+/*
+      P_0= 29.92126 "Hg = 1013.25 mB = 2116.2166 lbs/ft^2
+      P= P_0*(1-6.8755856*10^-6*PA)^5.2558797, pressure altitude, PA<36,089.24ft
+      CS= 38.967854*sqrt(T+273.15)  where T is the (static/true) OAT in Celsius.
+      
+      DP=P_0*((1 + 0.2*(IAS/CS_0)^2)^3.5 -1)
+      M=(5*( (DP/P + 1)^(2/7) -1) )^0.5   (*)
+      TAS= M*CS
+*/
+    }
+#endif
+  }
+
+  double distanceBetweenIndices(int from, int to) const
+  {
+    double total = 0.0;
+    
+    for (int i=from+1; i<= to; ++i) {
+      total += waypoints[i].pathDistanceM;
+    }
+    
+    return total;
+  }
+  
+  void initPerfData()
+  {
+      pathTurnRate = 3.0; // 3 deg/sec = 180deg/min = standard rate turn
+      switch (aircraftCategory) {
+      case ICAO_AIRCRAFT_CATEGORY_A:
+          perf.push_back(PerformanceBracket(4000, 600, 1200, 75));
+          perf.push_back(PerformanceBracket(10000, 600, 1200, 140));
+          break;
+
+      case ICAO_AIRCRAFT_CATEGORY_B:
+          perf.push_back(PerformanceBracket(4000, 100, 1200, 100));
+          perf.push_back(PerformanceBracket(10000, 800, 1200, 160));
+          perf.push_back(PerformanceBracket(18000, 600, 1800, 200));
+          break;
+
+      case ICAO_AIRCRAFT_CATEGORY_C:
+          perf.push_back(PerformanceBracket(4000, 1800, 1800, 150));
+          perf.push_back(PerformanceBracket(10000, 1800, 1800, 200));
+          perf.push_back(PerformanceBracket(18000, 1200, 1800, 270));
+          perf.push_back(PerformanceBracket(60000, 800, 1200, 0.80, true /* is Mach */));
+          break;
+
+      case ICAO_AIRCRAFT_CATEGORY_D:
+      case ICAO_AIRCRAFT_CATEGORY_E:
+      default:
+
+          perf.push_back(PerformanceBracket(4000, 1800, 1800, 180));
+          perf.push_back(PerformanceBracket(10000, 1800, 1800, 230));
+          perf.push_back(PerformanceBracket(18000, 1200, 1800, 270));
+          perf.push_back(PerformanceBracket(60000, 800, 1200, 0.87, true /* is Mach */));
+          break;
+      }
+  }
+
+    const WayptData& previousValidWaypoint(int index) const
+    {
+        assert(index > 0);
+        if (waypoints[index-1].skipped) {
+            return waypoints[index-2];
+        }
+
+        return waypoints[index-1];
+    }
+
+}; // of RoutePathPrivate class
+
+RoutePath::RoutePath(const flightgear::FlightPlan* fp) :
+  d(new RoutePathPrivate)
+{
+    for (int l=0; l<fp->numLegs(); ++l) {
+        d->waypoints.push_back(WayptData(fp->legAtIndex(l)->waypoint()));
+    }
+
+    d->aircraftCategory = fp->icaoAircraftCategory()[0];
+    d->constrainLegCourses = fp->followLegTrackToFixes();
+    commonInit();
+}
+
+RoutePath::~RoutePath()
+{
+}
+
+void RoutePath::commonInit()
+{
+  d->initPerfData();
+  
+  WayptDataVec::iterator it;
+  for (it = d->waypoints.begin(); it != d->waypoints.end(); ++it) {
+    it->initPass0();
+  }
+  
+  for (unsigned int i=1; i<d->waypoints.size(); ++i) {
+    WayptData* nextPtr = ((i + 1) < d->waypoints.size()) ? &d->waypoints[i+1] : 0;
+    d->waypoints[i].initPass1(d->waypoints[i-1], nextPtr);
+  }
+
+  for (unsigned int i=0; i<d->waypoints.size(); ++i) {
+      if (d->waypoints[i].skipped) {
+          continue;
+      }
+
+      double alt = 0.0; // FIXME
+      double gs = d->groundSpeedForAltitude(alt);
+      double radiusM = ((360.0 / d->pathTurnRate) * gs * SG_KT_TO_MPS) / SGMiscd::twopi();
+
+      if (i > 0) {
+          const WayptData& prev(d->previousValidWaypoint(i));
+          d->waypoints[i].computeLegCourse(prev, radiusM);
+          d->computeDynamicPosition(i);
+      }
+
+    if (i < (d->waypoints.size() - 1)) {
+        int nextIndex = i + 1;
+        if (d->waypoints[nextIndex].skipped) {
+            nextIndex++;
+        }
+        WayptData& next(d->waypoints[nextIndex]);
+        next.computeLegCourse(d->waypoints[i], radiusM);
+
+      if (next.legCourseValid) {
+        d->waypoints[i].computeTurn(radiusM, d->constrainLegCourses, next);
+      } else {
+        // next waypoint has indeterminate course. Let's create a sharp turn
+        // this can happen when the following point is ATC vectors, for example.
+        d->waypoints[i].turnEntryPos = d->waypoints[i].pos;
+        d->waypoints[i].turnExitPos = d->waypoints[i].pos;
+      }
+    } else {
+      // final waypt, fix up some data
+      d->waypoints[i].turnExitPos = d->waypoints[i].pos;
+    }
+    
+    // now turn is computed, can resolve distances
+    d->waypoints[i].pathDistanceM = computeDistanceForIndex(i);
+  }
+}
+
+SGGeodVec RoutePath::pathForIndex(int index) const
+{
+  const WayptData& w(d->waypoints[index]);
+  const std::string& ty(w.wpt->type());
+  SGGeodVec r;
+
+    if (d->waypoints[index].skipped) {
+        return SGGeodVec();
+    }
+
+  if (ty == "vectors") {
+      // ideally we'd show a stippled line to connect the route?
     return SGGeodVec();
   }
   
-  r.push_back(pos);
+  if (ty== "hold") {
+    return pathForHold((Hold*) d->waypoints[index].wpt.get());
+  }
+
+    if (index > 0) {
+      const WayptData& prev(d->previousValidWaypoint(index));
+      prev.turnExitPath(r);
+      
+      SGGeod from = prev.turnExitPos,
+        to = w.turnEntryPos;
+      
+      // compute rounding offset, we want to round towards the direction of travel
+      // which depends on the east/west sign of the longitude change
+      double lonDelta = to.getLongitudeDeg() - from.getLongitudeDeg();
+      if (fabs(lonDelta) > 0.5) {
+        interpolateGreatCircle(from, to, r);
+      }
+    } // of have previous waypoint
+
+    w.turnEntryPath(r);
   
-  if (_waypts[index]->type() == "runway") {
+  if (ty == "runway") {
     // runways get an extra point, at the end. this is particularly
     // important so missed approach segments draw correctly
-    FGRunway* rwy = static_cast<RunwayWaypt*>(_waypts[index].get())->runway();
+    FGRunway* rwy = static_cast<RunwayWaypt*>(w.wpt.get())->runway();
     r.push_back(rwy->end());
   }
   
   return r;
 }
 
-SGGeod RoutePath::positionForIndex(int index) const
+void RoutePath::interpolateGreatCircle(const SGGeod& aFrom, const SGGeod& aTo, SGGeodVec& r) const
 {
-  SGGeod r;
-  bool ok = computedPositionForIndex(index, r);
-  if (!ok) {
-    return SGGeod();
+  SGGeoc gcFrom = SGGeoc::fromGeod(aFrom),
+    gcTo = SGGeoc::fromGeod(aTo);
+  
+  double lonDelta = gcTo.getLongitudeRad() - gcFrom.getLongitudeRad();
+  if (fabs(lonDelta) < 1e-3) {
+    return;
   }
   
-  return r;
+  lonDelta = SGMiscd::normalizeAngle(lonDelta);    
+  int steps = static_cast<int>(fabs(lonDelta) * SG_RADIANS_TO_DEGREES * 2);
+  double lonStep = (lonDelta / steps);
+  
+  double lon = gcFrom.getLongitudeRad() + lonStep;
+  for (int s=0; s < (steps - 1); ++s) {
+    lon = SGMiscd::normalizeAngle(lon);
+    double lat = latitudeForGCLongitude(gcFrom, gcTo, lon);
+    r.push_back(SGGeod::fromGeoc(SGGeoc::fromRadM(lon, lat, SGGeodesy::EQURAD)));
+    //SG_LOG(SG_GENERAL, SG_INFO, "lon:" << lon * SG_RADIANS_TO_DEGREES << " gives lat " << lat * SG_RADIANS_TO_DEGREES);
+    lon += lonStep;
+  }
+}
+
+SGGeod RoutePath::positionForIndex(int index) const
+{
+  return d->waypoints[index].pos;
 }
 
 SGGeodVec RoutePath::pathForHold(Hold* hold) const
@@ -127,14 +1001,16 @@ SGGeodVec RoutePath::pathForHold(Hold* hold) const
   int turnSteps = 16;
   double hdg = hold->inboundRadial();
   double turnDelta = 180.0 / turnSteps;
+  double altFt = 0.0; // FIXME
+  double gsKts = d->groundSpeedForAltitude(altFt);
   
   SGGeodVec r;
   double az2;
-  double stepTime = turnDelta / _pathTurnRate; // in seconds
-  double stepDist = _pathIAS * (stepTime / 3600.0) * SG_NM_TO_METER;
+  double stepTime = turnDelta / d->pathTurnRate; // in seconds
+  double stepDist = gsKts * (stepTime / 3600.0) * SG_NM_TO_METER;
   double legDist = hold->isDistance() ? 
     hold->timeOrDistance() 
-    : _pathIAS * (hold->timeOrDistance() / 3600.0);
+    : gsKts * (hold->timeOrDistance() / 3600.0);
   legDist *= SG_NM_TO_METER;
   
   if (hold->isLeftHanded()) {
@@ -160,206 +1036,111 @@ SGGeodVec RoutePath::pathForHold(Hold* hold) const
   return r;
 }
 
-/**
- * the path context holds the state of of an imaginary aircraft traversing
- * the route, and limits the rate at which heading / altitude / position can
- * change
- */
-class RoutePath::PathCtx
-{
-public:
-  SGGeod pos;
-  double heading;
-};
-
-bool RoutePath::computedPositionForIndex(int index, SGGeod& r) const
+double RoutePath::computeDistanceForIndex(int index) const
 {
-  if ((index < 0) || (index >= (int) _waypts.size())) {
-    throw sg_range_exception("waypt index out of range", 
-      "RoutePath::computedPositionForIndex");
-  }
-
-  WayptRef w = _waypts[index];
-  if (!w->flag(WPT_DYNAMIC)) {
-    r = w->position();
-    return true;
+  if ((index < 0) || (index >= (int) d->waypoints.size())) {
+    throw sg_range_exception("waypt index out of range",
+                             "RoutePath::computeDistanceForIndex");
   }
   
-  if (w->type() == "radialIntercept") {
-    // radial intersection along track
-    SGGeod prev;
-    if (!computedPositionForIndex(index - 1, prev)) {
-      return false;
-    }
-  
-    SGGeoc prevGc = SGGeoc::fromGeod(prev);
-    SGGeoc navid = SGGeoc::fromGeod(w->position());
-    SGGeoc rGc;
-    double magVar = magVarFor(prev);
-    
-    RadialIntercept* i = (RadialIntercept*) w.get();
-    double radial = i->radialDegMagnetic() + magVar;
-    double track = i->courseDegMagnetic() + magVar;
-    bool ok = geocRadialIntersection(prevGc, track, navid, radial, rGc);
-    if (!ok) {
-      return false;
-    }
-    
-    r = SGGeod::fromGeoc(rGc);
-    return true;
-  } else if (w->type() == "dmeIntercept") {
-    // find the point along the DME track, from prev, that is the correct distance
-    // from the DME
-    SGGeod prev;
-    if (!computedPositionForIndex(index - 1, prev)) {
-      return false;
-    }
-    
-    DMEIntercept* di = (DMEIntercept*) w.get();
-    
-    SGGeoc prevGc = SGGeoc::fromGeod(prev);
-    SGGeoc navid = SGGeoc::fromGeod(w->position());
-    double distRad = di->dmeDistanceNm() * SG_NM_TO_RAD;
-    SGGeoc rGc;
-
-    SGGeoc bPt;
-    double crs = di->courseDegMagnetic() + magVarFor(prev);
-    SGGeodesy::advanceRadM(prevGc, crs, 100 * SG_NM_TO_RAD, bPt);
+  if (index == 0) {
+    // first waypoint, distance is 0
+    return 0.0;
+  }
 
-    double dNm = pointsKnownDistanceFromGC(prevGc, bPt, navid, distRad);
-    if (dNm < 0.0) {
-      return false;
+    if (d->waypoints[index].skipped) {
+        return 0.0;
     }
-    
-    double az2;
-    SGGeodesy::direct(prev, crs, dNm * SG_NM_TO_METER, r, az2);
-    return true;
-  } else if (w->type() == "hdgToAlt") {
-    HeadingToAltitude* h = (HeadingToAltitude*) w.get();
-    double climb = h->altitudeFt() - computeAltitudeForIndex(index - 1);
-    double d = distanceForClimb(climb);
-
-    SGGeod prevPos;
-    if (!computedPositionForIndex(index - 1, prevPos)) {
-      return false;
-    }
-    
-    double hdg = h->headingDegMagnetic() + magVarFor(prevPos);
-    
-    double az2;
-    SGGeodesy::direct(prevPos, hdg, d * SG_NM_TO_METER, r, az2);
-    return true;
-  } else if (w->type() == "vectors"){
-    return false;
-  } else if (w->type() == "hold") {
-    r = w->position();
-    return true;
-  }
-  
-  SG_LOG(SG_NAVAID, SG_INFO, "RoutePath::computedPositionForIndex: unhandled type:" << w->type());
-  return false;
-}
 
-double RoutePath::computeAltitudeForIndex(int index) const
-{
-  if ((index < 0) || (index >= (int) _waypts.size())) {
-    throw sg_range_exception("waypt index out of range", 
-      "RoutePath::computeAltitudeForIndex");
-  }
-  
-  WayptRef w = _waypts[index];
-  if (w->altitudeRestriction() != RESTRICT_NONE) {
-    return w->altitudeFt(); // easy!
-  }
-  
-  if (w->type() == "runway") {
-    FGRunway* rwy = static_cast<RunwayWaypt*>(w.get())->runway();
-    return rwy->threshold().getElevationFt();
-  } else if ((w->type() == "hold") || (w->type() == "vectors")) {
-    // pretend we don't change altitude in holds/vectoring
-    return computeAltitudeForIndex(index - 1);
-  }
-
-  double prevAlt = computeAltitudeForIndex(index - 1);
-// find distance to previous, and hence climb/descent 
-  SGGeod pos, prevPos;
-  
-  if (!computedPositionForIndex(index, pos) ||
-      !computedPositionForIndex(index - 1, prevPos))
-  {
-    SG_LOG(SG_NAVAID, SG_WARN, "unable to compute position for waypoints");
-    throw sg_range_exception("unable to compute position for waypoints");
-  }
-  
-  double d = SGGeodesy::distanceNm(prevPos, pos);
-  double tMinutes = (d / _pathIAS) * 60.0; // (nm / knots) * 60 = time in minutes
+    const WayptData& prev(d->previousValidWaypoint(index));
+  double dist = SGGeodesy::distanceM(prev.turnExitPos,
+                              d->waypoints[index].turnEntryPos);
+  dist += prev.turnDistanceM();
   
-  double deltaFt; // change in altitude in feet
-  if (w->flag(WPT_ARRIVAL) && !w->flag(WPT_MISS)) {
-    deltaFt = -_pathDescentFPM * tMinutes;
-  } else {
-    deltaFt = _pathClimbFPM * tMinutes;
+  if (!d->waypoints[index].flyOver) {
+    // add entry distance
+    dist += d->waypoints[index].turnDistanceM();
   }
   
-  return prevAlt + deltaFt;
+  return dist;
 }
 
-double RoutePath::computeTrackForIndex(int index) const
+double RoutePath::trackForIndex(int index) const
 {
-  if ((index < 0) || (index >= (int) _waypts.size())) {
-    throw sg_range_exception("waypt index out of range", 
-      "RoutePath::computeTrackForIndex");
-  }
-  
-  WayptRef w = _waypts[index];
-  if (w->type() == "radialIntercept") {
-    RadialIntercept* r = (RadialIntercept*) w.get();
-    return r->courseDegMagnetic();
-  } else if (w->type() == "dmeIntercept") {
-    DMEIntercept* d = (DMEIntercept*) w.get();
-    return d->courseDegMagnetic();
-  } else if (w->type() == "hdgToAlt") {
-    HeadingToAltitude* h = (HeadingToAltitude*) w.get();
-    return h->headingDegMagnetic();
-  } else if (w->type() == "hold") {
-    Hold* h = (Hold*) w.get();
-    return h->inboundRadial();
-  } else if (w->type() == "vectors") {
-    SG_LOG(SG_NAVAID, SG_WARN, "asked for track from VECTORS");
-    throw sg_range_exception("asked for track from vectors waypt");
-  } else if (w->type() == "runway") {
-    FGRunway* rwy = static_cast<RunwayWaypt*>(w.get())->runway();
-    return rwy->headingDeg();
-  }
-  
-// course based upon previous and current pos
-  SGGeod pos, prevPos;
-  
-  if (!computedPositionForIndex(index, pos) ||
-      !computedPositionForIndex(index - 1, prevPos))
-  {
-    SG_LOG(SG_NAVAID, SG_WARN, "unable to compute position for waypoints");
-    throw sg_range_exception("unable to compute position for waypoints");
-  }
-
-  return SGGeodesy::courseDeg(prevPos, pos);
+    if (d->waypoints[index].skipped)
+        return trackForIndex(index - 1);
+  return d->waypoints[index].legCourseTrue;
 }
 
-double RoutePath::distanceForClimb(double climbFt) const
+double RoutePath::distanceForIndex(int index) const
 {
-  double t = 0.0; // in seconds
-  if (climbFt > 0.0) {
-    t = (climbFt / _pathClimbFPM) * 60.0;
-  } else if (climbFt < 0.0) {
-    t = (climbFt / _pathDescentFPM) * 60.0;
-  }
-  
-  return _pathIAS * (t / 3600.0);
+  return d->waypoints[index].pathDistanceM;
 }
 
-double RoutePath::magVarFor(const SGGeod& geod) const
+double RoutePath::distanceBetweenIndices(int from, int to) const
 {
-  double jd = globals->get_time_params()->getJD();
-  return sgGetMagVar(geod, jd) * SG_RADIANS_TO_DEGREES;
+  return d->distanceBetweenIndices(from, to);
 }
 
+SGGeod RoutePath::positionForDistanceFrom(int index, double distanceM) const
+{
+    int sz = (int) d->waypoints.size();
+    if (index < 0) {
+        index = sz - 1; // map negative values to end of the route
+    }
+
+    if ((index < 0) || (index >= sz)) {
+        throw sg_range_exception("waypt index out of range",
+                                 "RoutePath::positionForDistanceFrom");
+    }
+
+    // find the actual leg we're within
+    if (distanceM < 0.0) {
+        // scan backwards
+        while ((index > 0) && (distanceM < 0.0)) {
+            // we are looking at index n, say 4, but with a negative distance.
+            // we want to look at index n-1 (so, 3), and see if this makes
+            // distance positive. We need to offset by distance from 3 -> 4,
+            // which is waypoint 4's path distance.
+            distanceM += d->waypoints[index].pathDistanceM;
+            --index;
+        }
+
+        if (distanceM < 0.0) {
+            // still negative, return route start
+            return d->waypoints[0].pos;
+        }
+
+    } else {
+        // scan forwards
+        int nextIndex = index + 1;
+        while ((nextIndex < sz) && (d->waypoints[nextIndex].pathDistanceM < distanceM)) {
+            distanceM -= d->waypoints[nextIndex].pathDistanceM;
+            index = nextIndex++;
+        }
+    }
+
+    if ((index + 1) >= sz) {
+        // past route end, just return final position
+        return d->waypoints[sz - 1].pos;
+    }
+
+    const WayptData& wpt(d->waypoints[index]);
+    const WayptData& next(d->waypoints[index+1]);
+
+    if (wpt.turnPathDistanceM > distanceM) {
+        // on the exit path of current wpt
+        return wpt.pointAlongExitPath(distanceM);
+    } else {
+        distanceM -= wpt.turnPathDistanceM;
+    }
+
+    double corePathDistance = next.pathDistanceM - next.turnPathDistanceM;
+    if (next.hasEntry && (distanceM > corePathDistance)) {
+        // on the entry path of next waypoint
+        return next.pointAlongEntryPath(distanceM - corePathDistance);
+    }
+
+    // linear between turn exit and turn entry points
+    return SGGeodesy::direct(wpt.turnExitPos, next.legCourseTrue, distanceM);
+}