X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FGUI%2FMapWidget.cxx;h=bb44327fb30d49cdd6dd842652e0ecb1af87048a;hb=4befe0e6ea6b5f21119364f1175a0b6c8e97395c;hp=e3cc5de772d8aa4dab79d706316a7c52ec753579;hpb=9f13c49d292825813073dc2375995238800326a6;p=flightgear.git diff --git a/src/GUI/MapWidget.cxx b/src/GUI/MapWidget.cxx index e3cc5de77..bb44327fb 100644 --- a/src/GUI/MapWidget.cxx +++ b/src/GUI/MapWidget.cxx @@ -8,7 +8,6 @@ #include // for std::sort #include -#include #include #include #include @@ -22,10 +21,14 @@ #include #include #include -#include +#include #include #include
// fgGetKeyModifiers() #include +#include +#include +#include +#include const char* RULER_LEGEND_KEY = "ruler-legend"; @@ -67,7 +70,7 @@ static bool puBoxIntersect(const puBox& a, const puBox& b) return (x0 <= x1) && (y0 <= y1); } - + class MapData; typedef std::vector MapDataVec; @@ -197,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(); @@ -216,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); } } @@ -373,8 +381,79 @@ int MapData::_fontDescender = 0; /////////////////////////////////////////////////////////////////////////// -const int MAX_ZOOM = 16; +// 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) : @@ -383,11 +462,12 @@ MapWidget::MapWidget(int x, int y, int maxX, int maxY) : _route = static_cast(globals->get_subsystem("route-manager")); _gps = fgGetNode("/instrumentation/gps"); - _zoom = 6; _width = maxX - x; _height = maxY - y; _hasPanned = false; - + _projection = PROJECTION_AZIMUTHAL_EQUIDISTANT; + _magneticHeadings = false; + MapData::setFont(legendFont); MapData::setPalette(colour); @@ -397,13 +477,22 @@ MapWidget::MapWidget(int x, int y, int maxX, int maxY) : MapWidget::~MapWidget() { delete _magVar; + clearData(); } void MapWidget::setProperty(SGPropertyNode_ptr prop) { _root = prop; + int zoom = _root->getIntValue("zoom", -1); + if (zoom < 0) { + _root->setIntValue("zoom", 6); // default zoom + } + +// expose MAX_ZOOM to the UI + _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); } @@ -501,58 +590,151 @@ void MapWidget::pan(const SGVec2d& delta) _projectionCenter = unproject(-delta); } +int MapWidget::zoom() const +{ + int z = _root->getIntValue("zoom"); + SG_CLAMP_RANGE(z, 0, MAX_ZOOM); + return z; +} + void MapWidget::zoomIn() { - if (_zoom <= 0) { + if (zoom() >= MAX_ZOOM) { return; } - --_zoom; - SG_LOG(SG_GENERAL, SG_INFO, "zoom is now:" << _zoom); + _root->setIntValue("zoom", zoom() + 1); } void MapWidget::zoomOut() { - if (_zoom >= MAX_ZOOM) { + if (zoom() <= 0) { return; } - ++_zoom; - SG_LOG(SG_GENERAL, SG_INFO, "zoom is now:" << _zoom); + _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")); - _magneticHeadings = _root->getBoolValue("magnetic-headings"); + _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; + } + + 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; + + FGFlightHistory* history = (FGFlightHistory*) globals->get_subsystem("history"); + if (history && _root->getBoolValue("draw-flight-history")) { + _flightHistoryPath = history->pathForHistory(); + } else { + _flightHistoryPath.clear(); + } - if (_hasPanned) - { - _root->setBoolValue("centre-on-aircraft", false); - _hasPanned = false; - } - else - if (_root->getBoolValue("centre-on-aircraft")) { - _projectionCenter = _aircraft; - } +// 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()); + } - double julianDate = globals->get_time_params()->getJD(); - _magVar->update(_projectionCenter, julianDate); + 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(); +} - bool aircraftUp = _root->getBoolValue("aircraft-heading-up"); - if (aircraftUp) { - _upHeading = fgGetDouble("/orientation/heading-deg"); - } else { - _upHeading = 0.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 - 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); @@ -560,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 @@ -574,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(); @@ -620,22 +795,15 @@ void MapWidget::paintRuler() double dist, az, az2; SGGeodesy::inverse(_aircraft, _clickGeod, az, az2, dist); - if (_magneticHeadings) { - az -= _magVar->get_magvar(); - SG_NORMALIZE_RANGE(az, 0.0, 360.0); - } - char buffer[1024]; ::snprintf(buffer, 1024, "%03d/%.1fnm", - SGMiscd::roundToInt(az), dist * SG_METER_TO_NM); + displayHeading(az), dist * SG_METER_TO_NM); MapData* d = getOrCreateDataForKey((void*) RULER_LEGEND_KEY); d->setLabel(buffer); d->setAnchor(clickPos); d->setOffset(MapData::VALIGN_TOP | MapData::HALIGN_CENTER, 15); d->setPriority(20000); - - } void MapWidget::paintAircraftLocation(const SGGeod& aircraftPos) @@ -669,7 +837,7 @@ void MapWidget::paintRoute() return; } - RoutePath path(_route->waypts()); + RoutePath path(_route->flightPlan()); // first pass, draw the actual lines glLineWidth(2.0); @@ -737,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 @@ -788,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(); @@ -808,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) { @@ -818,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); @@ -869,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; itype() == FGPositioned::FIX)) { - // ignore fixes which end in digits - expirmental - if (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() -{ - 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; itype(); - 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) @@ -1002,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); @@ -1028,7 +1163,7 @@ void MapWidget::drawFix(FGFix* fix) glColor3f(0.0, 0.0, 0.0); circleAt(pos, 3, 6); - if (_zoom > SHOW_DETAIL_ZOOM) { + if (_cachedZoom > SHOW_DETAIL_ZOOM) { return; // hide fix labels beyond a certain zoom level } @@ -1058,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; @@ -1100,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; @@ -1111,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) { @@ -1126,7 +1297,7 @@ void MapWidget::drawAirport(FGAirport* apt) // draw tower location SGVec2d towerPos = project(apt->getTowerLocation()); - if (_zoom <= SHOW_DETAIL_ZOOM) { + if (_cachedZoom <= SHOW_DETAIL_ZOOM) { glColor3f(1.0, 1.0, 1.0); glLineWidth(1.0); @@ -1152,27 +1323,36 @@ void MapWidget::drawAirport(FGAirport* apt) d->setAnchor(towerPos); } - if (_zoom > SHOW_DETAIL_ZOOM) { + if (_cachedZoom > SHOW_DETAIL_ZOOM) { return; } - for (unsigned int r=0; rnumRunways(); ++r) { - FGRunway* rwy = apt->getRunwayByIndex(r); - if (!rwy->isReciprocal()) { - drawRunwayPre(rwy); - } + FGRunwayList runways(apt->getRunwaysWithoutReciprocals()); + + for (unsigned int r=0; rnumRunways(); ++r) { - FGRunway* rwy = apt->getRunwayByIndex(r); - if (!rwy->isReciprocal()) { - drawRunway(rwy); - } + for (unsigned int r=0; rILS()) { - 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; rnumHelipads(); ++r) { + FGHelipad* hp = apt->getHelipadByIndex(r); + drawHelipad(hp); + } // of runway iteration } @@ -1181,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; rgetRunwayByIndex(r); - if (rwy->isReciprocal()) { - continue; - } + FGRunwayList runways(apt->getRunwaysWithoutReciprocals()); + int score = 0; + for (unsigned int r=0; risHardSurface()) { continue; } @@ -1233,13 +1410,13 @@ void MapWidget::drawRunway(FGRunway* rwy) setAnchorForKey(rwy, (p1 + p2) * 0.5); return; } - + char buffer[1024]; - ::snprintf(buffer, 1024, "%s/%s\n%3.0f/%3.0f\n%.0f'", + ::snprintf(buffer, 1024, "%s/%s\n%03d/%03d\n%.0f'", rwy->ident().c_str(), rwy->reciprocalRunway()->ident().c_str(), - rwy->headingDeg(), - rwy->reciprocalRunway()->headingDeg(), + displayHeading(rwy->headingDeg()), + displayHeading(rwy->reciprocalRunway()->headingDeg()), rwy->lengthFt()); MapData* d = createDataForKey(rwy); @@ -1301,8 +1478,10 @@ void MapWidget::drawILS(bool tuned, FGRunway* rwy) } char buffer[1024]; - ::snprintf(buffer, 1024, "%s\n%s\n%3.2fMHz", - loc->name().c_str(), loc->ident().c_str(),loc->get_freq()/100.0); + ::snprintf(buffer, 1024, "%s\n%s\n%03d - %3.2fMHz", + loc->ident().c_str(), loc->name().c_str(), + displayHeading(radial), + loc->get_freq()/100.0); MapData* d = createDataForKey(loc); d->setPriority(40); @@ -1314,142 +1493,152 @@ void MapWidget::drawILS(bool tuned, FGRunway* rwy) void MapWidget::drawTraffic() { - if (!_root->getBoolValue("draw-traffic")) { - return; - } - - if (_zoom > 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::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(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((void*) model)) { - setAnchorForKey((void*) model, p); + 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(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 = createDataForKey((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(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)); } - - if (validDataForKey((void*) model)) { - setAnchorForKey((void*) model, p); - return; - } - - // draw callsign / speed - char buffer[1024]; - ::snprintf(buffer, 1024, "%s\n%dkts", - model->getStringValue("name", "<>"), - speedKts); - - MapData* d = createDataForKey((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); @@ -1466,33 +1655,113 @@ 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 { - return 1.0 / pow(2.0, _zoom); + return 1.0 / pow(2.0, _cachedZoom); } 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; iresetAge(); return d; } + +void MapWidget::clearData() +{ + KeyDataMap::iterator it = _mapData.begin(); + for (; it != _mapData.end(); ++it) { + delete it->second; + } + + _mapData.clear(); +} + +int MapWidget::displayHeading(double h) const +{ + if (_magneticHeadings) { + h -= _magVar->get_magvar() * SG_RADIANS_TO_DEGREES; + } + + 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(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(); + FGAIBasePtr aircraft = aiManager ? aiManager->getObjectFromProperty(model) : NULL; + if (aircraft) { + FGAIAircraft* p = static_cast(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(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(model->getDoubleValue("velocities/speed-kts")); + label = model->getStringValue("name", "<>"); + + char buffer[1024]; + ::snprintf(buffer, 1024, "%s\n%dkts", + model->getStringValue("name", "<>"), + speedKts); + legend = buffer; + } +} +