]> git.mxchange.org Git - flightgear.git/blobdiff - src/GUI/MapWidget.cxx
Launcher: Maintain aircraft selection better
[flightgear.git] / src / GUI / MapWidget.cxx
index e166e24a69bea821a647e8b6fcc1f1fbf6a58858..bb44327fb30d49cdd6dd842652e0ecb1af87048a 100644 (file)
@@ -8,8 +8,6 @@
 #include <algorithm> // for std::sort
 #include <plib/puAux.h>
 
-#include <simgear/sg_inlines.h>
-#include <simgear/route/waypoint.hxx>
 #include <simgear/sg_inlines.h>
 #include <simgear/misc/strutils.hxx>
 #include <simgear/magvar/magvar.hxx>
 #include <Navaids/navrecord.hxx>
 #include <Navaids/navlist.hxx>
 #include <Navaids/fix.hxx>
-#include <Airports/simple.hxx>
+#include <Airports/airport.hxx>
 #include <Airports/runways.hxx>
 #include <Main/fg_os.hxx>      // fgGetKeyModifiers()
 #include <Navaids/routePath.hxx>
+#include <Aircraft/FlightHistory.hxx>
+#include <AIModel/AIAircraft.hxx>
+#include <AIModel/AIManager.hxx>
+#include <AIModel/AIFlightPlan.hxx>
 
 const char* RULER_LEGEND_KEY = "ruler-legend";
 
@@ -68,7 +70,7 @@ static bool puBoxIntersect(const puBox& a, const puBox& b)
 
   return (x0 <= x1) && (y0 <= y1);
 }
-
+    
 class MapData;
 typedef std::vector<MapData*> MapDataVec;
 
@@ -198,6 +200,11 @@ public:
       _width, _height);
   }
 
+  void drawStringUtf8(std::string& utf8Str, double x, double y, puFont fnt)
+  {
+    fnt.drawString(simgear::strutils::utf8ToLatin1(utf8Str).c_str(), x, y);
+  }
+
   void draw()
   {
     validate();
@@ -217,12 +224,12 @@ public:
       glColor3f(0.8, 0.8, 0.8);
 
       for (unsigned int ln=0; ln<_lines.size(); ++ln) {
-        _font.drawString(_lines[ln].c_str(), xPos, yPos);
+        drawStringUtf8(_lines[ln], xPos, yPos, _font);
         yPos -= lineHeight + LINE_LEADING;
       }
     } else {
       glColor3f(0.8, 0.8, 0.8);
-      _font.drawString(_label.c_str(), xx, yy + _fontDescender);
+      drawStringUtf8(_label, xx, yy + _fontDescender, _font);
     }
   }
 
@@ -374,8 +381,79 @@ int MapData::_fontDescender = 0;
 
 ///////////////////////////////////////////////////////////////////////////
 
+// anonymous namespace
+namespace
+{
+    
+class MapAirportFilter : public FGAirport::AirportFilter
+{
+public:
+    MapAirportFilter(SGPropertyNode_ptr nd)
+    {
+        _heliports = nd->getBoolValue("show-heliports", false);
+        _hardRunwaysOnly = nd->getBoolValue("hard-surfaced-airports", true);
+        _minLengthFt = fgGetDouble("/sim/navdb/min-runway-length-ft", 2000);
+    }
+    
+    virtual FGPositioned::Type maxType() const {
+        return _heliports ? FGPositioned::HELIPORT : FGPositioned::AIRPORT;
+    }
+    
+    virtual bool passAirport(FGAirport* aApt) const {
+        if (_hardRunwaysOnly) {
+            return aApt->hasHardRunwayOfLengthFt(_minLengthFt);
+        }
+        
+        return true;
+    }
+    
+    void showAll()
+    {
+        _hardRunwaysOnly = false;
+    }
+    
+private:
+    bool _heliports;
+    bool _hardRunwaysOnly;
+    double _minLengthFt;
+};
+
+class NavaidFilter : public FGPositioned::Filter
+{
+public:
+    NavaidFilter(bool fixesEnabled, bool navaidsEnabled) :
+    _fixes(fixesEnabled),
+    _navaids(navaidsEnabled)
+    {}
+    
+    virtual bool pass(FGPositioned* aPos) const {
+        if (_fixes && (aPos->type() == FGPositioned::FIX)) {
+            // ignore fixes which end in digits - expirmental
+            if (aPos->ident().length() > 4 && isdigit(aPos->ident()[3]) && isdigit(aPos->ident()[4])) {
+                return false;
+            }
+        }
+        
+        return true;
+    }
+    
+    virtual FGPositioned::Type minType() const {
+        return _fixes ? FGPositioned::FIX : FGPositioned::NDB;
+    }
+    
+    virtual FGPositioned::Type maxType() const {
+        return _navaids ? FGPositioned::VOR : FGPositioned::FIX;
+    }
+    
+private:
+    bool _fixes, _navaids;
+};
+    
+} // of anonymous namespace
+
 const int MAX_ZOOM = 12;
 const int SHOW_DETAIL_ZOOM = 8;
+const int SHOW_DETAIL2_ZOOM = 5;
 const int CURSOR_PAN_STEP = 32;
 
 MapWidget::MapWidget(int x, int y, int maxX, int maxY) :
@@ -387,7 +465,9 @@ MapWidget::MapWidget(int x, int y, int maxX, int maxY) :
   _width = maxX - x;
   _height = maxY - y;
   _hasPanned = false;
-
+  _projection = PROJECTION_AZIMUTHAL_EQUIDISTANT;
+  _magneticHeadings = false;
+  
   MapData::setFont(legendFont);
   MapData::setPalette(colour);
 
@@ -412,6 +492,7 @@ void MapWidget::setProperty(SGPropertyNode_ptr prop)
   _root->setIntValue("max-zoom", MAX_ZOOM);
   _root->setBoolValue("centre-on-aircraft", true);
   _root->setBoolValue("draw-data", false);
+  _root->setBoolValue("draw-flight-history", false);
   _root->setBoolValue("magnetic-headings", true);
 }
 
@@ -534,42 +615,126 @@ void MapWidget::zoomOut()
   _root->setIntValue("zoom", zoom() - 1);
 }
 
-void MapWidget::draw(int dx, int dy)
+void MapWidget::update()
 {
-  _aircraft = SGGeod::fromDeg(fgGetDouble("/position/longitude-deg"),
-    fgGetDouble("/position/latitude-deg"));
+    _aircraft = globals->get_aircraft_position();
     
-  bool mag = _root->getBoolValue("magnetic-headings");
-  if (mag != _magneticHeadings) {
-    clearData(); // flush cached data text, since it often includes heading
-    _magneticHeadings =  mag;
-  }
+    bool mag = _root->getBoolValue("magnetic-headings");
+    if (mag != _magneticHeadings) {
+        clearData(); // flush cached data text, since it often includes heading
+        _magneticHeadings =  mag;
+    }
+    
+    if (_hasPanned) {
+        _root->setBoolValue("centre-on-aircraft", false);
+        _hasPanned = false;
+    }
+    else if (_root->getBoolValue("centre-on-aircraft")) {
+        _projectionCenter = _aircraft;
+    }
+    
+    double julianDate = globals->get_time_params()->getJD();
+    _magVar->update(_projectionCenter, julianDate);
+    
+    _aircraftUp = _root->getBoolValue("aircraft-heading-up");
+    if (_aircraftUp) {
+        _upHeading = fgGetDouble("/orientation/heading-deg");
+    } else {
+        _upHeading = 0.0;
+    }
+    
+    if (_magneticHeadings) {
+        _displayHeading = (int) fgGetDouble("/orientation/heading-magnetic-deg");
+    } else {
+        _displayHeading = (int) _upHeading;
+    }
+    
+    _cachedZoom = MAX_ZOOM - zoom();
+    SGGeod topLeft = unproject(SGVec2d(_width/2, _height/2));
+    // compute draw range, including a fudge factor for ILSs and other 'long'
+    // symbols.
+    _drawRangeNm = SGGeodesy::distanceNm(_projectionCenter, topLeft) + 10.0;
   
-  if (_hasPanned) {
-      _root->setBoolValue("centre-on-aircraft", false);
-      _hasPanned = false;
-  }
-  else if (_root->getBoolValue("centre-on-aircraft")) {
-    _projectionCenter = _aircraft;
-  }
+    FGFlightHistory* history = (FGFlightHistory*) globals->get_subsystem("history");
+    if (history && _root->getBoolValue("draw-flight-history")) {
+        _flightHistoryPath = history->pathForHistory();
+    } else {
+        _flightHistoryPath.clear();
+    }
 
-  double julianDate = globals->get_time_params()->getJD();
-  _magVar->update(_projectionCenter, julianDate);
+// make spatial queries. This can trigger loading of XML files, etc, so we do
+// not want to do it in draw(), which can be called from an arbitrary OSG
+// rendering thread.
+    
+    MapAirportFilter af(_root);
+    if (_cachedZoom <= SHOW_DETAIL2_ZOOM) {
+        // show all airports when zoomed in sufficently
+        af.showAll();
+    }
+    
+    bool partial = false;
+    FGPositionedList newItemsToDraw =
+        FGPositioned::findWithinRangePartial(_projectionCenter, _drawRangeNm, &af, partial);
+    
+    bool fixes = _root->getBoolValue("draw-fixes");
+    NavaidFilter f(fixes, _root->getBoolValue("draw-navaids"));
+    if (f.minType() <= f.maxType()) {
+        FGPositionedList navs = FGPositioned::findWithinRange(_projectionCenter, _drawRangeNm, &f);
+        newItemsToDraw.insert(newItemsToDraw.end(), navs.begin(), navs.end());
+    }
 
-  bool aircraftUp = _root->getBoolValue("aircraft-heading-up");
-  if (aircraftUp) {
-    _upHeading = fgGetDouble("/orientation/heading-deg");
-  } else {
-    _upHeading = 0.0;
-  }
+    FGPositioned::TypeFilter tf(FGPositioned::COUNTRY);
+    if (_cachedZoom <= SHOW_DETAIL_ZOOM) {
+        tf.addType(FGPositioned::CITY);
+    }
+    
+    if (_cachedZoom <= SHOW_DETAIL2_ZOOM) {
+        tf.addType(FGPositioned::TOWN);
+    }
+    
+    FGPositionedList poi = FGPositioned::findWithinRange(_projectionCenter, _drawRangeNm, &tf);
+    newItemsToDraw.insert(newItemsToDraw.end(), poi.begin(), poi.end());
+    
+    _itemsToDraw.swap(newItemsToDraw);
+    
+    updateAIObjects();
+}
+
+void MapWidget::updateAIObjects()
+{
+    if (!_root->getBoolValue("draw-traffic") || (_cachedZoom > SHOW_DETAIL_ZOOM)) {
+        _aiDrawVec.clear();
+        return;
+    }
+    
+    AIDrawVec newDrawVec;
+    
+    const SGPropertyNode* ai = fgGetNode("/ai/models", true);
+    for (int i = 0; i < ai->nChildren(); ++i) {
+        const SGPropertyNode *model = ai->getChild(i);
+        // skip bad or dead entries
+        if (!model || model->getIntValue("id", -1) == -1) {
+            continue;
+        }
+        
+        SGGeod pos = SGGeod::fromDegFt(
+                                       model->getDoubleValue("position/longitude-deg"),
+                                       model->getDoubleValue("position/latitude-deg"),
+                                       model->getDoubleValue("position/altitude-ft"));
+        
+        double dist = SGGeodesy::distanceNm(_projectionCenter, pos);
+        if (dist > _drawRangeNm) {
+            continue;
+        }
+    
+        newDrawVec.push_back(DrawAIObject((SGPropertyNode*) model, pos));
+    } // of ai/models iteration
 
-  _cachedZoom = MAX_ZOOM - zoom();
-  SGGeod topLeft = unproject(SGVec2d(_width/2, _height/2));
-  // compute draw range, including a fudge factor for ILSs and other 'long'
-  // symbols
-  _drawRangeNm = SGGeodesy::distanceNm(_projectionCenter, topLeft) + 10.0;
+    _aiDrawVec.swap(newDrawVec);
+}
 
-// drawing operations
+void MapWidget::draw(int dx, int dy)
+{
   GLint sx = (int) abox.min[0],
     sy = (int) abox.min[1];
   glScissor(dx + sx, dy + sy, _width, _height);
@@ -577,13 +742,13 @@ void MapWidget::draw(int dx, int dy)
 
   glMatrixMode(GL_MODELVIEW);
   glPushMatrix();
-  // cetere drawing about the widget center (which is also the
+  // center drawing about the widget center (which is also the
   // projection centre)
   glTranslated(dx + sx + (_width/2), dy + sy + (_height/2), 0.0);
 
   drawLatLonGrid();
 
-  if (aircraftUp) {
+  if (_aircraftUp) {
     int textHeight = legendFont.getStringHeight() + 5;
 
     // draw heading line
@@ -591,27 +756,20 @@ void MapWidget::draw(int dx, int dy)
     glColor3f(1.0, 1.0, 1.0);
     drawLine(loc, SGVec2d(loc.x(), (_height / 2) - textHeight));
 
-    int displayHdg;
-    if (_magneticHeadings) {
-      displayHdg = (int) fgGetDouble("/orientation/heading-magnetic-deg");
-    } else {
-      displayHdg = (int) _upHeading;
-    }
-
     double y = (_height / 2) - textHeight;
     char buf[16];
-    ::snprintf(buf, 16, "%d", displayHdg);
+    ::snprintf(buf, 16, "%d", _displayHeading);
     int sw = legendFont.getStringWidth(buf);
     legendFont.drawString(buf, loc.x() - sw/2, y);
   }
 
-  drawAirports();
-  drawNavaids();
+  drawPositioned();
   drawTraffic();
   drawGPSData();
   drawNavRadio(fgGetNode("/instrumentation/nav[0]", false));
   drawNavRadio(fgGetNode("/instrumentation/nav[1]", false));
   paintAircraftLocation(_aircraft);
+  drawFlightHistory();
   paintRoute();
   paintRuler();
 
@@ -646,8 +804,6 @@ void MapWidget::paintRuler()
   d->setAnchor(clickPos);
   d->setOffset(MapData::VALIGN_TOP | MapData::HALIGN_CENTER, 15);
   d->setPriority(20000);
-
-
 }
 
 void MapWidget::paintAircraftLocation(const SGGeod& aircraftPos)
@@ -681,7 +837,7 @@ void MapWidget::paintRoute()
     return;
   }
 
-  RoutePath path(_route->waypts());
+  RoutePath path(_route->flightPlan());
 
 // first pass, draw the actual lines
   glLineWidth(2.0);
@@ -749,6 +905,25 @@ void MapWidget::paintRoute()
   } // of second waypoint iteration
 }
 
+void MapWidget::drawFlightHistory()
+{
+  if (_flightHistoryPath.empty())
+    return;
+    
+  // first pass, draw the actual lines
+  glLineWidth(2.0);
+  
+  glColor4f(0.0, 0.0, 1.0, 0.7);
+
+  glBegin(GL_LINE_STRIP);
+  for (unsigned int i=0; i<_flightHistoryPath.size(); ++i) {
+    SGVec2d p = project(_flightHistoryPath[i]);
+    glVertex2d(p.x(), p.y());
+  }
+  
+  glEnd();
+}
+
 /**
  * Round a SGGeod to an arbitrary precision.
  * For example, passing precision of 0.5 will round to the nearest 0.5 of
@@ -800,7 +975,13 @@ SGVec2d MapWidget::gridPoint(int ix, int iy)
 
 void MapWidget::drawLatLonGrid()
 {
-  _gridSpacing = 1.0;
+  // Larger grid spacing when zoomed out, to prevent clutter
+  if (_cachedZoom < SHOW_DETAIL_ZOOM) {
+    _gridSpacing = 1.0;
+  } else {
+    _gridSpacing = 5.0;
+  }
+  
   _gridCenter = roundGeod(_gridSpacing, _projectionCenter);
   _gridCache.clear();
 
@@ -820,7 +1001,6 @@ void MapWidget::drawLatLonGrid()
       didDraw |= drawLineClipped(gridPoint(x, iy), gridPoint(x+1, iy));
       didDraw |= drawLineClipped(gridPoint(x, -iy), gridPoint(x, -iy + 1));
       didDraw |= drawLineClipped(gridPoint(x, iy), gridPoint(x, iy - 1));
-
     }
 
     for (int y = -iy; y < iy; ++y) {
@@ -830,7 +1010,7 @@ void MapWidget::drawLatLonGrid()
       didDraw |= drawLineClipped(gridPoint(ix, y), gridPoint(ix - 1, y));
     }
 
-    if (ix > 30) {
+    if (ix > (90/_gridSpacing)-1) {
       break;
     }
   } while (didDraw);
@@ -881,94 +1061,33 @@ void MapWidget::drawGPSData()
   }
 }
 
-class MapAirportFilter : public FGAirport::AirportFilter
-{
-public:
-  MapAirportFilter(SGPropertyNode_ptr nd)
-  {
-    _heliports = nd->getBoolValue("show-heliports", false);
-    _hardRunwaysOnly = nd->getBoolValue("hard-surfaced-airports", true);
-    _minLengthFt = nd->getDoubleValue("min-runway-length-ft", 2000.0);
-  }
-
-  virtual FGPositioned::Type maxType() const {
-    return _heliports ? FGPositioned::HELIPORT : FGPositioned::AIRPORT;
-  }
-
-  virtual bool passAirport(FGAirport* aApt) const {
-    if (_hardRunwaysOnly) {
-      return aApt->hasHardRunwayOfLengthFt(_minLengthFt);
-    }
-
-    return true;
-  }
-
-private:
-  bool _heliports;
-  bool _hardRunwaysOnly;
-  double _minLengthFt;
-};
-
-void MapWidget::drawAirports()
-{
-  MapAirportFilter af(_root);
-  FGPositioned::List apts = FGPositioned::findWithinRange(_projectionCenter, _drawRangeNm, &af);
-  for (unsigned int i=0; i<apts.size(); ++i) {
-    drawAirport((FGAirport*) apts[i].get());
-  }
-}
-
-class NavaidFilter : public FGPositioned::Filter
-{
-public:
-  NavaidFilter(bool fixesEnabled, bool navaidsEnabled) :
-    _fixes(fixesEnabled),
-    _navaids(navaidsEnabled)
-  {}
-
-  virtual bool pass(FGPositioned* aPos) const {
-    if (_fixes && (aPos->type() == FGPositioned::FIX)) {
-      // ignore fixes which end in digits - expirmental
-      if (aPos->ident().length() > 4 && isdigit(aPos->ident()[3]) && isdigit(aPos->ident()[4])) {
-        return false;
-      }
-    }
-
-    return true;
-  }
-
-  virtual FGPositioned::Type minType() const {
-    return _fixes ? FGPositioned::FIX : FGPositioned::VOR;
-  }
-
-  virtual FGPositioned::Type maxType() const {
-    return _navaids ? FGPositioned::NDB : FGPositioned::FIX;
-  }
-
-private:
-  bool _fixes, _navaids;
-};
-
-void MapWidget::drawNavaids()
+void MapWidget::drawPositioned()
 {
-  bool fixes = _root->getBoolValue("draw-fixes");
-  NavaidFilter f(fixes, _root->getBoolValue("draw-navaids"));
-
-  if (f.minType() <= f.maxType()) {
-    FGPositioned::List navs = FGPositioned::findWithinRange(_projectionCenter, _drawRangeNm, &f);
-
-    glLineWidth(1.0);
-    for (unsigned int i=0; i<navs.size(); ++i) {
-      FGPositioned::Type ty = navs[i]->type();
-      if (ty == FGPositioned::NDB) {
-        drawNDB(false, (FGNavRecord*) navs[i].get());
-      } else if (ty == FGPositioned::VOR) {
-        drawVOR(false, (FGNavRecord*) navs[i].get());
-      } else if (ty == FGPositioned::FIX) {
-        drawFix((FGFix*) navs[i].get());
-      }
-    } // of navaid iteration
-  } // of navaids || fixes are drawn test
+  for (unsigned int i=0; i<_itemsToDraw.size(); ++i) {
+      FGPositionedRef p = _itemsToDraw[i];
+      switch (p->type()) {
+          case FGPositioned::AIRPORT:
+              drawAirport((FGAirport*) p.get());
+              break;
+          case FGPositioned::NDB:
+              drawNDB(false, (FGNavRecord*) p.get());
+              break;
+          case FGPositioned::VOR:
+              drawVOR(false, (FGNavRecord*) p.get());
+              break;
+          case FGPositioned::FIX:
+              drawFix((FGFix*) p.get());
+              break;
+         case FGPositioned::TOWN:
+          case FGPositioned::CITY:
+          case FGPositioned::COUNTRY:
+              drawPOI(p);
+              break;
+              
+          default:
+              SG_LOG(SG_GENERAL, SG_WARN, "unhandled type in MapWidget::drawPositioned");
+      } // of positioned type switch
+  } // of items to draw iteration
 }
 
 void MapWidget::drawNDB(bool tuned, FGNavRecord* ndb)
@@ -1014,7 +1133,11 @@ void MapWidget::drawVOR(bool tuned, FGNavRecord* vor)
     glColor3f(0.0, 0.0, 1.0);
   }
 
-  circleAt(pos, 6, 8);
+  circleAt(pos, 6, 9);
+  circleAt(pos, 8, 1);
+
+  if (vor->hasDME())
+  squareAt(pos, 9);
 
   if (validDataForKey(vor)) {
     setAnchorForKey(vor, pos);
@@ -1070,7 +1193,9 @@ void MapWidget::drawNavRadio(SGPropertyNode_ptr radio)
   // identify the tuned station - unfortunately we don't get lat/lon directly,
   // need to do the frequency search again
   double mhz = radio->getDoubleValue("frequencies/selected-mhz", 0.0);
-  FGNavRecord* nav = globals->get_navlist()->findByFreq(mhz, _aircraft);
+
+  FGNavRecord* nav = FGNavList::findByFreq(mhz, _aircraft,
+                                           FGNavList::navFilter());
   if (!nav || (nav->ident() != radio->getStringValue("nav-id"))) {
     // mismatch between navradio selection logic and ours!
     return;
@@ -1112,7 +1237,7 @@ void MapWidget::drawNavRadio(SGPropertyNode_ptr radio)
 void MapWidget::drawTunedLocalizer(SGPropertyNode_ptr radio)
 {
   double mhz = radio->getDoubleValue("frequencies/selected-mhz", 0.0);
-  FGNavRecord* loc = globals->get_loclist()->findByFreq(mhz, _aircraft);
+  FGNavRecord* loc = FGNavList::findByFreq(mhz, _aircraft, FGNavList::locFilter());
   if (!loc || (loc->ident() != radio->getStringValue("nav-id"))) {
     // mismatch between navradio selection logic and ours!
     return;
@@ -1123,6 +1248,40 @@ void MapWidget::drawTunedLocalizer(SGPropertyNode_ptr radio)
   }
 }
 
+void MapWidget::drawPOI(FGPositioned* poi)
+{
+  SGVec2d pos = project(poi->geod());
+  glColor3f(1.0, 1.0, 0.0);
+  glLineWidth(1.0);
+
+    int radius = 10;
+    if (poi->type() == FGPositioned::CITY) {
+        radius = 8;
+        glColor3f(0.0, 1.0, 0.0);
+    } else if (poi->type() == FGPositioned::TOWN) {
+        radius =  5;
+        glColor3f(0.2, 1.0, 0.0);
+    }
+    
+  circleAt(pos, 4, radius);
+
+  if (validDataForKey(poi)) {
+    setAnchorForKey(poi, pos);
+    return;
+  }
+
+  char buffer[1024];
+        ::snprintf(buffer, 1024, "%s",
+                poi->name().c_str());
+
+  MapData* d = createDataForKey(poi);
+  d->setPriority(200);
+  d->setLabel(poi->ident());
+  d->setText(buffer);
+  d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 10);
+  d->setAnchor(pos);
+}
+
 /*
 void MapWidget::drawObstacle(FGPositioned* obs)
 {
@@ -1168,23 +1327,32 @@ void MapWidget::drawAirport(FGAirport* apt)
     return;
   }
 
-  for (unsigned int r=0; r<apt->numRunways(); ++r) {
-    FGRunway* rwy = apt->getRunwayByIndex(r);
-               if (!rwy->isReciprocal()) {
-                       drawRunwayPre(rwy);
-               }
+  FGRunwayList runways(apt->getRunwaysWithoutReciprocals());
+    
+  for (unsigned int r=0; r<runways.size(); ++r) {
+    drawRunwayPre(runways[r]);
   }
 
-       for (unsigned int r=0; r<apt->numRunways(); ++r) {
-               FGRunway* rwy = apt->getRunwayByIndex(r);
-               if (!rwy->isReciprocal()) {
-                       drawRunway(rwy);
-               }
+  for (unsigned int r=0; r<runways.size(); ++r) {
+    FGRunway* rwy = runways[r];
+    drawRunway(rwy);
 
-               if (rwy->ILS()) {
-                       drawILS(false, rwy);
-               }
-       } // of runway iteration
+    if (rwy->ILS()) {
+        drawILS(false, rwy);
+    }
+    
+    if (rwy->reciprocalRunway()) {
+      FGRunway* recip = rwy->reciprocalRunway();
+      if (recip->ILS()) {
+        drawILS(false, recip);
+      }
+    }
+  }
+
+  for (unsigned int r=0; r<apt->numHelipads(); ++r) {
+      FGHelipad* hp = apt->getHelipadByIndex(r);
+      drawHelipad(hp);
+  }  // of runway iteration
 
 }
 
@@ -1193,14 +1361,11 @@ int MapWidget::scoreAirportRunways(FGAirport* apt)
   bool needHardSurface = _root->getBoolValue("hard-surfaced-airports", true);
   double minLength = _root->getDoubleValue("min-runway-length-ft", 2000.0);
 
-  int score = 0;
-  unsigned int numRunways(apt->numRunways());
-  for (unsigned int r=0; r<numRunways; ++r) {
-    FGRunway* rwy = apt->getRunwayByIndex(r);
-    if (rwy->isReciprocal()) {
-      continue;
-    }
+  FGRunwayList runways(apt->getRunwaysWithoutReciprocals());
 
+  int score = 0;
+  for (unsigned int r=0; r<runways.size(); ++r) {
+    FGRunway* rwy = runways[r];
     if (needHardSurface && !rwy->isHardSurface()) {
       continue;
     }
@@ -1328,131 +1493,152 @@ void MapWidget::drawILS(bool tuned, FGRunway* rwy)
 
 void MapWidget::drawTraffic()
 {
-  if (!_root->getBoolValue("draw-traffic")) {
-    return;
-  }
-
-  if (_cachedZoom > SHOW_DETAIL_ZOOM) {
-    return;
-  }
-
-  const SGPropertyNode* ai = fgGetNode("/ai/models", true);
-
-  for (int i = 0; i < ai->nChildren(); ++i) {
-    const SGPropertyNode *model = ai->getChild(i);
-    // skip bad or dead entries
-    if (!model || model->getIntValue("id", -1) == -1) {
-      continue;
-    }
-
-    const std::string& name(model->getName());
-    SGGeod pos = SGGeod::fromDegFt(
-      model->getDoubleValue("position/longitude-deg"),
-      model->getDoubleValue("position/latitude-deg"),
-      model->getDoubleValue("position/altitude-ft"));
-
-    double dist = SGGeodesy::distanceNm(_projectionCenter, pos);
-    if (dist > _drawRangeNm) {
-      continue;
+    AIDrawVec::const_iterator it;
+    for (it = _aiDrawVec.begin(); it != _aiDrawVec.end(); ++it) {
+        drawAI(*it);
     }
-
-    double heading = model->getDoubleValue("orientation/true-heading-deg");
-    if ((name == "aircraft") || (name == "multiplayer") ||
-        (name == "wingman") || (name == "tanker")) {
-      drawAIAircraft(model, pos, heading);
-    } else if ((name == "ship") || (name == "carrier") || (name == "escort")) {
-      drawAIShip(model, pos, heading);
-    }
-  } // of ai/models iteration
 }
 
-void MapWidget::drawAIAircraft(const SGPropertyNode* model, const SGGeod& pos, double hdg)
+void MapWidget::drawHelipad(FGHelipad* hp)
 {
+  SGVec2d pos = project(hp->geod());
+  glLineWidth(1.0);
+  glColor3f(1.0, 0.0, 1.0);
+  circleAt(pos, 16, 5.0);
 
-  SGVec2d p = project(pos);
-
-  glColor3f(0.0, 0.0, 0.0);
-  glLineWidth(2.0);
-  circleAt(p, 4, 6.0); // black diamond
-
-// draw heading vector
-  int speedKts = static_cast<int>(model->getDoubleValue("velocities/true-airspeed-kt"));
-  if (speedKts > 1) {
-    glLineWidth(1.0);
-
-    const double dt = 15.0 / (3600.0); // 15 seconds look-ahead
-    double distanceM = speedKts * SG_NM_TO_METER * dt;
-
-    SGGeod advance;
-    double az2;
-    SGGeodesy::direct(pos, hdg, distanceM, advance, az2);
-
-    drawLine(p, project(advance));
+  if (validDataForKey(hp)) {
+    setAnchorForKey(hp, pos);
+    return;
   }
 
-
-  // draw callsign / altitude / speed
   char buffer[1024];
-       ::snprintf(buffer, 1024, "%s\n%d'\n%dkts",
-               model->getStringValue("callsign", "<>"),
-               static_cast<int>(pos.getElevationFt() / 50.0) * 50,
-    speedKts);
+  ::snprintf(buffer, 1024, "%s\n%03d\n%.0f'",
+             hp->ident().c_str(),
+             displayHeading(hp->headingDeg()),
+             hp->lengthFt());
 
-  MapData* d = getOrCreateDataForKey((void*) model);
+  MapData* d = createDataForKey(hp);
   d->setText(buffer);
-  d->setLabel(model->getStringValue("callsign", "<>"));
-  d->setPriority(speedKts > 5 ? 60 : 10); // low priority for parked aircraft
-  d->setOffset(MapData::VALIGN_CENTER | MapData::HALIGN_LEFT, 10);
-  d->setAnchor(p);
-
+  d->setLabel(hp->ident());
+  d->setPriority(40);
+  d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 8);
+  d->setAnchor(pos);
 }
 
-void MapWidget::drawAIShip(const SGPropertyNode* model, const SGGeod& pos, double hdg)
+void MapWidget::drawAI(const DrawAIObject& dai)
 {
-  SGVec2d p = project(pos);
+  SGVec2d p = project(dai.pos);
+
+    if (dai.boat) {
+        glColor3f(0.0, 0.0, 0.5);
 
-  glColor3f(0.0, 0.0, 0.5);
+    } else {
+        glColor3f(0.0, 0.0, 0.0);
+    }
   glLineWidth(2.0);
-  circleAt(p, 4, 6.0); // blue diamond (to differentiate from aircraft.
+  circleAt(p, 4, 6.0); // black diamond
 
 // draw heading vector
-  int speedKts = static_cast<int>(model->getDoubleValue("velocities/speed-kts"));
-  if (speedKts > 1) {
+  if (dai.speedKts > 1) {
     glLineWidth(1.0);
-
     const double dt = 15.0 / (3600.0); // 15 seconds look-ahead
-    double distanceM = speedKts * SG_NM_TO_METER * dt;
-
-    SGGeod advance;
-    double az2;
-    SGGeodesy::direct(pos, hdg, distanceM, advance, az2);
-
+    double distanceM = dai.speedKts * SG_NM_TO_METER * dt;
+    SGGeod advance = SGGeodesy::direct(dai.pos, dai.heading, distanceM);
     drawLine(p, project(advance));
   }
-
-  // draw callsign / speed
-  char buffer[1024];
-       ::snprintf(buffer, 1024, "%s\n%dkts",
-               model->getStringValue("name", "<>"),
-    speedKts);
-
-  MapData* d = getOrCreateDataForKey((void*) model);
-  d->setText(buffer);
-  d->setLabel(model->getStringValue("name", "<>"));
-  d->setPriority(speedKts > 2 ? 30 : 10); // low priority for slow moving ships
-  d->setOffset(MapData::VALIGN_CENTER | MapData::HALIGN_LEFT, 10);
-  d->setAnchor(p);
+   
+    MapData* d = getOrCreateDataForKey((void*) dai.model);
+    d->setText(dai.legend);
+    d->setLabel(dai.label);
+    d->setPriority(dai.speedKts > 5 ? 60 : 10); // low priority for parked aircraft
+    d->setOffset(MapData::VALIGN_CENTER | MapData::HALIGN_LEFT, 10);
+    d->setAnchor(p);
 }
 
 SGVec2d MapWidget::project(const SGGeod& geod) const
 {
-  // Sanson-Flamsteed projection, relative to the projection center
+  SGVec2d p;
   double r = earth_radius_lat(geod.getLatitudeRad());
-  double lonDiff = geod.getLongitudeRad() - _projectionCenter.getLongitudeRad(),
-    latDiff = geod.getLatitudeRad() - _projectionCenter.getLatitudeRad();
-
-  SGVec2d p = SGVec2d(cos(geod.getLatitudeRad()) * lonDiff, latDiff) * r * currentScale();
-
+  
+    switch (_projection) {
+    case PROJECTION_SAMSON_FLAMSTEED:
+    {
+        // Sanson-Flamsteed projection, relative to the projection center
+        double lonDiff = geod.getLongitudeRad() - _projectionCenter.getLongitudeRad(),
+        latDiff = geod.getLatitudeRad() - _projectionCenter.getLatitudeRad();
+        
+        p = SGVec2d(cos(geod.getLatitudeRad()) * lonDiff, latDiff) * r * currentScale();
+        break;
+    }
+       
+    case PROJECTION_AZIMUTHAL_EQUIDISTANT:
+    {
+        // Azimuthal Equidistant projection, relative to the projection center
+      // http://www.globmaritime.com/martech/marine-navigation/general-concepts/626-azimuthal-equidistant-projection
+        double ref_lat = _projectionCenter.getLatitudeRad(),
+               ref_lon = _projectionCenter.getLongitudeRad(),
+               lat = geod.getLatitudeRad(),
+               lon = geod.getLongitudeRad(),
+               lonDiff = lon - ref_lon;
+      
+        double c = acos( sin(ref_lat) * sin(lat) + cos(ref_lat) * cos(lat) * cos(lonDiff) );
+        if (c == 0.0){
+            // angular distance from center is 0
+            p= SGVec2d(0.0, 0.0);
+            break;
+        }
+      
+        double k = c / sin(c);
+        double x, y;
+        if (ref_lat == (90 * SG_DEGREES_TO_RADIANS))
+        {
+            x = (SGD_PI / 2 - lat) * sin(lonDiff);
+            y = -(SGD_PI / 2 - lat) * cos(lonDiff);
+        }
+        else if (ref_lat == -(90 * SG_DEGREES_TO_RADIANS))
+        {
+            x = (SGD_PI / 2 + lat) * sin(lonDiff);
+            y = (SGD_PI / 2 + lat) * cos(lonDiff);
+        }
+        else
+        {
+            x = k * cos(lat) * sin(lonDiff);
+            y = k * ( cos(ref_lat) * sin(lat) - sin(ref_lat) * cos(lat) * cos(lonDiff) );
+        }
+        p = SGVec2d(x, y) * r * currentScale();
+      
+        break;
+    }
+    
+    case PROJECTION_ORTHO_AZIMUTH:
+    {
+        // http://mathworld.wolfram.com/OrthographicProjection.html
+        double cosTheta = cos(geod.getLatitudeRad());
+        double sinDLambda = sin(geod.getLongitudeRad() - _projectionCenter.getLongitudeRad());
+        double cosDLambda = cos(geod.getLongitudeRad() - _projectionCenter.getLongitudeRad());
+        double sinTheta1 = sin(_projectionCenter.getLatitudeRad());
+        double sinTheta = sin(geod.getLatitudeRad());
+        double cosTheta1 = cos(_projectionCenter.getLatitudeRad());
+        
+        p = SGVec2d(cosTheta * sinDLambda,
+                    (cosTheta1 * sinTheta) - (sinTheta1 * cosTheta * cosDLambda)) * r * currentScale();
+        break;
+    }
+            
+    case PROJECTION_SPHERICAL:
+    {
+        SGVec3d cartCenter = SGVec3d::fromGeod(_projectionCenter);
+        SGVec3d cartPt = SGVec3d::fromGeod(geod) - cartCenter;
+        
+        // rotate relative to projection center
+        SGQuatd orient = SGQuatd::fromLonLat(_projectionCenter);
+        cartPt = orient.rotateBack(cartPt);
+        return SGVec2d(cartPt.y(), cartPt.x()) * currentScale();
+        break;
+    }
+    } // of projection mode switch
+    
+  
 // rotate as necessary
   double cost = cos(_upHeading * SG_DEGREES_TO_RADIANS),
     sint = sin(_upHeading * SG_DEGREES_TO_RADIANS);
@@ -1469,13 +1655,83 @@ SGGeod MapWidget::unproject(const SGVec2d& p) const
   SGVec2d ur(cost * p.x() - sint * p.y(),
              sint * p.x() + cost * p.y());
 
-  double r = earth_radius_lat(_projectionCenter.getLatitudeRad());
-  SGVec2d unscaled = ur * (1.0 / (currentScale() * r));
+  
 
-  double lat = unscaled.y() + _projectionCenter.getLatitudeRad();
-  double lon = (unscaled.x() / cos(lat)) + _projectionCenter.getLongitudeRad();
+    switch (_projection) {
+    case PROJECTION_SAMSON_FLAMSTEED:
+    {
+        double r = earth_radius_lat(_projectionCenter.getLatitudeRad());
+        SGVec2d unscaled = ur * (1.0 / (currentScale() * r));
+        double lat = unscaled.y() + _projectionCenter.getLatitudeRad();
+        double lon = (unscaled.x() / cos(lat)) + _projectionCenter.getLongitudeRad();
+        return SGGeod::fromRad(lon, lat);
+    }
+       
+    case PROJECTION_AZIMUTHAL_EQUIDISTANT:
+    {
+        double r = earth_radius_lat(_projectionCenter.getLatitudeRad());
+        SGVec2d unscaled = ur * (1.0 / currentScale());
+        double lat = 0,
+               lon = 0,
+               ref_lat = _projectionCenter.getLatitudeRad(),
+               ref_lon = _projectionCenter.getLongitudeRad(),
+               rho = sqrt(unscaled.x() * unscaled.x() + unscaled.y() * unscaled.y()),
+               c = rho/r;
+        
+        if (rho == 0)
+        {
+            lat = ref_lat;
+            lon = ref_lon;
+        } 
+        else
+        {
+            lat = asin( cos(c) * sin(ref_lat) + (unscaled.y()  * sin(c) * cos(ref_lat)) / rho);
+
+            if (ref_lat == (90 * SG_DEGREES_TO_RADIANS))
+            {
+                lon = ref_lon + atan(-unscaled.x()/unscaled.y());
+            }
+            else if (ref_lat == -(90 * SG_DEGREES_TO_RADIANS))
+            {
+                lon = ref_lon + atan(unscaled.x()/unscaled.y());
+            }
+            else
+            {
+                lon = ref_lon + atan(unscaled.x() * sin(c) / (rho * cos(ref_lat) * cos(c) - unscaled.y() * sin(ref_lat) * sin(c)));
+            }
+         }
+
+        return SGGeod::fromRad(lon, lat);
+    }
+        
+    case PROJECTION_ORTHO_AZIMUTH:
+    {
+        double r = earth_radius_lat(_projectionCenter.getLatitudeRad());
+        SGVec2d unscaled = ur * (1.0 / (currentScale() * r));
+        
+        double phi = length(p);
+        double c = asin(phi);
+        double sinTheta1 = sin(_projectionCenter.getLatitudeRad());
+        double cosTheta1 = cos(_projectionCenter.getLatitudeRad());
+        
+        double lat = asin(cos(c) * sinTheta1 + ((unscaled.y() * sin(c) * cosTheta1) / phi));
+        double lon = _projectionCenter.getLongitudeRad() +
+        atan((unscaled.x()* sin(c)) / (phi  * cosTheta1 * cos(c) - unscaled.y() * sinTheta1 * sin(c)));
+        return SGGeod::fromRad(lon, lat);
+    }
+        
+    case PROJECTION_SPHERICAL:
+    {
+        SGVec2d unscaled = ur * (1.0 / currentScale());
+        SGQuatd orient = SGQuatd::fromLonLat(_projectionCenter);
+        SGVec3d cartCenter = SGVec3d::fromGeod(_projectionCenter);
+        SGVec3d cartPt = orient.rotate(SGVec3d(unscaled.x(), unscaled.y(), 0.0));
+        return SGGeod::fromCart(cartPt + cartCenter);
+    }
 
-  return SGGeod::fromRad(lon, lat);
+    default:
+      throw sg_exception("MapWidget::unproject(): requested unknown projection");
+    } // of projection mode switch
 }
 
 double MapWidget::currentScale() const
@@ -1487,15 +1743,25 @@ void MapWidget::circleAt(const SGVec2d& center, int nSides, double r)
 {
   glBegin(GL_LINE_LOOP);
   double advance = (SGD_PI * 2) / nSides;
-  glVertex2d(center.x(), center.y() + r);
+  glVertex2d(center.x() +r, center.y());
   double t=advance;
   for (int i=1; i<nSides; ++i) {
-    glVertex2d(center.x() + (sin(t) * r), center.y() + (cos(t) * r));
+    glVertex2d(center.x() + (cos(t) * r), center.y() + (sin(t) * r));
     t += advance;
   }
   glEnd();
 }
 
+void MapWidget::squareAt(const SGVec2d& center, double r)
+{
+  glBegin(GL_LINE_LOOP);
+  glVertex2d(center.x() + r, center.y() + r);
+  glVertex2d(center.x() + r, center.y() - r);
+  glVertex2d(center.x() - r, center.y() - r);
+  glVertex2d(center.x() - r, center.y() + r);
+  glEnd();
+}
+
 void MapWidget::circleAtAlt(const SGVec2d& center, int nSides, double r, double r2)
 {
   glBegin(GL_LINE_LOOP);
@@ -1693,3 +1959,63 @@ int MapWidget::displayHeading(double h) const
   SG_NORMALIZE_RANGE(h, 0.0, 360.0);
   return SGMiscd::roundToInt(h);
 }
+
+MapWidget::DrawAIObject::DrawAIObject(SGPropertyNode* m, const SGGeod& g) :
+    model(m),
+    boat(false),
+    pos(g),
+    speedKts(0)
+{
+    std::string name(model->getNameString());
+    heading = model->getDoubleValue("orientation/true-heading-deg");
+    
+    if ((name == "aircraft") || (name == "multiplayer") ||
+        (name == "wingman") || (name == "tanker"))
+    {
+        speedKts = static_cast<int>(model->getDoubleValue("velocities/true-airspeed-kt"));
+        label = model->getStringValue("callsign", "<>");
+    
+        // try to access the flight-plan of the aircraft. There are several layers
+        // of potential NULL-ness here, so we have to be defensive at each stage.
+        std::string originICAO, destinationICAO;
+        FGAIManager* aiManager = globals->get_subsystem<FGAIManager>();
+        FGAIBasePtr aircraft = aiManager ? aiManager->getObjectFromProperty(model) : NULL;
+        if (aircraft) {
+            FGAIAircraft* p = static_cast<FGAIAircraft*>(aircraft.get());
+            if (p->GetFlightPlan()) {
+                if (p->GetFlightPlan()->departureAirport()) {
+                    originICAO = p->GetFlightPlan()->departureAirport()->ident();
+                }
+                
+                if (p->GetFlightPlan()->arrivalAirport()) {
+                    destinationICAO = p->GetFlightPlan()->arrivalAirport()->ident();
+                }
+            } // of flight-plan exists
+        } // of check for AIBase-derived instance
+        
+        // draw callsign / altitude / speed
+        int altFt50 = static_cast<int>(pos.getElevationFt() / 50.0) * 50;
+        std::ostringstream ss;
+        ss << model->getStringValue("callsign", "<>");
+        if (speedKts > 1) {
+            ss << "\n" << altFt50 << "' " << speedKts << "kts";
+        }
+        
+        if (!originICAO.empty() || ! destinationICAO.empty()) {
+            ss << "\n" << originICAO << " -> " << destinationICAO;
+        }
+
+        legend = ss.str();
+    } else if ((name == "ship") || (name == "carrier") || (name == "escort")) {
+        boat = true;
+        speedKts = static_cast<int>(model->getDoubleValue("velocities/speed-kts"));
+        label = model->getStringValue("name", "<>");
+        
+        char buffer[1024];
+        ::snprintf(buffer, 1024, "%s\n%dkts",
+                   model->getStringValue("name", "<>"),
+                   speedKts);
+        legend = buffer;
+    }
+}
+