X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FGUI%2FMapWidget.cxx;h=bb44327fb30d49cdd6dd842652e0ecb1af87048a;hb=4befe0e6ea6b5f21119364f1175a0b6c8e97395c;hp=44b1f7601ad3729832db9aa20492be672bef6cec;hpb=78d9caceadfc09238b0ef00e40fede5a95d29437;p=flightgear.git diff --git a/src/GUI/MapWidget.cxx b/src/GUI/MapWidget.cxx index 44b1f7601..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,18 +21,23 @@ #include #include #include -#include +#include #include #include
// fgGetKeyModifiers() +#include +#include +#include +#include +#include const char* RULER_LEGEND_KEY = "ruler-legend"; - + /* equatorial and polar earth radius */ const float rec = 6378137; // earth radius, equator (?) const float rpol = 6356752.314f; // earth radius, polar (?) /************************************************************************ - some trigonometric helper functions + some trigonometric helper functions (translated more or less directly from Alexei Novikovs perl original) *************************************************************************/ @@ -63,10 +67,10 @@ static bool puBoxIntersect(const puBox& a, const puBox& b) int y0 = SG_MAX2(a.min[1], b.min[1]); int x1 = SG_MIN2(a.max[0], b.max[0]); int y1 = SG_MIN2(a.max[1], b.max[1]); - - return (x0 <= x1) && (y0 <= y1); -} + return (x0 <= x1) && (y0 <= y1); +} + class MapData; typedef std::vector MapDataVec; @@ -76,11 +80,11 @@ public: static const int HALIGN_LEFT = 1; static const int HALIGN_CENTER = 2; static const int HALIGN_RIGHT = 3; - + static const int VALIGN_TOP = 1 << 4; static const int VALIGN_CENTER = 2 << 4; static const int VALIGN_BOTTOM = 3 << 4; - + MapData(int priority) : _dirtyText(true), _age(0), @@ -92,151 +96,156 @@ public: _dataVisible(false) { } - + void setLabel(const std::string& label) { if (label == _label) { return; // common case, and saves invalidation } - + _label = label; _dirtyText = true; } - + void setText(const std::string &text) { if (_rawText == text) { return; // common case, and saves invalidation } - + _rawText = text; _dirtyText = true; } - + void setDataVisible(bool vis) { if (vis == _dataVisible) { return; } - + if (_rawText.empty()) { vis = false; } - + _dataVisible = vis; _dirtyText = true; } - + static void setFont(puFont f) { _font = f; _fontHeight = f.getStringHeight(); _fontDescender = f.getStringDescender(); } - + static void setPalette(puColor* pal) { _palette = pal; } - + void setPriority(int pri) { _priority = pri; } - + int priority() const { return _priority; } - + void setAnchor(const SGVec2d& anchor) { _anchor = anchor; } - + void setOffset(int direction, int px) { if ((_offsetPx == px) && (_offsetDir == direction)) { return; } - + _dirtyOffset = true; _offsetDir = direction; _offsetPx = px; } - + bool isClipped(const puBox& vis) const { validate(); if ((_width < 1) || (_height < 1)) { return true; } - + return !puBoxIntersect(vis, box()); } - + bool overlaps(const MapDataVec& l) const { validate(); puBox b(box()); - + MapDataVec::const_iterator it; for (it = l.begin(); it != l.end(); ++it) { if (puBoxIntersect(b, (*it)->box())) { return true; } } // of list iteration - + return false; } - + puBox box() const { validate(); return makePuBox( - _anchor.x() + _offset.x(), + _anchor.x() + _offset.x(), _anchor.y() + _offset.y(), _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(); - + int xx = _anchor.x() + _offset.x(); int yy = _anchor.y() + _offset.y(); - + if (_dataVisible) { puBox box(makePuBox(0,0,_width, _height)); int border = 1; box.draw(xx, yy, PUSTYLE_DROPSHADOW, _palette, FALSE, border); - + // draw lines int lineHeight = _fontHeight; int xPos = xx + MARGIN; int yPos = yy + _height - (lineHeight + MARGIN); 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 { + } else { glColor3f(0.8, 0.8, 0.8); - _font.drawString(_label.c_str(), xx, yy + _fontDescender); + drawStringUtf8(_label, xx, yy + _fontDescender, _font); } } - + void age() { ++_age; } - + void resetAge() { _age = 0; } - + bool isExpired() const { return (_age > 100); } - + static bool order(MapData* a, MapData* b) { return a->_priority > b->_priority; @@ -248,92 +257,92 @@ private: if (_dirtyOffset) { computeOffset(); } - + return; } - + if (_dataVisible) { measureData(); } else { measureLabel(); } - + computeOffset(); _dirtyText = false; } - + void measureData() const { _lines = simgear::strutils::split(_rawText, "\n"); // measure text to find width and height _width = -1; _height = 0; - + for (unsigned int ln=0; ln<_lines.size(); ++ln) { _height += _fontHeight; if (ln > 0) { _height += LINE_LEADING; } - + int lw = _font.getStringWidth(_lines[ln].c_str()); _width = std::max(_width, lw); } // of line measurement - + if ((_width < 1) || (_height < 1)) { // will be clipped return; } - + _height += MARGIN * 2; _width += MARGIN * 2; } - + void measureLabel() const { if (_label.empty()) { _width = _height = -1; return; } - + _height = _fontHeight; _width = _font.getStringWidth(_label.c_str()); } - + void computeOffset() const { _dirtyOffset = false; if ((_width <= 0) || (_height <= 0)) { return; } - + int hOffset = 0; int vOffset = 0; - + switch (_offsetDir & 0x0f) { default: case HALIGN_LEFT: hOffset = _offsetPx; break; - + case HALIGN_CENTER: hOffset = -(_width>>1); break; - + case HALIGN_RIGHT: hOffset = -(_offsetPx + _width); break; } - + switch (_offsetDir & 0xf0) { default: case VALIGN_TOP: vOffset = -(_offsetPx + _height); break; - + case VALIGN_CENTER: vOffset = -(_height>>1); break; - + case VALIGN_BOTTOM: vOffset = _offsetPx; break; @@ -341,10 +350,10 @@ private: _offset = SGVec2d(hOffset, vOffset); } - + static const int LINE_LEADING = 3; static const int MARGIN = 3; - + mutable bool _dirtyText; mutable bool _dirtyOffset; int _age; @@ -358,7 +367,7 @@ private: int _offsetPx; mutable SGVec2d _offset; bool _dataVisible; - + static puFont _font; static puColor* _palette; static int _fontHeight; @@ -372,36 +381,118 @@ 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) : +MapWidget::MapWidget(int x, int y, int maxX, int maxY) : puObject(x,y,maxX, 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); - + _magVar = new SGMagVar(); } 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); } @@ -416,29 +507,29 @@ void MapWidget::setSize(int w, int h) void MapWidget::doHit( int button, int updown, int x, int y ) { - puObject::doHit(button, updown, x, y); - if (updown == PU_DRAG) { + puObject::doHit(button, updown, x, y); + if (updown == PU_DRAG) { handlePan(x, y); return; } - + if (button == 3) { // mouse-wheel up zoomIn(); } else if (button == 4) { // mouse-wheel down zoomOut(); } - + if (button != active_mouse_button) { return; } - + _hitLocation = SGVec2d(x - abox.min[0], y - abox.min[1]); - + if (updown == PU_UP) { puDeactivateWidget(); } else if (updown == PU_DOWN) { puSetActiveWidget(this, x, y); - + if (fgGetKeyModifiers() & KEYMOD_CTRL) { _clickGeod = unproject(_hitLocation - SGVec2d(_width>>1, _height>>1)); } @@ -457,7 +548,7 @@ int MapWidget::checkKey (int key, int updown ) if ((updown == PU_UP) || !isVisible () || !isActive () || (window != puGetWindow())) { return FALSE ; } - + switch (key) { @@ -468,24 +559,24 @@ int MapWidget::checkKey (int key, int updown ) case PU_KEY_DOWN: pan(SGVec2d(0, CURSOR_PAN_STEP)); break ; - + case PU_KEY_LEFT: pan(SGVec2d(CURSOR_PAN_STEP, 0)); break; - + case PU_KEY_RIGHT: pan(SGVec2d(-CURSOR_PAN_STEP, 0)); break; - + case '-': zoomOut(); - + break; - + case '=': zoomIn(); break; - + default : return FALSE; } @@ -495,103 +586,195 @@ int MapWidget::checkKey (int key, int updown ) void MapWidget::pan(const SGVec2d& delta) { + _hasPanned = true; _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"); - - if (_root->getBoolValue("centre-on-aircraft")) { - _projectionCenter = _aircraft; - _root->setBoolValue("centre-on-aircraft", false); - } - - double julianDate = globals->get_time_params()->getJD(); - _magVar->update(_projectionCenter, julianDate); + _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(); + } - bool aircraftUp = _root->getBoolValue("aircraft-heading-up"); - if (aircraftUp) { - _upHeading = fgGetDouble("/orientation/heading-deg"); - } else { - _upHeading = 0.0; - } +// 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()); + } + + 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(); +} - 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); glEnable(GL_SCISSOR_TEST); - + 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 SGVec2d loc = project(_aircraft); 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(); - + drawData(); - + glPopMatrix(); glDisable(GL_SCISSOR_TEST); } @@ -601,138 +784,148 @@ void MapWidget::paintRuler() if (_clickGeod == SGGeod()) { return; } - + SGVec2d acftPos = project(_aircraft); SGVec2d clickPos = project(_clickGeod); - + glColor4f(0.0, 1.0, 1.0, 0.6); drawLine(acftPos, clickPos); - + circleAtAlt(clickPos, 8, 10, 5); - + 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) { SGVec2d loc = project(aircraftPos); - + double hdg = fgGetDouble("/orientation/heading-deg"); - + glLineWidth(2.0); glColor4f(1.0, 1.0, 0.0, 1.0); glPushMatrix(); glTranslated(loc.x(), loc.y(), 0.0); glRotatef(hdg - _upHeading, 0.0, 0.0, -1.0); - + const SGVec2d wingspan(12, 0); const SGVec2d nose(0, 8); const SGVec2d tail(0, -14); const SGVec2d tailspan(4,0); - + drawLine(-wingspan, wingspan); drawLine(nose, tail); drawLine(tail - tailspan, tail + tailspan); - + glPopMatrix(); glLineWidth(1.0); } void MapWidget::paintRoute() { - if (_route->size() < 2) { + if (_route->numWaypts() < 2) { return; } - -// first pass, draw the actual line + + RoutePath path(_route->flightPlan()); + +// first pass, draw the actual lines glLineWidth(2.0); - glBegin(GL_LINE_STRIP); - - SGVec2d prev = project(_route->get_waypoint(0).get_target()); - glVertex2d(prev.x(), prev.y()); - - for (int w=1; w < _route->size(); ++w) { - - SGVec2d p = project(_route->get_waypoint(w).get_target()); - - if (w < _route->currentWaypoint()) { + + for (int w=0; w<_route->numWaypts(); ++w) { + SGGeodVec gv(path.pathForIndex(w)); + if (gv.empty()) { + continue; + } + + if (w < _route->currentIndex()) { glColor4f(0.5, 0.5, 0.5, 0.7); } else { glColor4f(1.0, 0.0, 1.0, 1.0); } - - glVertex2d(p.x(), p.y()); - + + flightgear::WayptRef wpt(_route->wayptAtIndex(w)); + if (wpt->flag(flightgear::WPT_MISS)) { + glEnable(GL_LINE_STIPPLE); + glLineStipple(1, 0x00FF); + } + + glBegin(GL_LINE_STRIP); + for (unsigned int i=0; isize(); ++w) { - const SGWayPoint& wpt(_route->get_waypoint(w)); - SGVec2d p = project(wpt.get_target()); + for (int w=0; w < _route->numWaypts(); ++w) { + flightgear::WayptRef wpt(_route->wayptAtIndex(w)); + SGGeod g = path.positionForIndex(w); + if (g == SGGeod()) { + continue; // Vectors or similar + } + + SGVec2d p = project(g); glColor4f(1.0, 0.0, 1.0, 1.0); circleAtAlt(p, 8, 12, 5); - + std::ostringstream legend; - legend << wpt.get_id(); - if (wpt.get_target_alt() > -9990.0) { - legend << '\n' << SGMiscd::roundToInt(wpt.get_target_alt()) << '\''; + legend << wpt->ident(); + if (wpt->altitudeRestriction() != flightgear::RESTRICT_NONE) { + legend << '\n' << SGMiscd::roundToInt(wpt->altitudeFt()) << '\''; } - - if (wpt.get_speed() > 0.0) { - legend << '\n' << SGMiscd::roundToInt(wpt.get_speed()) << "Kts"; + + if (wpt->speedRestriction() == flightgear::SPEED_RESTRICT_MACH) { + legend << '\n' << wpt->speedMach() << "M"; + } else if (wpt->speedRestriction() != flightgear::RESTRICT_NONE) { + legend << '\n' << SGMiscd::roundToInt(wpt->speedKts()) << "Kts"; } - + MapData* d = getOrCreateDataForKey(reinterpret_cast(w * 2)); d->setText(legend.str()); - d->setLabel(wpt.get_id()); + d->setLabel(wpt->ident()); d->setAnchor(p); d->setOffset(MapData::VALIGN_TOP | MapData::HALIGN_CENTER, 15); - d->setPriority(w < _route->currentWaypoint() ? 9000 : 12000); - - if (w > 0) { - SGVec2d legMid = (prev + p) * 0.5; - std::ostringstream legLegend; - - double track = wpt.get_track(); - if (_magneticHeadings) { - track -= _magVar->get_magvar(); // show magnetic track for leg - } - - legLegend << SGMiscd::roundToInt(track) << " " - << SGMiscd::roundToInt(wpt.get_distance() * SG_METER_TO_NM) << "Nm"; - - MapData* ld = getOrCreateDataForKey(reinterpret_cast(w * 2 + 1)); - ld->setText(legLegend.str()); - ld->setAnchor(legMid); - ld->setOffset(MapData::VALIGN_TOP | MapData::HALIGN_CENTER, 15); - ld->setPriority(w < _route->currentWaypoint() ? 8000 : 11000); - } // of draw leg data - - prev = p; + d->setPriority(w < _route->currentIndex() ? 9000 : 12000); + } // 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. + * Round a SGGeod to an arbitrary precision. * For example, passing precision of 0.5 will round to the nearest 0.5 of * a degree in both lat and lon - passing in 3.0 rounds to the nearest 3 degree * multiple, and so on. @@ -741,7 +934,7 @@ static SGGeod roundGeod(double precision, const SGGeod& g) { double lon = SGMiscd::round(g.getLongitudeDeg() / precision); double lat = SGMiscd::round(g.getLatitudeDeg() / precision); - + return SGGeod::fromDeg(lon * precision, lat * precision); } @@ -751,13 +944,13 @@ bool MapWidget::drawLineClipped(const SGVec2d& a, const SGVec2d& b) minY = SGMiscd::min(a.y(), b.y()), maxX = SGMiscd::max(a.x(), b.x()), maxY = SGMiscd::max(a.y(), b.y()); - + int hh = _height >> 1, hw = _width >> 1; - + if ((maxX < -hw) || (minX > hw) || (minY > hh) || (maxY < -hh)) { return false; } - + glVertex2dv(a.data()); glVertex2dv(b.data()); return true; @@ -770,11 +963,11 @@ SGVec2d MapWidget::gridPoint(int ix, int iy) if (it != _gridCache.end()) { return it->second; } - + SGGeod gp = SGGeod::fromDeg( _gridCenter.getLongitudeDeg() + ix * _gridSpacing, _gridCenter.getLatitudeDeg() + iy * _gridSpacing); - + SGVec2d proj = project(gp); _gridCache[key] = proj; return proj; @@ -782,10 +975,16 @@ 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(); - + int ix = 0; int iy = 0; @@ -796,188 +995,126 @@ void MapWidget::drawLatLonGrid() didDraw = false; ++ix; ++iy; - + for (int x = -ix; x < ix; ++x) { didDraw |= drawLineClipped(gridPoint(x, -iy), gridPoint(x+1, -iy)); 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, y+1)); didDraw |= drawLineClipped(gridPoint(-ix, y), gridPoint(-ix + 1, y)); didDraw |= drawLineClipped(gridPoint(ix, y), gridPoint(ix, y+1)); didDraw |= drawLineClipped(gridPoint(ix, y), gridPoint(ix - 1, y)); } - - if (ix > 30) { + + if (ix > (90/_gridSpacing)-1) { break; } } while (didDraw); - + glEnd(); } void MapWidget::drawGPSData() { std::string gpsMode = _gps->getStringValue("mode"); - + SGGeod wp0Geod = SGGeod::fromDeg( - _gps->getDoubleValue("wp/wp[0]/longitude-deg"), + _gps->getDoubleValue("wp/wp[0]/longitude-deg"), _gps->getDoubleValue("wp/wp[0]/latitude-deg")); - + SGGeod wp1Geod = SGGeod::fromDeg( - _gps->getDoubleValue("wp/wp[1]/longitude-deg"), + _gps->getDoubleValue("wp/wp[1]/longitude-deg"), _gps->getDoubleValue("wp/wp[1]/latitude-deg")); - + // draw track line double gpsTrackDeg = _gps->getDoubleValue("indicated-track-true-deg"); double gpsSpeed = _gps->getDoubleValue("indicated-ground-speed-kt"); double az2; - + if (gpsSpeed > 3.0) { // only draw track line if valid SGGeod trackRadial; SGGeodesy::direct(_aircraft, gpsTrackDeg, _drawRangeNm * SG_NM_TO_METER, trackRadial, az2); - + glColor4f(1.0, 1.0, 0.0, 1.0); glEnable(GL_LINE_STIPPLE); glLineStipple(1, 0x00FF); drawLine(project(_aircraft), project(trackRadial)); glDisable(GL_LINE_STIPPLE); } - + if (gpsMode == "dto") { SGVec2d wp0Pos = project(wp0Geod); SGVec2d wp1Pos = project(wp1Geod); - + glColor4f(1.0, 0.0, 1.0, 1.0); drawLine(wp0Pos, wp1Pos); - + } - + if (_gps->getBoolValue("scratch/valid")) { // draw scratch data - - } -} -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) { SGVec2d pos = project(ndb->geod()); - + if (tuned) { glColor3f(0.0, 1.0, 1.0); } else { glColor3f(0.0, 0.0, 0.0); } - + glEnable(GL_LINE_STIPPLE); glLineStipple(1, 0x00FF); circleAt(pos, 20, 6); circleAt(pos, 20, 10); glDisable(GL_LINE_STIPPLE); - + if (validDataForKey(ndb)) { setAnchorForKey(ndb, pos); return; } - + char buffer[1024]; ::snprintf(buffer, 1024, "%s\n%s %3.0fKhz", ndb->name().c_str(), ndb->ident().c_str(),ndb->get_freq()/100.0); - + MapData* d = createDataForKey(ndb); d->setPriority(40); d->setLabel(ndb->ident()); @@ -995,19 +1132,23 @@ void MapWidget::drawVOR(bool tuned, FGNavRecord* vor) } else { 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); return; } - + char buffer[1024]; ::snprintf(buffer, 1024, "%s\n%s %6.3fMhz", vor->name().c_str(), vor->ident().c_str(), vor->get_freq() / 100.0); - + MapData* d = createDataForKey(vor); d->setText(buffer); d->setLabel(vor->ident()); @@ -1021,8 +1162,8 @@ void MapWidget::drawFix(FGFix* fix) SGVec2d pos = project(fix->geod()); 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 } @@ -1030,7 +1171,7 @@ void MapWidget::drawFix(FGFix* fix) setAnchorForKey(fix, pos); return; } - + MapData* d = createDataForKey(fix); d->setLabel(fix->ident()); d->setPriority(20); @@ -1040,71 +1181,107 @@ void MapWidget::drawFix(FGFix* fix) void MapWidget::drawNavRadio(SGPropertyNode_ptr radio) { - if (!radio || radio->getBoolValue("slaved-to-gps", false) + if (!radio || radio->getBoolValue("slaved-to-gps", false) || !radio->getBoolValue("in-range", false)) { return; } - + if (radio->getBoolValue("nav-loc", false)) { drawTunedLocalizer(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; } - + glLineWidth(1.0); drawVOR(true, nav); - + SGVec2d pos = project(nav->geod()); SGGeod range; double az2; double trueRadial = radio->getDoubleValue("radials/target-radial-deg"); SGGeodesy::direct(nav->geod(), trueRadial, nav->get_range() * SG_NM_TO_METER, range, az2); SGVec2d prange = project(range); - + SGVec2d norm = normalize(prange - pos); SGVec2d perp(norm.y(), -norm.x()); - + circleAt(pos, 64, length(prange - pos)); drawLine(pos, prange); - + // draw to/from arrows SGVec2d midPoint = (pos + prange) * 0.5; if (radio->getBoolValue("from-flag")) { norm = -norm; perp = -perp; } - + int sz = 10; SGVec2d arrowB = midPoint - (norm * sz) + (perp * sz); SGVec2d arrowC = midPoint - (norm * sz) - (perp * sz); drawLine(midPoint, arrowB); drawLine(arrowB, arrowC); drawLine(arrowC, midPoint); - + drawLine(pos, (2 * pos) - prange); // reciprocal radial } 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; } - + if (loc->runway()) { drawILS(true, loc->runway()); } } +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) { @@ -1119,18 +1296,18 @@ 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); - + drawLine(towerPos + SGVec2d(3, 0), towerPos + SGVec2d(3, 10)); drawLine(towerPos + SGVec2d(-3, 0), towerPos + SGVec2d(-3, 10)); drawLine(towerPos + SGVec2d(-6, 20), towerPos + SGVec2d(-3, 10)); drawLine(towerPos + SGVec2d(6, 20), towerPos + SGVec2d(3, 10)); drawLine(towerPos + SGVec2d(-6, 20), towerPos + SGVec2d(6, 20)); } - + if (validDataForKey(apt)) { setAnchorForKey(apt, towerPos); } else { @@ -1145,52 +1322,58 @@ void MapWidget::drawAirport(FGAirport* apt) d->setOffset(MapData::VALIGN_TOP | MapData::HALIGN_CENTER, 6); 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); - } - - if (rwy->ILS()) { - drawILS(false, rwy); - } - } // of runway iteration - + + for (unsigned int r=0; rILS()) { + 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 + } 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; } - + if (rwy->lengthFt() < minLength) { continue; } - + int scoreLength = SGMiscd::roundToInt(rwy->lengthFt() / 200.0); score += scoreLength; } // of runways iteration @@ -1202,7 +1385,7 @@ void MapWidget::drawRunwayPre(FGRunway* rwy) { SGVec2d p1 = project(rwy->begin()); SGVec2d p2 = project(rwy->end()); - + glLineWidth(4.0); glColor3f(1.0, 0.0, 1.0); drawLine(p1, p2); @@ -1210,32 +1393,32 @@ void MapWidget::drawRunwayPre(FGRunway* rwy) void MapWidget::drawRunway(FGRunway* rwy) { - // line for runway + // line for runway // optionally show active, stopway, etc // in legend, show published heading and length // and threshold elevation - + SGVec2d p1 = project(rwy->begin()); SGVec2d p2 = project(rwy->end()); glLineWidth(2.0); glColor3f(1.0, 1.0, 1.0); SGVec2d inset = normalize(p2 - p1) * 2; - + drawLine(p1 + inset, p2 - inset); - + if (validDataForKey(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); d->setText(buffer); d->setLabel(rwy->ident() + "/" + rwy->reciprocalRunway()->ident()); @@ -1250,7 +1433,7 @@ void MapWidget::drawILS(bool tuned, FGRunway* rwy) // using LOC transmitter position would be more accurate, but // is visually cluttered // arrow width is based upon the computed localizer width - + FGNavRecord* loc = rwy->ILS(); double halfBeamWidth = loc->localizerWidth() * 0.5; SGVec2d t = project(rwy->threshold()); @@ -1259,17 +1442,17 @@ void MapWidget::drawILS(bool tuned, FGRunway* rwy) double radial = loc->get_multiuse(); SG_NORMALIZE_RANGE(radial, 0.0, 360.0); double az2; - + // compute the three end points at the widge end of the arrow SGGeodesy::direct(loc->geod(), radial, -rangeM, locEnd, az2); SGVec2d endCentre = project(locEnd); - + SGGeodesy::direct(loc->geod(), radial + halfBeamWidth, -rangeM * 1.1, locEnd, az2); SGVec2d endR = project(locEnd); - + SGGeodesy::direct(loc->geod(), radial - halfBeamWidth, -rangeM * 1.1, locEnd, az2); SGVec2d endL = project(locEnd); - + // outline two triangles glLineWidth(1.0); if (tuned) { @@ -1277,7 +1460,7 @@ void MapWidget::drawILS(bool tuned, FGRunway* rwy) } else { glColor3f(0.0, 0.0, 1.0); } - + glBegin(GL_LINE_LOOP); glVertex2dv(t.data()); glVertex2dv(endCentre.data()); @@ -1288,111 +1471,176 @@ void MapWidget::drawILS(bool tuned, FGRunway* rwy) glVertex2dv(endCentre.data()); glVertex2dv(endR.data()); glEnd(); + + if (validDataForKey(loc)) { + setAnchorForKey(loc, endR); + return; + } + + char buffer[1024]; + ::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); + d->setLabel(loc->ident()); + d->setText(buffer); + d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 10); + d->setAnchor(endR); } void MapWidget::drawTraffic() { - if (!_root->getBoolValue("draw-traffic")) { - return; - } - - if (_zoom > SHOW_DETAIL_ZOOM) { + AIDrawVec::const_iterator it; + for (it = _aiDrawVec.begin(); it != _aiDrawVec.end(); ++it) { + drawAI(*it); + } +} + +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; } - - 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) < 0) { - continue; - } + char buffer[1024]; + ::snprintf(buffer, 1024, "%s\n%03d\n%.0f'", + hp->ident().c_str(), + displayHeading(hp->headingDeg()), + hp->lengthFt()); - 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); - } - } // of ai/models iteration + 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) +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(model->getDoubleValue("velocities/true-airspeed-kt")); - 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 / altitude / speed - - - char buffer[1024]; - ::snprintf(buffer, 1024, "%s\n%d'\n%dkts", - model->getStringValue("callsign", "<>"), - static_cast(pos.getElevationFt() / 50.0) * 50, - speedKts); - - MapData* d = createDataForKey((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) -{ - + + 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), + double cost = cos(_upHeading * SG_DEGREES_TO_RADIANS), sint = sin(_upHeading * SG_DEGREES_TO_RADIANS); double rx = cost * p.x() - sint * p.y(); double ry = sint * p.x() + cost * p.y(); @@ -1402,38 +1650,118 @@ SGVec2d MapWidget::project(const SGGeod& geod) const SGGeod MapWidget::unproject(const SGVec2d& p) const { // unrotate, if necessary - double cost = cos(-_upHeading * SG_DEGREES_TO_RADIANS), + double cost = cos(-_upHeading * SG_DEGREES_TO_RADIANS), sint = sin(-_upHeading * SG_DEGREES_TO_RADIANS); - SGVec2d ur(cost * p.x() - sint * p.y(), + 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(); - - return SGGeod::fromRad(lon, lat); + + 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); + } + + 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; i lines(simgear::strutils::split(t, "\n")); const int LINE_LEADING = 4; const int MARGIN = 4; - + // measure int maxWidth = -1, totalHeight = 0; int lineHeight = legendFont.getStringHeight(); - + for (unsigned int ln=0; ln 0) { totalHeight += LINE_LEADING; } - + int lw = legendFont.getStringWidth(lines[ln].c_str()); maxWidth = std::max(maxWidth, lw); } // of line measurement - + if (maxWidth < 0) { return; // all lines are empty, don't draw } - + totalHeight += MARGIN * 2; // draw box @@ -1490,12 +1818,12 @@ void MapWidget::drawLegendBox(const SGVec2d& pos, const std::string& t) box.max[1] = 0; int border = 1; box.draw (pos.x(), pos.y(), PUSTYLE_DROPSHADOW, colour, FALSE, border); - + // draw lines int xPos = pos.x() + MARGIN; int yPos = pos.y() - (lineHeight + MARGIN); glColor3f(0.8, 0.8, 0.8); - + for (unsigned int ln=0; ln> 1, + + int hw = _width >> 1, hh = _height >> 1; puBox visBox(makePuBox(-hw, -hh, _width, _height)); - + unsigned int d = 0; int drawn = 0; std::vector drawQueue; - + bool drawData = _root->getBoolValue("draw-data"); const int MAX_DRAW_DATA = 25; const int MAX_DRAW = 50; - + for (; (d < _dataQueue.size()) && (drawn < MAX_DRAW); ++d) { MapData* md = _dataQueue[d]; md->setDataVisible(drawData); - + if (md->isClipped(visBox)) { continue; } - + if (md->overlaps(drawQueue)) { if (drawData) { // overlapped with data, let's try just the label md->setDataVisible(false); @@ -1536,20 +1864,20 @@ void MapWidget::drawData() continue; } } // of overlaps case - + drawQueue.push_back(md); ++drawn; if (drawData && (drawn >= MAX_DRAW_DATA)) { drawData = false; } } - + // draw lowest-priority first, so higher-priorty items appear on top std::vector::reverse_iterator r; for (r = drawQueue.rbegin(); r!= drawQueue.rend(); ++r) { (*r)->draw(); } - + _dataQueue.clear(); KeyDataMap::iterator it = _mapData.begin(); for (; it != _mapData.end(); ) { @@ -1570,7 +1898,7 @@ bool MapWidget::validDataForKey(void* key) if (it == _mapData.end()) { return false; // no valid data for the key! } - + it->second->resetAge(); // mark data as valid this frame _dataQueue.push_back(it->second); return true; @@ -1582,7 +1910,7 @@ void MapWidget::setAnchorForKey(void* key, const SGVec2d& anchor) if (it == _mapData.end()) { throw sg_exception("no valid data for key!"); } - + it->second->setAnchor(anchor); } @@ -1592,7 +1920,7 @@ MapData* MapWidget::getOrCreateDataForKey(void* key) if (it == _mapData.end()) { return createDataForKey(key); } - + it->second->resetAge(); // mark data as valid this frame _dataQueue.push_back(it->second); return it->second; @@ -1604,10 +1932,90 @@ MapData* MapWidget::createDataForKey(void* key) if (it != _mapData.end()) { throw sg_exception("duplicate data requested for key!"); } - + MapData* d = new MapData(0); _mapData[key] = d; _dataQueue.push_back(d); d->resetAge(); 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; + } +} +