]> git.mxchange.org Git - flightgear.git/blobdiff - src/Navaids/FlightPlan.cxx
Fix distance-along-path computation
[flightgear.git] / src / Navaids / FlightPlan.cxx
index 2087d5cc1fb165c1efb6b0f990ad05811854ef6f..83edf5c78aded2b7b566f55fbaf73da7d0692ff0 100644 (file)
 #include <simgear/misc/sgstream.hxx>
 #include <simgear/misc/strutils.hxx>
 #include <simgear/props/props_io.hxx>
+#include <simgear/xml/easyxml.hxx>
 
 // FlightGear
 #include <Main/globals.hxx>
 #include "Main/fg_props.hxx"
 #include <Navaids/procedure.hxx>
 #include <Navaids/waypoint.hxx>
+#include <Navaids/routePath.hxx>
 
 using std::string;
 using std::vector;
@@ -67,6 +69,7 @@ FlightPlan::FlightPlan() :
   _sid(NULL),
   _star(NULL),
   _approach(NULL),
+  _totalDistance(0.0),
   _delegate(NULL)
 {
   _departureChanged = _arrivalChanged = _waypointsChanged = _currentWaypointChanged = false;
@@ -269,14 +272,24 @@ int FlightPlan::clearWayptsWithFlag(WayptFlag flag)
       ++count;
     }
   }
-  
+
   // test if the current leg will be removed
   bool currentIsBeingCleared = false;
-  if (_currentIndex >= 0) {
-    currentIsBeingCleared = _legs[_currentIndex]->waypoint()->flag(flag);
+  Leg* curLeg = currentLeg();
+  if (curLeg) {
+    currentIsBeingCleared = curLeg->waypoint()->flag(flag);
   }
   
   _currentIndex -= count;
+    
+    // if we're clearing the current waypoint, what shall we do with the
+    // index? there's various options, but safest is to select no waypoint
+    // and let the use re-activate.
+    // http://code.google.com/p/flightgear-bugs/issues/detail?id=1134
+    if (currentIsBeingCleared) {
+        SG_LOG(SG_GENERAL, SG_INFO, "currentIsBeingCleared:" << currentIsBeingCleared);
+        _currentIndex = -1;
+    }
   
 // now delete and remove
   RemoveWithFlag rf(flag);
@@ -346,6 +359,17 @@ int FlightPlan::findWayptIndex(const SGGeod& aPos) const
   
   return -1;
 }
+  
+int FlightPlan::findWayptIndex(const FGPositionedRef aPos) const
+{
+  for (int i=0; i<numLegs(); ++i) {
+    if (_legs[i]->waypoint()->source() == aPos) {
+      return i;
+    }
+  }
+  
+  return -1;
+}
 
 FlightPlan::Leg* FlightPlan::currentLeg() const
 {
@@ -595,7 +619,7 @@ bool FlightPlan::save(const SGPath& path)
     return false;
   }
 }
-  
+
 bool FlightPlan::load(const SGPath& path)
 {
   if (!path.exists())
@@ -604,21 +628,133 @@ bool FlightPlan::load(const SGPath& path)
            << "'. The file does not exist.");
     return false;
   }
-  
-  SGPropertyNode_ptr routeData(new SGPropertyNode);
+
   SG_LOG(SG_NAVAID, SG_INFO, "going to read flight-plan from:" << path.str());
   
   bool Status = false;
   lockDelegate();
+
+  // try different file formats
+  if (loadGpxFormat(path)) // GPX format
+      Status = true;
+  else
+  if (loadXmlFormat(path)) // XML property data
+      Status = true;
+  else
+  if (loadPlainTextFormat(path)) // simple textual list of waypoints
+      Status = true;
+
+  _waypointsChanged = true;
+  unlockDelegate();
+
+  return Status;
+}
+
+/** XML loader for GPX file format */
+class GpxXmlVisitor : public XMLVisitor
+{
+public:
+    GpxXmlVisitor(FlightPlan* fp) : _fp(fp), _lat(-9999), _lon(-9999) {}
+
+    virtual void startElement (const char * name, const XMLAttributes &atts);
+    virtual void endElement (const char * name);
+    virtual void data (const char * s, int length);
+
+private:
+    FlightPlan* _fp;
+    double      _lat, _lon;
+    string      _element;
+    string      _waypoint;
+};
+
+void GpxXmlVisitor::startElement(const char * name, const XMLAttributes &atts)
+{
+    _element = name;
+    if (strcmp(name, "rtept")==0)
+    {
+        _waypoint = "";
+        _lat = _lon = -9999;
+
+        const char* slat = atts.getValue("lat");
+        const char* slon = atts.getValue("lon");
+        if (slat && slon)
+        {
+            _lat = atof(slat);
+            _lon = atof(slon);
+        }
+    }
+}
+
+void GpxXmlVisitor::data(const char * s, int length)
+{
+    // use "name" when given, otherwise use "cmt" (comment) as ID
+    if ((_element == "name")||
+        ((_waypoint == "")&&(_element == "cmt")))
+    {
+        char* buf = (char*) malloc(length+1);
+        memcpy(buf, s, length);
+        buf[length] = 0;
+        _waypoint = buf;
+        free(buf);
+    }
+}
+
+void GpxXmlVisitor::endElement(const char * name)
+{
+    _element = "";
+    if (strcmp(name, "rtept") == 0)
+    {
+        if (_lon > -9990.0)
+        {
+            _fp->insertWayptAtIndex(new BasicWaypt(SGGeod::fromDeg(_lon, _lat), _waypoint.c_str(), NULL), -1);
+        }
+    }
+}
+
+/** Load a flightplan in GPX format */
+bool FlightPlan::loadGpxFormat(const SGPath& path)
+{
+    if (path.lower_extension() != "gpx")
+    {
+        // not a valid GPX file
+        return false;
+    }
+
+    _legs.clear();
+    GpxXmlVisitor gpxVistor(this);
+    try
+    {
+        readXML(path.str(), gpxVistor);
+    } catch (sg_exception& e)
+    {
+        // XML parsing fails => not a GPX XML file
+        SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load flight-plan in GPX format: '" << e.getOrigin()
+                     << "'. " << e.getMessage());
+        return false;
+    }
+
+    if (numLegs() == 0)
+    {
+        SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load flight-plan in GPX format. No route found.");
+        return false;
+    }
+
+    return true;
+}
+
+/** Load a flightplan in FlightGear XML property format */
+bool FlightPlan::loadXmlFormat(const SGPath& path)
+{
+  SGPropertyNode_ptr routeData(new SGPropertyNode);
   try {
     readProperties(path.str(), routeData);
-  } catch (sg_exception& ) {
-    // if XML parsing fails, the file might be simple textual list of waypoints
-    Status = loadPlainTextRoute(path);
-    routeData = 0;
-    _waypointsChanged = true;
+  } catch (sg_exception& e) {
+     SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load flight-plan '" << e.getOrigin()
+             << "'. " << e.getMessage());
+    // XML parsing fails => not a property XML file
+    return false;
   }
-  
+
   if (routeData.valid())
   {
     try {
@@ -630,16 +766,14 @@ bool FlightPlan::load(const SGPath& path)
       } else {
         throw sg_io_exception("unsupported XML route version");
       }
-      Status = true;
+      return true;
     } catch (sg_exception& e) {
       SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load flight-plan '" << e.getOrigin()
              << "'. " << e.getMessage());
-      Status = false;
     }
   }
-  
-  unlockDelegate();  
-  return Status;
+
+  return false;
 }
 
 void FlightPlan::loadXMLRouteHeader(SGPropertyNode_ptr routeData)
@@ -706,12 +840,14 @@ void FlightPlan::loadVersion2XMLRoute(SGPropertyNode_ptr routeData)
   
   // route nodes
   _legs.clear();
-  SGPropertyNode_ptr routeNode = routeData->getChild("route", 0);    
-  for (int i=0; i<routeNode->nChildren(); ++i) {
-    SGPropertyNode_ptr wpNode = routeNode->getChild("wp", i);
-    Leg* l = new Leg(this, Waypt::createFromProperties(NULL, wpNode));
-    _legs.push_back(l);
-  } // of route iteration
+  SGPropertyNode_ptr routeNode = routeData->getChild("route", 0);
+  if (routeNode.valid()) {
+    for (int i=0; i<routeNode->nChildren(); ++i) {
+      SGPropertyNode_ptr wpNode = routeNode->getChild("wp", i);
+      Leg* l = new Leg(this, Waypt::createFromProperties(NULL, wpNode));
+      _legs.push_back(l);
+    } // of route iteration
+  }
   _waypointsChanged = true;
 }
 
@@ -779,7 +915,8 @@ WayptRef FlightPlan::parseVersion1XMLWaypt(SGPropertyNode* aWP)
   return w;
 }
 
-bool FlightPlan::loadPlainTextRoute(const SGPath& path)
+/** Load a flightplan in FlightGear plain-text format */
+bool FlightPlan::loadPlainTextFormat(const SGPath& path)
 {
   try {
     sg_gzifstream in(path.str().c_str());
@@ -1050,60 +1187,29 @@ double FlightPlan::Leg::distanceAlongRoute() const
 void FlightPlan::rebuildLegData()
 {
   _totalDistance = 0.0;
-  int lastLeg = static_cast<int>(_legs.size()) - 1;
-  for (int l=0; l<lastLeg; ++l) {
-    Leg* cur = _legs[l];
-    Leg* next = _legs[l + 1];
+  double totalDistanceIncludingMissed = 0.0;
+  RoutePath path(this);
+  
+  for (unsigned int l=0; l<_legs.size(); ++l) {
+    _legs[l]->_courseDeg = path.trackForIndex(l);
+    _legs[l]->_pathDistance = path.distanceForIndex(l) * SG_METER_TO_NM;
+
+    totalDistanceIncludingMissed += _legs[l]->_pathDistance;
+    // distance along path includes our own leg distance
+    _legs[l]->_distanceAlongPath = totalDistanceIncludingMissed;
     
-    std::pair<double, double> crsDist =
-      next->waypoint()->courseAndDistanceFrom(cur->waypoint()->position());
-    _legs[l]->_courseDeg = crsDist.first;
-    _legs[l]->_pathDistance = crsDist.second * SG_METER_TO_NM;
-    _legs[l]->_distanceAlongPath = _totalDistance;
-    _totalDistance += crsDist.second * SG_METER_TO_NM;
-  } // of legs iteration
+    // omit missed-approach waypoints from total distance calculation
+    if (!_legs[l]->waypoint()->flag(WPT_MISS)) {
+      _totalDistance += _legs[l]->_pathDistance;
+    }
+} // of legs iteration
   
-// set some data on the final leg
-  if (lastLeg > 0) {
-    // keep the same course as the final leg, when passing the final
-    // waypoint
-    _legs[lastLeg]->_courseDeg = _legs[lastLeg - 1]->_courseDeg;
-    _legs[lastLeg]->_pathDistance = 0.0;
-    _legs[lastLeg]->_distanceAlongPath = _totalDistance;
-  }
 }
   
 SGGeod FlightPlan::pointAlongRoute(int aIndex, double aOffsetNm) const
 {
-  if (aIndex >= (int) _legs.size()) {
-    throw sg_range_exception();
-  }
-  
-  const int lastLeg = static_cast<int>(_legs.size()) - 1;
-// convert the relative offset and leg index into an absolute, positive
-// distance in nm from the route origin. This means we can simply walk
-// forwards to find the actual leg.
-  Leg* leg = _legs[(aIndex >= 0) ? aIndex : lastLeg];
-  double absolutePathDistance = leg->_distanceAlongPath + aOffsetNm;
-  if (absolutePathDistance < 0.0) {
-    return _legs[0]->waypoint()->position(); // begining of route
-  }
-  
-  if (absolutePathDistance > _totalDistance) {
-    return _legs[lastLeg]->waypoint()->position(); // end of route
-  }
-  
-// find the leg containing the absolute distance
-  for (int l=0; l<lastLeg; ++l) {
-    leg = _legs[l];
-    if (absolutePathDistance < leg->_pathDistance) {
-      break; // found our matching leg
-    }
-    absolutePathDistance -= leg->_pathDistance;
-  } // of forwards walk along route to find leg
-  
-  return SGGeodesy::direct(leg->waypoint()->position(),
-                               leg->_courseDeg, absolutePathDistance * SG_NM_TO_METER);
+    RoutePath rp(this);
+    return rp.positionForDistanceFrom(aIndex, aOffsetNm * SG_NM_TO_METER);
 }
     
 void FlightPlan::lockDelegate()