#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";
return (x0 <= x1) && (y0 <= y1);
}
-
+
class MapData;
typedef std::vector<MapData*> MapDataVec;
_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();
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);
}
}
///////////////////////////////////////////////////////////////////////////
+// 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) :
_width = maxX - x;
_height = maxY - y;
_hasPanned = false;
- _orthoAzimuthProject = false;
+ _projection = PROJECTION_AZIMUTHAL_EQUIDISTANT;
+ _magneticHeadings = false;
MapData::setFont(legendFont);
MapData::setPalette(colour);
_root->setIntValue("zoom", zoom() - 1);
}
-void MapWidget::draw(int dx, int dy)
+void MapWidget::update()
{
- _aircraft = globals->get_aircraft_position();
+ _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();
+}
- _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;
+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
+
+ _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);
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
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));
d->setAnchor(clickPos);
d->setOffset(MapData::VALIGN_TOP | MapData::HALIGN_CENTER, 15);
d->setPriority(20000);
-
-
}
void MapWidget::paintAircraftLocation(const SGGeod& aircraftPos)
void MapWidget::drawFlightHistory()
{
- FGFlightHistory* history = (FGFlightHistory*) globals->get_subsystem("history");
- if (!history || !_root->getBoolValue("draw-flight-history")) {
+ if (_flightHistoryPath.empty())
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]);
+ for (unsigned int i=0; i<_flightHistoryPath.size(); ++i) {
+ SGVec2d p = project(_flightHistoryPath[i]);
glVertex2d(p.x(), p.y());
}
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();
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) {
didDraw |= drawLineClipped(gridPoint(ix, y), gridPoint(ix - 1, y));
}
- if (ix > 30) {
+ if (ix > (90/_gridSpacing)-1) {
break;
}
} while (didDraw);
}
}
-class MapAirportFilter : public FGAirport::AirportFilter
+void MapWidget::drawPositioned()
{
-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;
- }
-
-private:
- bool _heliports;
- bool _hardRunwaysOnly;
- double _minLengthFt;
-};
-
-void MapWidget::drawAirports()
-{
- MapAirportFilter af(_root);
- bool partial = false;
- FGPositioned::List apts = FGPositioned::findWithinRangePartial(_projectionCenter, _drawRangeNm, &af, partial);
- 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::NDB;
- }
-
- virtual FGPositioned::Type maxType() const {
- return _navaids ? FGPositioned::VOR : FGPositioned::FIX;
- }
-
-private:
- bool _fixes, _navaids;
-};
-
-void MapWidget::drawNavaids()
-{
- 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)
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);
}
}
+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)
{
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);
+ 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) {
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;
}
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;
- }
-
- 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);
+ AIDrawVec::const_iterator it;
+ for (it = _aiDrawVec.begin(); it != _aiDrawVec.end(); ++it) {
+ drawAI(*it);
}
- } // of ai/models iteration
}
void MapWidget::drawHelipad(FGHelipad* hp)
{
SGVec2d pos = project(hp->geod());
glLineWidth(1.0);
- glColor3f(1.0, 1.0, 1.0);
+ glColor3f(1.0, 0.0, 1.0);
circleAt(pos, 16, 5.0);
if (validDataForKey(hp)) {
d->setAnchor(pos);
}
-void MapWidget::drawAIAircraft(const SGPropertyNode* model, const SGGeod& pos, double hdg)
+void MapWidget::drawAI(const DrawAIObject& dai)
{
+ SGVec2d p = project(dai.pos);
- SGVec2d p = project(pos);
+ if (dai.boat) {
+ glColor3f(0.0, 0.0, 0.5);
- glColor3f(0.0, 0.0, 0.0);
+ } else {
+ 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));
- }
-
-
- // 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);
-
-}
-
-void MapWidget::drawAIShip(const SGPropertyNode* model, const SGGeod& pos, double hdg)
-{
- SGVec2d p = project(pos);
-
- glColor3f(0.0, 0.0, 0.5);
- glLineWidth(2.0);
- circleAt(p, 4, 6.0); // blue diamond (to differentiate from aircraft.
-
-// 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
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());
+ 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;
+ }
- p = SGVec2d(cosTheta * sinDLambda,
- (cosTheta1 * sinTheta) - (sinTheta1 * cosTheta * cosDLambda)) * r * currentScale();
+ 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),
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_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);
- } 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);
+ }
+
+ default:
+ throw sg_exception("MapWidget::unproject(): requested unknown projection");
+ } // of projection mode switch
}
double MapWidget::currentScale() const
{
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);
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;
+ }
+}
+