]> git.mxchange.org Git - flightgear.git/blobdiff - src/GUI/MapWidget.cxx
Support for multiple data dirs.
[flightgear.git] / src / GUI / MapWidget.cxx
index a0c6d2b4cbdc0db0e83491fa98e382a59e81adf8..7edb0c93e5dc72c4fdf9794270ed6f3c35c4021a 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/AIFlightPlan.hxx>
 
 const char* RULER_LEGEND_KEY = "ruler-legend";
 
@@ -376,6 +377,7 @@ int MapData::_fontDescender = 0;
 
 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 +389,8 @@ MapWidget::MapWidget(int x, int y, int maxX, int maxY) :
   _width = maxX - x;
   _height = maxY - y;
   _hasPanned = false;
-  _orthoAzimuthProject = false;
+  _projection = PROJECTION_SAMSON_FLAMSTEED;
+  _magneticHeadings = false;
   
   MapData::setFont(legendFont);
   MapData::setPalette(colour);
@@ -413,6 +416,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);
 }
 
@@ -537,8 +541,7 @@ void MapWidget::zoomOut()
 
 void MapWidget::draw(int dx, int dy)
 {
-  _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) {
@@ -608,11 +611,13 @@ void MapWidget::draw(int dx, int dy)
 
   drawAirports();
   drawNavaids();
+  drawPOIs();
   drawTraffic();
   drawGPSData();
   drawNavRadio(fgGetNode("/instrumentation/nav[0]", false));
   drawNavRadio(fgGetNode("/instrumentation/nav[1]", false));
   paintAircraftLocation(_aircraft);
+  drawFlightHistory();
   paintRoute();
   paintRuler();
 
@@ -682,7 +687,7 @@ void MapWidget::paintRoute()
     return;
   }
 
-  RoutePath path(_route->waypts());
+  RoutePath path(_route->flightPlan());
 
 // first pass, draw the actual lines
   glLineWidth(2.0);
@@ -750,6 +755,28 @@ void MapWidget::paintRoute()
   } // of second waypoint iteration
 }
 
+void MapWidget::drawFlightHistory()
+{
+  FGFlightHistory* history = (FGFlightHistory*) globals->get_subsystem("history");
+  if (!history || !_root->getBoolValue("draw-flight-history")) {
+    return;
+  }
+  
+  // first pass, draw the actual lines
+  glLineWidth(2.0);
+  
+  SGGeodVec gv(history->pathForHistory());
+  glColor4f(0.0, 0.0, 1.0, 0.7);
+
+  glBegin(GL_LINE_STRIP);
+  for (unsigned int i=0; i<gv.size(); ++i) {
+    SGVec2d p = project(gv[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
@@ -889,7 +916,7 @@ public:
   {
     _heliports = nd->getBoolValue("show-heliports", false);
     _hardRunwaysOnly = nd->getBoolValue("hard-surfaced-airports", true);
-    _minLengthFt = nd->getDoubleValue("min-runway-length-ft", 2000.0);
+    _minLengthFt = fgGetDouble("/sim/navdb/min-runway-length-ft", 2000);
   }
 
   virtual FGPositioned::Type maxType() const {
@@ -913,7 +940,8 @@ private:
 void MapWidget::drawAirports()
 {
   MapAirportFilter af(_root);
-  FGPositioned::List apts = FGPositioned::findWithinRange(_projectionCenter, _drawRangeNm, &af);
+  bool partial = false;
+  FGPositionedList apts = FGPositioned::findWithinRangePartial(_projectionCenter, _drawRangeNm, &af, partial);
   for (unsigned int i=0; i<apts.size(); ++i) {
     drawAirport((FGAirport*) apts[i].get());
   }
@@ -939,11 +967,11 @@ public:
   }
 
   virtual FGPositioned::Type minType() const {
-    return _fixes ? FGPositioned::FIX : FGPositioned::VOR;
+    return _fixes ? FGPositioned::FIX : FGPositioned::NDB;
   }
 
   virtual FGPositioned::Type maxType() const {
-    return _navaids ? FGPositioned::NDB : FGPositioned::FIX;
+    return _navaids ? FGPositioned::VOR : FGPositioned::FIX;
   }
 
 private:
@@ -956,7 +984,7 @@ void MapWidget::drawNavaids()
   NavaidFilter f(fixes, _root->getBoolValue("draw-navaids"));
 
   if (f.minType() <= f.maxType()) {
-    FGPositioned::List navs = FGPositioned::findWithinRange(_projectionCenter, _drawRangeNm, &f);
+    FGPositionedList navs = FGPositioned::findWithinRange(_projectionCenter, _drawRangeNm, &f);
 
     glLineWidth(1.0);
     for (unsigned int i=0; i<navs.size(); ++i) {
@@ -972,6 +1000,26 @@ void MapWidget::drawNavaids()
   } // of navaids || fixes are drawn test
 }
 
+void MapWidget::drawPOIs()
+{
+  FGPositioned::TypeFilter f(FGPositioned::COUNTRY);
+  f.addType(FGPositioned::CITY);
+  f.addType(FGPositioned::TOWN);
+  FGPositionedList poi = FGPositioned::findWithinRange(_projectionCenter, _drawRangeNm, &f);
+
+    glLineWidth(1.0);
+    for (unsigned int i=0; i<poi.size(); ++i) {
+      FGPositioned::Type ty = poi[i]->type();
+      if (ty == FGPositioned::COUNTRY) {
+        drawCountries((FGNavRecord*) poi[i].get());
+      } else if (ty == FGPositioned::CITY) {
+        drawCities((FGNavRecord*) poi[i].get());
+      } else if (ty == FGPositioned::TOWN) {
+        drawTowns((FGNavRecord*) poi[i].get());
+      }
+    } // of navaid iteration
+}
+
 void MapWidget::drawNDB(bool tuned, FGNavRecord* ndb)
 {
   SGVec2d pos = project(ndb->geod());
@@ -1015,7 +1063,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);
@@ -1071,7 +1123,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;
@@ -1113,7 +1167,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;
@@ -1124,6 +1178,93 @@ void MapWidget::drawTunedLocalizer(SGPropertyNode_ptr radio)
   }
 }
 
+void MapWidget::drawCountries(FGNavRecord* rec)
+{
+  if (_cachedZoom < 9) {
+    return; // hide labels beyond a certain zoom level
+  }
+
+  SGVec2d pos = project(rec->geod());
+  glColor3f(1.0, 1.0, 0.0);
+
+  circleAt(pos, 4, 10);
+
+  if (validDataForKey(rec)) {
+    setAnchorForKey(rec, pos);
+    return;
+  }
+
+  char buffer[1024];
+        ::snprintf(buffer, 1024, "%s",
+                rec->name().c_str());
+
+  MapData* d = createDataForKey(rec);
+  d->setPriority(200);
+  d->setLabel(rec->ident());
+  d->setText(buffer);
+  d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 10);
+  d->setAnchor(pos);
+
+}
+
+void MapWidget::drawCities(FGNavRecord* rec)
+{
+  if (_cachedZoom > SHOW_DETAIL_ZOOM) {
+    return; // hide labels beyond a certain zoom level
+  }
+
+  SGVec2d pos = project(rec->geod());
+  glColor3f(0.0, 1.0, 0.0);
+
+  circleAt(pos, 4, 8);
+
+  if (validDataForKey(rec)) {
+    setAnchorForKey(rec, pos);
+    return;
+  }
+
+  char buffer[1024];
+        ::snprintf(buffer, 1024, "%s",
+                rec->name().c_str());
+
+  MapData* d = createDataForKey(rec);
+  d->setPriority(40);
+  d->setLabel(rec->ident());
+  d->setText(buffer);
+  d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 10);
+  d->setAnchor(pos);
+
+}
+
+void MapWidget::drawTowns(FGNavRecord* rec)
+{
+  if (_cachedZoom > SHOW_DETAIL2_ZOOM) {
+    return; // hide labels beyond a certain zoom level
+  }
+
+  SGVec2d pos = project(rec->geod());
+  glColor3f(0.2, 1.0, 0.0);
+
+  circleAt(pos, 4, 5);
+
+  if (validDataForKey(rec)) {
+    setAnchorForKey(rec, pos);
+    return;
+  }
+
+  char buffer[1024];
+        ::snprintf(buffer, 1024, "%s",
+                rec->name().c_str());
+
+  MapData* d = createDataForKey(rec);
+  d->setPriority(40);
+  d->setLabel(rec->ident());
+  d->setText(buffer);
+  d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 10);
+  d->setAnchor(pos);
+
+}
+
 /*
 void MapWidget::drawObstacle(FGPositioned* obs)
 {
@@ -1169,23 +1310,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
 
 }
 
@@ -1194,14 +1344,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;
     }
@@ -1367,6 +1514,32 @@ void MapWidget::drawTraffic()
   } // of ai/models iteration
 }
 
+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);
+
+  if (validDataForKey(hp)) {
+    setAnchorForKey(hp, pos);
+    return;
+  }
+
+  char buffer[1024];
+  ::snprintf(buffer, 1024, "%s\n%03d\n%.0f'",
+             hp->ident().c_str(),
+             displayHeading(hp->headingDeg()),
+             hp->lengthFt());
+
+  MapData* d = createDataForKey(hp);
+  d->setText(buffer);
+  d->setLabel(hp->ident());
+  d->setPriority(40);
+  d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 8);
+  d->setAnchor(pos);
+}
+
 void MapWidget::drawAIAircraft(const SGPropertyNode* model, const SGGeod& pos, double hdg)
 {
 
@@ -1390,22 +1563,38 @@ void MapWidget::drawAIAircraft(const SGPropertyNode* model, const SGGeod& pos, d
 
     drawLine(p, project(advance));
   }
-
+   
+    // 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 = static_cast<FGAIManager*>(globals->get_subsystem("ai-model"));
+    FGAIBasePtr aircraft = aiManager ? aiManager->getObjectFromProperty(model) : NULL;
+    if (aircraft) {
+        FGAIAircraft* p = static_cast<FGAIAircraft*>(aircraft.get());
+        if (p->GetFlightPlan()) {
+            originICAO = p->GetFlightPlan()->departureAirport()->ident();
+            destinationICAO = p->GetFlightPlan()->arrivalAirport()->ident();
+        }
+    }
 
   // 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);
-
-  MapData* d = getOrCreateDataForKey((void*) model);
-  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);
-
+    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;
+    }
+    
+    MapData* d = getOrCreateDataForKey((void*) model);
+    d->setText(ss.str().c_str());
+    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);
 }
 
 void MapWidget::drawAIShip(const SGPropertyNode* model, const SGGeod& pos, double hdg)
@@ -1450,26 +1639,45 @@ SGVec2d MapWidget::project(const SGGeod& geod) const
   SGVec2d p;
   double r = earth_radius_lat(geod.getLatitudeRad());
   
-  if (_orthoAzimuthProject) {
-    // 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();
+    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_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
     
-  } else {
-    // 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();
-      
-  }
   
 // rotate as necessary
   double cost = cos(_upHeading * SG_DEGREES_TO_RADIANS),
@@ -1487,24 +1695,43 @@ 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));
-
-  if (_orthoAzimuthProject) {
-      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() + 
+  
+
+    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_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);
-  } else {
-      double lat = unscaled.y() + _projectionCenter.getLatitudeRad();
-      double lon = (unscaled.x() / cos(lat)) + _projectionCenter.getLongitudeRad();
-      return SGGeod::fromRad(lon, lat);
-  }
+        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);
+    }
+    } // of projection mode switch
 }
 
 double MapWidget::currentScale() const
@@ -1516,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);