5 #include "MapWidget.hxx"
8 #include <algorithm> // for std::sort
9 #include <plib/puAux.h>
11 #include <simgear/sg_inlines.h>
12 #include <simgear/misc/strutils.hxx>
13 #include <simgear/magvar/magvar.hxx>
14 #include <simgear/timing/sg_time.hxx> // for magVar julianDate
15 #include <simgear/structure/exception.hxx>
17 #include <Main/globals.hxx>
18 #include <Main/fg_props.hxx>
19 #include <Autopilot/route_mgr.hxx>
20 #include <Navaids/positioned.hxx>
21 #include <Navaids/navrecord.hxx>
22 #include <Navaids/navlist.hxx>
23 #include <Navaids/fix.hxx>
24 #include <Airports/airport.hxx>
25 #include <Airports/runways.hxx>
26 #include <Main/fg_os.hxx> // fgGetKeyModifiers()
27 #include <Navaids/routePath.hxx>
28 #include <Aircraft/FlightHistory.hxx>
29 #include <AIModel/AIAircraft.hxx>
30 #include <AIModel/AIManager.hxx>
31 #include <AIModel/AIFlightPlan.hxx>
33 const char* RULER_LEGEND_KEY = "ruler-legend";
35 /* equatorial and polar earth radius */
36 const float rec = 6378137; // earth radius, equator (?)
37 const float rpol = 6356752.314f; // earth radius, polar (?)
39 /************************************************************************
40 some trigonometric helper functions
41 (translated more or less directly from Alexei Novikovs perl original)
42 *************************************************************************/
44 //Returns Earth radius at a given latitude (Ellipsoide equation with two equal axis)
45 static float earth_radius_lat( float lat )
47 double a = cos(lat)/rec;
48 double b = sin(lat)/rpol;
49 return 1.0f / sqrt( a * a + b * b );
52 ///////////////////////////////////////////////////////////////////////////
54 static puBox makePuBox(int x, int y, int w, int h)
64 static bool puBoxIntersect(const puBox& a, const puBox& b)
66 int x0 = SG_MAX2(a.min[0], b.min[0]);
67 int y0 = SG_MAX2(a.min[1], b.min[1]);
68 int x1 = SG_MIN2(a.max[0], b.max[0]);
69 int y1 = SG_MIN2(a.max[1], b.max[1]);
71 return (x0 <= x1) && (y0 <= y1);
75 typedef std::vector<MapData*> MapDataVec;
80 static const int HALIGN_LEFT = 1;
81 static const int HALIGN_CENTER = 2;
82 static const int HALIGN_RIGHT = 3;
84 static const int VALIGN_TOP = 1 << 4;
85 static const int VALIGN_CENTER = 2 << 4;
86 static const int VALIGN_BOTTOM = 3 << 4;
88 MapData(int priority) :
94 _offsetDir(HALIGN_LEFT | VALIGN_CENTER),
100 void setLabel(const std::string& label)
102 if (label == _label) {
103 return; // common case, and saves invalidation
110 void setText(const std::string &text)
112 if (_rawText == text) {
113 return; // common case, and saves invalidation
120 void setDataVisible(bool vis) {
121 if (vis == _dataVisible) {
125 if (_rawText.empty()) {
133 static void setFont(puFont f)
136 _fontHeight = f.getStringHeight();
137 _fontDescender = f.getStringDescender();
140 static void setPalette(puColor* pal)
145 void setPriority(int pri)
151 { return _priority; }
153 void setAnchor(const SGVec2d& anchor)
158 void setOffset(int direction, int px)
160 if ((_offsetPx == px) && (_offsetDir == direction)) {
165 _offsetDir = direction;
169 bool isClipped(const puBox& vis) const
172 if ((_width < 1) || (_height < 1)) {
176 return !puBoxIntersect(vis, box());
179 bool overlaps(const MapDataVec& l) const
184 MapDataVec::const_iterator it;
185 for (it = l.begin(); it != l.end(); ++it) {
186 if (puBoxIntersect(b, (*it)->box())) {
189 } // of list iteration
198 _anchor.x() + _offset.x(),
199 _anchor.y() + _offset.y(),
203 void drawStringUtf8(std::string& utf8Str, double x, double y, puFont fnt)
205 fnt.drawString(simgear::strutils::utf8ToLatin1(utf8Str).c_str(), x, y);
212 int xx = _anchor.x() + _offset.x();
213 int yy = _anchor.y() + _offset.y();
216 puBox box(makePuBox(0,0,_width, _height));
218 box.draw(xx, yy, PUSTYLE_DROPSHADOW, _palette, FALSE, border);
221 int lineHeight = _fontHeight;
222 int xPos = xx + MARGIN;
223 int yPos = yy + _height - (lineHeight + MARGIN);
224 glColor3f(0.8, 0.8, 0.8);
226 for (unsigned int ln=0; ln<_lines.size(); ++ln) {
227 drawStringUtf8(_lines[ln], xPos, yPos, _font);
228 yPos -= lineHeight + LINE_LEADING;
231 glColor3f(0.8, 0.8, 0.8);
232 drawStringUtf8(_label, xx, yy + _fontDescender, _font);
246 bool isExpired() const
247 { return (_age > 100); }
249 static bool order(MapData* a, MapData* b)
251 return a->_priority > b->_priority;
254 void validate() const
274 void measureData() const
276 _lines = simgear::strutils::split(_rawText, "\n");
277 // measure text to find width and height
281 for (unsigned int ln=0; ln<_lines.size(); ++ln) {
282 _height += _fontHeight;
284 _height += LINE_LEADING;
287 int lw = _font.getStringWidth(_lines[ln].c_str());
288 _width = std::max(_width, lw);
289 } // of line measurement
291 if ((_width < 1) || (_height < 1)) {
296 _height += MARGIN * 2;
297 _width += MARGIN * 2;
300 void measureLabel() const
302 if (_label.empty()) {
303 _width = _height = -1;
307 _height = _fontHeight;
308 _width = _font.getStringWidth(_label.c_str());
311 void computeOffset() const
313 _dirtyOffset = false;
314 if ((_width <= 0) || (_height <= 0)) {
321 switch (_offsetDir & 0x0f) {
328 hOffset = -(_width>>1);
332 hOffset = -(_offsetPx + _width);
336 switch (_offsetDir & 0xf0) {
339 vOffset = -(_offsetPx + _height);
343 vOffset = -(_height>>1);
351 _offset = SGVec2d(hOffset, vOffset);
354 static const int LINE_LEADING = 3;
355 static const int MARGIN = 3;
357 mutable bool _dirtyText;
358 mutable bool _dirtyOffset;
360 std::string _rawText;
362 mutable std::vector<std::string> _lines;
364 mutable int _width, _height;
368 mutable SGVec2d _offset;
372 static puColor* _palette;
373 static int _fontHeight;
374 static int _fontDescender;
377 puFont MapData::_font;
378 puColor* MapData::_palette;
379 int MapData::_fontHeight = 0;
380 int MapData::_fontDescender = 0;
382 ///////////////////////////////////////////////////////////////////////////
384 // anonymous namespace
388 class MapAirportFilter : public FGAirport::AirportFilter
391 MapAirportFilter(SGPropertyNode_ptr nd)
393 _heliports = nd->getBoolValue("show-heliports", false);
394 _hardRunwaysOnly = nd->getBoolValue("hard-surfaced-airports", true);
395 _minLengthFt = fgGetDouble("/sim/navdb/min-runway-length-ft", 2000);
398 virtual FGPositioned::Type maxType() const {
399 return _heliports ? FGPositioned::HELIPORT : FGPositioned::AIRPORT;
402 virtual bool passAirport(FGAirport* aApt) const {
403 if (_hardRunwaysOnly) {
404 return aApt->hasHardRunwayOfLengthFt(_minLengthFt);
412 _hardRunwaysOnly = false;
417 bool _hardRunwaysOnly;
421 class NavaidFilter : public FGPositioned::Filter
424 NavaidFilter(bool fixesEnabled, bool navaidsEnabled) :
425 _fixes(fixesEnabled),
426 _navaids(navaidsEnabled)
429 virtual bool pass(FGPositioned* aPos) const {
430 if (_fixes && (aPos->type() == FGPositioned::FIX)) {
431 // ignore fixes which end in digits - expirmental
432 if (aPos->ident().length() > 4 && isdigit(aPos->ident()[3]) && isdigit(aPos->ident()[4])) {
440 virtual FGPositioned::Type minType() const {
441 return _fixes ? FGPositioned::FIX : FGPositioned::NDB;
444 virtual FGPositioned::Type maxType() const {
445 return _navaids ? FGPositioned::VOR : FGPositioned::FIX;
449 bool _fixes, _navaids;
452 } // of anonymous namespace
454 const int MAX_ZOOM = 12;
455 const int SHOW_DETAIL_ZOOM = 8;
456 const int SHOW_DETAIL2_ZOOM = 5;
457 const int CURSOR_PAN_STEP = 32;
459 MapWidget::MapWidget(int x, int y, int maxX, int maxY) :
460 puObject(x,y,maxX, maxY)
462 _route = static_cast<FGRouteMgr*>(globals->get_subsystem("route-manager"));
463 _gps = fgGetNode("/instrumentation/gps");
468 _projection = PROJECTION_AZIMUTHAL_EQUIDISTANT;
469 _magneticHeadings = false;
471 MapData::setFont(legendFont);
472 MapData::setPalette(colour);
474 _magVar = new SGMagVar();
477 MapWidget::~MapWidget()
483 void MapWidget::setProperty(SGPropertyNode_ptr prop)
486 int zoom = _root->getIntValue("zoom", -1);
488 _root->setIntValue("zoom", 6); // default zoom
491 // expose MAX_ZOOM to the UI
492 _root->setIntValue("max-zoom", MAX_ZOOM);
493 _root->setBoolValue("centre-on-aircraft", true);
494 _root->setBoolValue("draw-data", false);
495 _root->setBoolValue("draw-flight-history", false);
496 _root->setBoolValue("magnetic-headings", true);
499 void MapWidget::setSize(int w, int h)
501 puObject::setSize(w, h);
508 void MapWidget::doHit( int button, int updown, int x, int y )
510 puObject::doHit(button, updown, x, y);
511 if (updown == PU_DRAG) {
516 if (button == 3) { // mouse-wheel up
518 } else if (button == 4) { // mouse-wheel down
522 if (button != active_mouse_button) {
526 _hitLocation = SGVec2d(x - abox.min[0], y - abox.min[1]);
528 if (updown == PU_UP) {
529 puDeactivateWidget();
530 } else if (updown == PU_DOWN) {
531 puSetActiveWidget(this, x, y);
533 if (fgGetKeyModifiers() & KEYMOD_CTRL) {
534 _clickGeod = unproject(_hitLocation - SGVec2d(_width>>1, _height>>1));
539 void MapWidget::handlePan(int x, int y)
541 SGVec2d delta = SGVec2d(x, y) - _hitLocation;
543 _hitLocation = SGVec2d(x,y);
546 int MapWidget::checkKey (int key, int updown )
548 if ((updown == PU_UP) || !isVisible () || !isActive () || (window != puGetWindow())) {
556 pan(SGVec2d(0, -CURSOR_PAN_STEP));
560 pan(SGVec2d(0, CURSOR_PAN_STEP));
564 pan(SGVec2d(CURSOR_PAN_STEP, 0));
568 pan(SGVec2d(-CURSOR_PAN_STEP, 0));
587 void MapWidget::pan(const SGVec2d& delta)
590 _projectionCenter = unproject(-delta);
593 int MapWidget::zoom() const
595 int z = _root->getIntValue("zoom");
596 SG_CLAMP_RANGE(z, 0, MAX_ZOOM);
600 void MapWidget::zoomIn()
602 if (zoom() >= MAX_ZOOM) {
606 _root->setIntValue("zoom", zoom() + 1);
609 void MapWidget::zoomOut()
615 _root->setIntValue("zoom", zoom() - 1);
618 void MapWidget::update()
620 _aircraft = globals->get_aircraft_position();
622 bool mag = _root->getBoolValue("magnetic-headings");
623 if (mag != _magneticHeadings) {
624 clearData(); // flush cached data text, since it often includes heading
625 _magneticHeadings = mag;
629 _root->setBoolValue("centre-on-aircraft", false);
632 else if (_root->getBoolValue("centre-on-aircraft")) {
633 _projectionCenter = _aircraft;
636 double julianDate = globals->get_time_params()->getJD();
637 _magVar->update(_projectionCenter, julianDate);
639 _aircraftUp = _root->getBoolValue("aircraft-heading-up");
641 _upHeading = fgGetDouble("/orientation/heading-deg");
646 if (_magneticHeadings) {
647 _displayHeading = (int) fgGetDouble("/orientation/heading-magnetic-deg");
649 _displayHeading = (int) _upHeading;
652 _cachedZoom = MAX_ZOOM - zoom();
653 SGGeod topLeft = unproject(SGVec2d(_width/2, _height/2));
654 // compute draw range, including a fudge factor for ILSs and other 'long'
656 _drawRangeNm = SGGeodesy::distanceNm(_projectionCenter, topLeft) + 10.0;
658 FGFlightHistory* history = (FGFlightHistory*) globals->get_subsystem("history");
659 if (history && _root->getBoolValue("draw-flight-history")) {
660 _flightHistoryPath = history->pathForHistory();
662 _flightHistoryPath.clear();
665 // make spatial queries. This can trigger loading of XML files, etc, so we do
666 // not want to do it in draw(), which can be called from an arbitrary OSG
669 MapAirportFilter af(_root);
670 if (_cachedZoom <= SHOW_DETAIL2_ZOOM) {
671 // show all airports when zoomed in sufficently
675 bool partial = false;
676 FGPositionedList newItemsToDraw =
677 FGPositioned::findWithinRangePartial(_projectionCenter, _drawRangeNm, &af, partial);
679 bool fixes = _root->getBoolValue("draw-fixes");
680 NavaidFilter f(fixes, _root->getBoolValue("draw-navaids"));
681 if (f.minType() <= f.maxType()) {
682 FGPositionedList navs = FGPositioned::findWithinRange(_projectionCenter, _drawRangeNm, &f);
683 newItemsToDraw.insert(newItemsToDraw.end(), navs.begin(), navs.end());
686 FGPositioned::TypeFilter tf(FGPositioned::COUNTRY);
687 if (_cachedZoom <= SHOW_DETAIL_ZOOM) {
688 tf.addType(FGPositioned::CITY);
691 if (_cachedZoom <= SHOW_DETAIL2_ZOOM) {
692 tf.addType(FGPositioned::TOWN);
695 FGPositionedList poi = FGPositioned::findWithinRange(_projectionCenter, _drawRangeNm, &tf);
696 newItemsToDraw.insert(newItemsToDraw.end(), poi.begin(), poi.end());
698 _itemsToDraw.swap(newItemsToDraw);
703 void MapWidget::updateAIObjects()
705 if (!_root->getBoolValue("draw-traffic") || (_cachedZoom > SHOW_DETAIL_ZOOM)) {
710 AIDrawVec newDrawVec;
712 const SGPropertyNode* ai = fgGetNode("/ai/models", true);
713 for (int i = 0; i < ai->nChildren(); ++i) {
714 const SGPropertyNode *model = ai->getChild(i);
715 // skip bad or dead entries
716 if (!model || model->getIntValue("id", -1) == -1) {
720 SGGeod pos = SGGeod::fromDegFt(
721 model->getDoubleValue("position/longitude-deg"),
722 model->getDoubleValue("position/latitude-deg"),
723 model->getDoubleValue("position/altitude-ft"));
725 double dist = SGGeodesy::distanceNm(_projectionCenter, pos);
726 if (dist > _drawRangeNm) {
730 newDrawVec.push_back(DrawAIObject((SGPropertyNode*) model, pos));
731 } // of ai/models iteration
733 _aiDrawVec.swap(newDrawVec);
736 void MapWidget::draw(int dx, int dy)
738 GLint sx = (int) abox.min[0],
739 sy = (int) abox.min[1];
740 glScissor(dx + sx, dy + sy, _width, _height);
741 glEnable(GL_SCISSOR_TEST);
743 glMatrixMode(GL_MODELVIEW);
745 // center drawing about the widget center (which is also the
746 // projection centre)
747 glTranslated(dx + sx + (_width/2), dy + sy + (_height/2), 0.0);
752 int textHeight = legendFont.getStringHeight() + 5;
755 SGVec2d loc = project(_aircraft);
756 glColor3f(1.0, 1.0, 1.0);
757 drawLine(loc, SGVec2d(loc.x(), (_height / 2) - textHeight));
759 double y = (_height / 2) - textHeight;
761 ::snprintf(buf, 16, "%d", _displayHeading);
762 int sw = legendFont.getStringWidth(buf);
763 legendFont.drawString(buf, loc.x() - sw/2, y);
769 drawNavRadio(fgGetNode("/instrumentation/nav[0]", false));
770 drawNavRadio(fgGetNode("/instrumentation/nav[1]", false));
771 paintAircraftLocation(_aircraft);
779 glDisable(GL_SCISSOR_TEST);
782 void MapWidget::paintRuler()
784 if (_clickGeod == SGGeod()) {
788 SGVec2d acftPos = project(_aircraft);
789 SGVec2d clickPos = project(_clickGeod);
791 glColor4f(0.0, 1.0, 1.0, 0.6);
792 drawLine(acftPos, clickPos);
794 circleAtAlt(clickPos, 8, 10, 5);
796 double dist, az, az2;
797 SGGeodesy::inverse(_aircraft, _clickGeod, az, az2, dist);
799 ::snprintf(buffer, 1024, "%03d/%.1fnm",
800 displayHeading(az), dist * SG_METER_TO_NM);
802 MapData* d = getOrCreateDataForKey((void*) RULER_LEGEND_KEY);
804 d->setAnchor(clickPos);
805 d->setOffset(MapData::VALIGN_TOP | MapData::HALIGN_CENTER, 15);
806 d->setPriority(20000);
809 void MapWidget::paintAircraftLocation(const SGGeod& aircraftPos)
811 SGVec2d loc = project(aircraftPos);
813 double hdg = fgGetDouble("/orientation/heading-deg");
816 glColor4f(1.0, 1.0, 0.0, 1.0);
818 glTranslated(loc.x(), loc.y(), 0.0);
819 glRotatef(hdg - _upHeading, 0.0, 0.0, -1.0);
821 const SGVec2d wingspan(12, 0);
822 const SGVec2d nose(0, 8);
823 const SGVec2d tail(0, -14);
824 const SGVec2d tailspan(4,0);
826 drawLine(-wingspan, wingspan);
827 drawLine(nose, tail);
828 drawLine(tail - tailspan, tail + tailspan);
834 void MapWidget::paintRoute()
836 if (_route->numWaypts() < 2) {
840 RoutePath path(_route->flightPlan());
842 // first pass, draw the actual lines
845 for (int w=0; w<_route->numWaypts(); ++w) {
846 SGGeodVec gv(path.pathForIndex(w));
851 if (w < _route->currentIndex()) {
852 glColor4f(0.5, 0.5, 0.5, 0.7);
854 glColor4f(1.0, 0.0, 1.0, 1.0);
857 flightgear::WayptRef wpt(_route->wayptAtIndex(w));
858 if (wpt->flag(flightgear::WPT_MISS)) {
859 glEnable(GL_LINE_STIPPLE);
860 glLineStipple(1, 0x00FF);
863 glBegin(GL_LINE_STRIP);
864 for (unsigned int i=0; i<gv.size(); ++i) {
865 SGVec2d p = project(gv[i]);
866 glVertex2d(p.x(), p.y());
870 glDisable(GL_LINE_STIPPLE);
874 // second pass, draw waypoint symbols and data
875 for (int w=0; w < _route->numWaypts(); ++w) {
876 flightgear::WayptRef wpt(_route->wayptAtIndex(w));
877 SGGeod g = path.positionForIndex(w);
879 continue; // Vectors or similar
882 SGVec2d p = project(g);
883 glColor4f(1.0, 0.0, 1.0, 1.0);
884 circleAtAlt(p, 8, 12, 5);
886 std::ostringstream legend;
887 legend << wpt->ident();
888 if (wpt->altitudeRestriction() != flightgear::RESTRICT_NONE) {
889 legend << '\n' << SGMiscd::roundToInt(wpt->altitudeFt()) << '\'';
892 if (wpt->speedRestriction() == flightgear::SPEED_RESTRICT_MACH) {
893 legend << '\n' << wpt->speedMach() << "M";
894 } else if (wpt->speedRestriction() != flightgear::RESTRICT_NONE) {
895 legend << '\n' << SGMiscd::roundToInt(wpt->speedKts()) << "Kts";
898 MapData* d = getOrCreateDataForKey(reinterpret_cast<void*>(w * 2));
899 d->setText(legend.str());
900 d->setLabel(wpt->ident());
902 d->setOffset(MapData::VALIGN_TOP | MapData::HALIGN_CENTER, 15);
903 d->setPriority(w < _route->currentIndex() ? 9000 : 12000);
905 } // of second waypoint iteration
908 void MapWidget::drawFlightHistory()
910 if (_flightHistoryPath.empty())
913 // first pass, draw the actual lines
916 glColor4f(0.0, 0.0, 1.0, 0.7);
918 glBegin(GL_LINE_STRIP);
919 for (unsigned int i=0; i<_flightHistoryPath.size(); ++i) {
920 SGVec2d p = project(_flightHistoryPath[i]);
921 glVertex2d(p.x(), p.y());
928 * Round a SGGeod to an arbitrary precision.
929 * For example, passing precision of 0.5 will round to the nearest 0.5 of
930 * a degree in both lat and lon - passing in 3.0 rounds to the nearest 3 degree
931 * multiple, and so on.
933 static SGGeod roundGeod(double precision, const SGGeod& g)
935 double lon = SGMiscd::round(g.getLongitudeDeg() / precision);
936 double lat = SGMiscd::round(g.getLatitudeDeg() / precision);
938 return SGGeod::fromDeg(lon * precision, lat * precision);
941 bool MapWidget::drawLineClipped(const SGVec2d& a, const SGVec2d& b)
943 double minX = SGMiscd::min(a.x(), b.x()),
944 minY = SGMiscd::min(a.y(), b.y()),
945 maxX = SGMiscd::max(a.x(), b.x()),
946 maxY = SGMiscd::max(a.y(), b.y());
948 int hh = _height >> 1, hw = _width >> 1;
950 if ((maxX < -hw) || (minX > hw) || (minY > hh) || (maxY < -hh)) {
954 glVertex2dv(a.data());
955 glVertex2dv(b.data());
959 SGVec2d MapWidget::gridPoint(int ix, int iy)
961 int key = (ix + 0x7fff) | ((iy + 0x7fff) << 16);
962 GridPointCache::iterator it = _gridCache.find(key);
963 if (it != _gridCache.end()) {
967 SGGeod gp = SGGeod::fromDeg(
968 _gridCenter.getLongitudeDeg() + ix * _gridSpacing,
969 _gridCenter.getLatitudeDeg() + iy * _gridSpacing);
971 SGVec2d proj = project(gp);
972 _gridCache[key] = proj;
976 void MapWidget::drawLatLonGrid()
978 // Larger grid spacing when zoomed out, to prevent clutter
979 if (_cachedZoom < SHOW_DETAIL_ZOOM) {
985 _gridCenter = roundGeod(_gridSpacing, _projectionCenter);
991 glColor4f(0.8, 0.8, 0.8, 0.4);
999 for (int x = -ix; x < ix; ++x) {
1000 didDraw |= drawLineClipped(gridPoint(x, -iy), gridPoint(x+1, -iy));
1001 didDraw |= drawLineClipped(gridPoint(x, iy), gridPoint(x+1, iy));
1002 didDraw |= drawLineClipped(gridPoint(x, -iy), gridPoint(x, -iy + 1));
1003 didDraw |= drawLineClipped(gridPoint(x, iy), gridPoint(x, iy - 1));
1006 for (int y = -iy; y < iy; ++y) {
1007 didDraw |= drawLineClipped(gridPoint(-ix, y), gridPoint(-ix, y+1));
1008 didDraw |= drawLineClipped(gridPoint(-ix, y), gridPoint(-ix + 1, y));
1009 didDraw |= drawLineClipped(gridPoint(ix, y), gridPoint(ix, y+1));
1010 didDraw |= drawLineClipped(gridPoint(ix, y), gridPoint(ix - 1, y));
1013 if (ix > (90/_gridSpacing)-1) {
1021 void MapWidget::drawGPSData()
1023 std::string gpsMode = _gps->getStringValue("mode");
1025 SGGeod wp0Geod = SGGeod::fromDeg(
1026 _gps->getDoubleValue("wp/wp[0]/longitude-deg"),
1027 _gps->getDoubleValue("wp/wp[0]/latitude-deg"));
1029 SGGeod wp1Geod = SGGeod::fromDeg(
1030 _gps->getDoubleValue("wp/wp[1]/longitude-deg"),
1031 _gps->getDoubleValue("wp/wp[1]/latitude-deg"));
1034 double gpsTrackDeg = _gps->getDoubleValue("indicated-track-true-deg");
1035 double gpsSpeed = _gps->getDoubleValue("indicated-ground-speed-kt");
1038 if (gpsSpeed > 3.0) { // only draw track line if valid
1040 SGGeodesy::direct(_aircraft, gpsTrackDeg, _drawRangeNm * SG_NM_TO_METER, trackRadial, az2);
1042 glColor4f(1.0, 1.0, 0.0, 1.0);
1043 glEnable(GL_LINE_STIPPLE);
1044 glLineStipple(1, 0x00FF);
1045 drawLine(project(_aircraft), project(trackRadial));
1046 glDisable(GL_LINE_STIPPLE);
1049 if (gpsMode == "dto") {
1050 SGVec2d wp0Pos = project(wp0Geod);
1051 SGVec2d wp1Pos = project(wp1Geod);
1053 glColor4f(1.0, 0.0, 1.0, 1.0);
1054 drawLine(wp0Pos, wp1Pos);
1058 if (_gps->getBoolValue("scratch/valid")) {
1059 // draw scratch data
1064 void MapWidget::drawPositioned()
1066 for (unsigned int i=0; i<_itemsToDraw.size(); ++i) {
1067 FGPositionedRef p = _itemsToDraw[i];
1068 switch (p->type()) {
1069 case FGPositioned::AIRPORT:
1070 drawAirport((FGAirport*) p.get());
1072 case FGPositioned::NDB:
1073 drawNDB(false, (FGNavRecord*) p.get());
1075 case FGPositioned::VOR:
1076 drawVOR(false, (FGNavRecord*) p.get());
1078 case FGPositioned::FIX:
1079 drawFix((FGFix*) p.get());
1081 case FGPositioned::TOWN:
1082 case FGPositioned::CITY:
1083 case FGPositioned::COUNTRY:
1088 SG_LOG(SG_GENERAL, SG_WARN, "unhandled type in MapWidget::drawPositioned");
1089 } // of positioned type switch
1090 } // of items to draw iteration
1093 void MapWidget::drawNDB(bool tuned, FGNavRecord* ndb)
1095 SGVec2d pos = project(ndb->geod());
1098 glColor3f(0.0, 1.0, 1.0);
1100 glColor3f(0.0, 0.0, 0.0);
1103 glEnable(GL_LINE_STIPPLE);
1104 glLineStipple(1, 0x00FF);
1105 circleAt(pos, 20, 6);
1106 circleAt(pos, 20, 10);
1107 glDisable(GL_LINE_STIPPLE);
1109 if (validDataForKey(ndb)) {
1110 setAnchorForKey(ndb, pos);
1115 ::snprintf(buffer, 1024, "%s\n%s %3.0fKhz",
1116 ndb->name().c_str(), ndb->ident().c_str(),ndb->get_freq()/100.0);
1118 MapData* d = createDataForKey(ndb);
1120 d->setLabel(ndb->ident());
1122 d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 10);
1127 void MapWidget::drawVOR(bool tuned, FGNavRecord* vor)
1129 SGVec2d pos = project(vor->geod());
1131 glColor3f(0.0, 1.0, 1.0);
1133 glColor3f(0.0, 0.0, 1.0);
1136 circleAt(pos, 6, 9);
1137 circleAt(pos, 8, 1);
1142 if (validDataForKey(vor)) {
1143 setAnchorForKey(vor, pos);
1148 ::snprintf(buffer, 1024, "%s\n%s %6.3fMhz",
1149 vor->name().c_str(), vor->ident().c_str(),
1150 vor->get_freq() / 100.0);
1152 MapData* d = createDataForKey(vor);
1154 d->setLabel(vor->ident());
1155 d->setPriority(tuned ? 10000 : 100);
1156 d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 12);
1160 void MapWidget::drawFix(FGFix* fix)
1162 SGVec2d pos = project(fix->geod());
1163 glColor3f(0.0, 0.0, 0.0);
1164 circleAt(pos, 3, 6);
1166 if (_cachedZoom > SHOW_DETAIL_ZOOM) {
1167 return; // hide fix labels beyond a certain zoom level
1170 if (validDataForKey(fix)) {
1171 setAnchorForKey(fix, pos);
1175 MapData* d = createDataForKey(fix);
1176 d->setLabel(fix->ident());
1178 d->setOffset(MapData::VALIGN_CENTER | MapData::HALIGN_LEFT, 10);
1182 void MapWidget::drawNavRadio(SGPropertyNode_ptr radio)
1184 if (!radio || radio->getBoolValue("slaved-to-gps", false)
1185 || !radio->getBoolValue("in-range", false)) {
1189 if (radio->getBoolValue("nav-loc", false)) {
1190 drawTunedLocalizer(radio);
1193 // identify the tuned station - unfortunately we don't get lat/lon directly,
1194 // need to do the frequency search again
1195 double mhz = radio->getDoubleValue("frequencies/selected-mhz", 0.0);
1197 FGNavRecord* nav = FGNavList::findByFreq(mhz, _aircraft,
1198 FGNavList::navFilter());
1199 if (!nav || (nav->ident() != radio->getStringValue("nav-id"))) {
1200 // mismatch between navradio selection logic and ours!
1207 SGVec2d pos = project(nav->geod());
1210 double trueRadial = radio->getDoubleValue("radials/target-radial-deg");
1211 SGGeodesy::direct(nav->geod(), trueRadial, nav->get_range() * SG_NM_TO_METER, range, az2);
1212 SGVec2d prange = project(range);
1214 SGVec2d norm = normalize(prange - pos);
1215 SGVec2d perp(norm.y(), -norm.x());
1217 circleAt(pos, 64, length(prange - pos));
1218 drawLine(pos, prange);
1220 // draw to/from arrows
1221 SGVec2d midPoint = (pos + prange) * 0.5;
1222 if (radio->getBoolValue("from-flag")) {
1228 SGVec2d arrowB = midPoint - (norm * sz) + (perp * sz);
1229 SGVec2d arrowC = midPoint - (norm * sz) - (perp * sz);
1230 drawLine(midPoint, arrowB);
1231 drawLine(arrowB, arrowC);
1232 drawLine(arrowC, midPoint);
1234 drawLine(pos, (2 * pos) - prange); // reciprocal radial
1237 void MapWidget::drawTunedLocalizer(SGPropertyNode_ptr radio)
1239 double mhz = radio->getDoubleValue("frequencies/selected-mhz", 0.0);
1240 FGNavRecord* loc = FGNavList::findByFreq(mhz, _aircraft, FGNavList::locFilter());
1241 if (!loc || (loc->ident() != radio->getStringValue("nav-id"))) {
1242 // mismatch between navradio selection logic and ours!
1246 if (loc->runway()) {
1247 drawILS(true, loc->runway());
1251 void MapWidget::drawPOI(FGPositioned* poi)
1253 SGVec2d pos = project(poi->geod());
1254 glColor3f(1.0, 1.0, 0.0);
1258 if (poi->type() == FGPositioned::CITY) {
1260 glColor3f(0.0, 1.0, 0.0);
1261 } else if (poi->type() == FGPositioned::TOWN) {
1263 glColor3f(0.2, 1.0, 0.0);
1266 circleAt(pos, 4, radius);
1268 if (validDataForKey(poi)) {
1269 setAnchorForKey(poi, pos);
1274 ::snprintf(buffer, 1024, "%s",
1275 poi->name().c_str());
1277 MapData* d = createDataForKey(poi);
1278 d->setPriority(200);
1279 d->setLabel(poi->ident());
1281 d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 10);
1286 void MapWidget::drawObstacle(FGPositioned* obs)
1288 SGVec2d pos = project(obs->geod());
1289 glColor3f(0.0, 0.0, 0.0);
1291 drawLine(pos, pos + SGVec2d());
1295 void MapWidget::drawAirport(FGAirport* apt)
1297 // draw tower location
1298 SGVec2d towerPos = project(apt->getTowerLocation());
1300 if (_cachedZoom <= SHOW_DETAIL_ZOOM) {
1301 glColor3f(1.0, 1.0, 1.0);
1304 drawLine(towerPos + SGVec2d(3, 0), towerPos + SGVec2d(3, 10));
1305 drawLine(towerPos + SGVec2d(-3, 0), towerPos + SGVec2d(-3, 10));
1306 drawLine(towerPos + SGVec2d(-6, 20), towerPos + SGVec2d(-3, 10));
1307 drawLine(towerPos + SGVec2d(6, 20), towerPos + SGVec2d(3, 10));
1308 drawLine(towerPos + SGVec2d(-6, 20), towerPos + SGVec2d(6, 20));
1311 if (validDataForKey(apt)) {
1312 setAnchorForKey(apt, towerPos);
1315 ::snprintf(buffer, 1024, "%s\n%s",
1316 apt->ident().c_str(), apt->name().c_str());
1318 MapData* d = createDataForKey(apt);
1320 d->setLabel(apt->ident());
1321 d->setPriority(100 + scoreAirportRunways(apt));
1322 d->setOffset(MapData::VALIGN_TOP | MapData::HALIGN_CENTER, 6);
1323 d->setAnchor(towerPos);
1326 if (_cachedZoom > SHOW_DETAIL_ZOOM) {
1330 FGRunwayList runways(apt->getRunwaysWithoutReciprocals());
1332 for (unsigned int r=0; r<runways.size(); ++r) {
1333 drawRunwayPre(runways[r]);
1336 for (unsigned int r=0; r<runways.size(); ++r) {
1337 FGRunway* rwy = runways[r];
1341 drawILS(false, rwy);
1344 if (rwy->reciprocalRunway()) {
1345 FGRunway* recip = rwy->reciprocalRunway();
1347 drawILS(false, recip);
1352 for (unsigned int r=0; r<apt->numHelipads(); ++r) {
1353 FGHelipad* hp = apt->getHelipadByIndex(r);
1355 } // of runway iteration
1359 int MapWidget::scoreAirportRunways(FGAirport* apt)
1361 bool needHardSurface = _root->getBoolValue("hard-surfaced-airports", true);
1362 double minLength = _root->getDoubleValue("min-runway-length-ft", 2000.0);
1364 FGRunwayList runways(apt->getRunwaysWithoutReciprocals());
1367 for (unsigned int r=0; r<runways.size(); ++r) {
1368 FGRunway* rwy = runways[r];
1369 if (needHardSurface && !rwy->isHardSurface()) {
1373 if (rwy->lengthFt() < minLength) {
1377 int scoreLength = SGMiscd::roundToInt(rwy->lengthFt() / 200.0);
1378 score += scoreLength;
1379 } // of runways iteration
1384 void MapWidget::drawRunwayPre(FGRunway* rwy)
1386 SGVec2d p1 = project(rwy->begin());
1387 SGVec2d p2 = project(rwy->end());
1390 glColor3f(1.0, 0.0, 1.0);
1394 void MapWidget::drawRunway(FGRunway* rwy)
1397 // optionally show active, stopway, etc
1398 // in legend, show published heading and length
1399 // and threshold elevation
1401 SGVec2d p1 = project(rwy->begin());
1402 SGVec2d p2 = project(rwy->end());
1404 glColor3f(1.0, 1.0, 1.0);
1405 SGVec2d inset = normalize(p2 - p1) * 2;
1407 drawLine(p1 + inset, p2 - inset);
1409 if (validDataForKey(rwy)) {
1410 setAnchorForKey(rwy, (p1 + p2) * 0.5);
1415 ::snprintf(buffer, 1024, "%s/%s\n%03d/%03d\n%.0f'",
1416 rwy->ident().c_str(),
1417 rwy->reciprocalRunway()->ident().c_str(),
1418 displayHeading(rwy->headingDeg()),
1419 displayHeading(rwy->reciprocalRunway()->headingDeg()),
1422 MapData* d = createDataForKey(rwy);
1424 d->setLabel(rwy->ident() + "/" + rwy->reciprocalRunway()->ident());
1426 d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 12);
1427 d->setAnchor((p1 + p2) * 0.5);
1430 void MapWidget::drawILS(bool tuned, FGRunway* rwy)
1432 // arrow, tip centered on the landing threshold
1433 // using LOC transmitter position would be more accurate, but
1434 // is visually cluttered
1435 // arrow width is based upon the computed localizer width
1437 FGNavRecord* loc = rwy->ILS();
1438 double halfBeamWidth = loc->localizerWidth() * 0.5;
1439 SGVec2d t = project(rwy->threshold());
1441 double rangeM = loc->get_range() * SG_NM_TO_METER;
1442 double radial = loc->get_multiuse();
1443 SG_NORMALIZE_RANGE(radial, 0.0, 360.0);
1446 // compute the three end points at the widge end of the arrow
1447 SGGeodesy::direct(loc->geod(), radial, -rangeM, locEnd, az2);
1448 SGVec2d endCentre = project(locEnd);
1450 SGGeodesy::direct(loc->geod(), radial + halfBeamWidth, -rangeM * 1.1, locEnd, az2);
1451 SGVec2d endR = project(locEnd);
1453 SGGeodesy::direct(loc->geod(), radial - halfBeamWidth, -rangeM * 1.1, locEnd, az2);
1454 SGVec2d endL = project(locEnd);
1456 // outline two triangles
1459 glColor3f(0.0, 1.0, 1.0);
1461 glColor3f(0.0, 0.0, 1.0);
1464 glBegin(GL_LINE_LOOP);
1465 glVertex2dv(t.data());
1466 glVertex2dv(endCentre.data());
1467 glVertex2dv(endL.data());
1469 glBegin(GL_LINE_LOOP);
1470 glVertex2dv(t.data());
1471 glVertex2dv(endCentre.data());
1472 glVertex2dv(endR.data());
1475 if (validDataForKey(loc)) {
1476 setAnchorForKey(loc, endR);
1481 ::snprintf(buffer, 1024, "%s\n%s\n%03d - %3.2fMHz",
1482 loc->ident().c_str(), loc->name().c_str(),
1483 displayHeading(radial),
1484 loc->get_freq()/100.0);
1486 MapData* d = createDataForKey(loc);
1488 d->setLabel(loc->ident());
1490 d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 10);
1494 void MapWidget::drawTraffic()
1496 AIDrawVec::const_iterator it;
1497 for (it = _aiDrawVec.begin(); it != _aiDrawVec.end(); ++it) {
1502 void MapWidget::drawHelipad(FGHelipad* hp)
1504 SGVec2d pos = project(hp->geod());
1506 glColor3f(1.0, 0.0, 1.0);
1507 circleAt(pos, 16, 5.0);
1509 if (validDataForKey(hp)) {
1510 setAnchorForKey(hp, pos);
1515 ::snprintf(buffer, 1024, "%s\n%03d\n%.0f'",
1516 hp->ident().c_str(),
1517 displayHeading(hp->headingDeg()),
1520 MapData* d = createDataForKey(hp);
1522 d->setLabel(hp->ident());
1524 d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 8);
1528 void MapWidget::drawAI(const DrawAIObject& dai)
1530 SGVec2d p = project(dai.pos);
1533 glColor3f(0.0, 0.0, 0.5);
1536 glColor3f(0.0, 0.0, 0.0);
1539 circleAt(p, 4, 6.0); // black diamond
1541 // draw heading vector
1542 if (dai.speedKts > 1) {
1544 const double dt = 15.0 / (3600.0); // 15 seconds look-ahead
1545 double distanceM = dai.speedKts * SG_NM_TO_METER * dt;
1546 SGGeod advance = SGGeodesy::direct(dai.pos, dai.heading, distanceM);
1547 drawLine(p, project(advance));
1550 MapData* d = getOrCreateDataForKey((void*) dai.model);
1551 d->setText(dai.legend);
1552 d->setLabel(dai.label);
1553 d->setPriority(dai.speedKts > 5 ? 60 : 10); // low priority for parked aircraft
1554 d->setOffset(MapData::VALIGN_CENTER | MapData::HALIGN_LEFT, 10);
1558 SGVec2d MapWidget::project(const SGGeod& geod) const
1561 double r = earth_radius_lat(geod.getLatitudeRad());
1563 switch (_projection) {
1564 case PROJECTION_SAMSON_FLAMSTEED:
1566 // Sanson-Flamsteed projection, relative to the projection center
1567 double lonDiff = geod.getLongitudeRad() - _projectionCenter.getLongitudeRad(),
1568 latDiff = geod.getLatitudeRad() - _projectionCenter.getLatitudeRad();
1570 p = SGVec2d(cos(geod.getLatitudeRad()) * lonDiff, latDiff) * r * currentScale();
1574 case PROJECTION_AZIMUTHAL_EQUIDISTANT:
1576 // Azimuthal Equidistant projection, relative to the projection center
1577 // http://www.globmaritime.com/martech/marine-navigation/general-concepts/626-azimuthal-equidistant-projection
1578 double ref_lat = _projectionCenter.getLatitudeRad(),
1579 ref_lon = _projectionCenter.getLongitudeRad(),
1580 lat = geod.getLatitudeRad(),
1581 lon = geod.getLongitudeRad(),
1582 lonDiff = lon - ref_lon;
1584 double c = acos( sin(ref_lat) * sin(lat) + cos(ref_lat) * cos(lat) * cos(lonDiff) );
1586 // angular distance from center is 0
1587 p= SGVec2d(0.0, 0.0);
1591 double k = c / sin(c);
1593 if (ref_lat == (90 * SG_DEGREES_TO_RADIANS))
1595 x = (SGD_PI / 2 - lat) * sin(lonDiff);
1596 y = -(SGD_PI / 2 - lat) * cos(lonDiff);
1598 else if (ref_lat == -(90 * SG_DEGREES_TO_RADIANS))
1600 x = (SGD_PI / 2 + lat) * sin(lonDiff);
1601 y = (SGD_PI / 2 + lat) * cos(lonDiff);
1605 x = k * cos(lat) * sin(lonDiff);
1606 y = k * ( cos(ref_lat) * sin(lat) - sin(ref_lat) * cos(lat) * cos(lonDiff) );
1608 p = SGVec2d(x, y) * r * currentScale();
1613 case PROJECTION_ORTHO_AZIMUTH:
1615 // http://mathworld.wolfram.com/OrthographicProjection.html
1616 double cosTheta = cos(geod.getLatitudeRad());
1617 double sinDLambda = sin(geod.getLongitudeRad() - _projectionCenter.getLongitudeRad());
1618 double cosDLambda = cos(geod.getLongitudeRad() - _projectionCenter.getLongitudeRad());
1619 double sinTheta1 = sin(_projectionCenter.getLatitudeRad());
1620 double sinTheta = sin(geod.getLatitudeRad());
1621 double cosTheta1 = cos(_projectionCenter.getLatitudeRad());
1623 p = SGVec2d(cosTheta * sinDLambda,
1624 (cosTheta1 * sinTheta) - (sinTheta1 * cosTheta * cosDLambda)) * r * currentScale();
1628 case PROJECTION_SPHERICAL:
1630 SGVec3d cartCenter = SGVec3d::fromGeod(_projectionCenter);
1631 SGVec3d cartPt = SGVec3d::fromGeod(geod) - cartCenter;
1633 // rotate relative to projection center
1634 SGQuatd orient = SGQuatd::fromLonLat(_projectionCenter);
1635 cartPt = orient.rotateBack(cartPt);
1636 return SGVec2d(cartPt.y(), cartPt.x()) * currentScale();
1639 } // of projection mode switch
1642 // rotate as necessary
1643 double cost = cos(_upHeading * SG_DEGREES_TO_RADIANS),
1644 sint = sin(_upHeading * SG_DEGREES_TO_RADIANS);
1645 double rx = cost * p.x() - sint * p.y();
1646 double ry = sint * p.x() + cost * p.y();
1647 return SGVec2d(rx, ry);
1650 SGGeod MapWidget::unproject(const SGVec2d& p) const
1652 // unrotate, if necessary
1653 double cost = cos(-_upHeading * SG_DEGREES_TO_RADIANS),
1654 sint = sin(-_upHeading * SG_DEGREES_TO_RADIANS);
1655 SGVec2d ur(cost * p.x() - sint * p.y(),
1656 sint * p.x() + cost * p.y());
1660 switch (_projection) {
1661 case PROJECTION_SAMSON_FLAMSTEED:
1663 double r = earth_radius_lat(_projectionCenter.getLatitudeRad());
1664 SGVec2d unscaled = ur * (1.0 / (currentScale() * r));
1665 double lat = unscaled.y() + _projectionCenter.getLatitudeRad();
1666 double lon = (unscaled.x() / cos(lat)) + _projectionCenter.getLongitudeRad();
1667 return SGGeod::fromRad(lon, lat);
1670 case PROJECTION_AZIMUTHAL_EQUIDISTANT:
1672 double r = earth_radius_lat(_projectionCenter.getLatitudeRad());
1673 SGVec2d unscaled = ur * (1.0 / currentScale());
1676 ref_lat = _projectionCenter.getLatitudeRad(),
1677 ref_lon = _projectionCenter.getLongitudeRad(),
1678 rho = sqrt(unscaled.x() * unscaled.x() + unscaled.y() * unscaled.y()),
1688 lat = asin( cos(c) * sin(ref_lat) + (unscaled.y() * sin(c) * cos(ref_lat)) / rho);
1690 if (ref_lat == (90 * SG_DEGREES_TO_RADIANS))
1692 lon = ref_lon + atan(-unscaled.x()/unscaled.y());
1694 else if (ref_lat == -(90 * SG_DEGREES_TO_RADIANS))
1696 lon = ref_lon + atan(unscaled.x()/unscaled.y());
1700 lon = ref_lon + atan(unscaled.x() * sin(c) / (rho * cos(ref_lat) * cos(c) - unscaled.y() * sin(ref_lat) * sin(c)));
1704 return SGGeod::fromRad(lon, lat);
1707 case PROJECTION_ORTHO_AZIMUTH:
1709 double r = earth_radius_lat(_projectionCenter.getLatitudeRad());
1710 SGVec2d unscaled = ur * (1.0 / (currentScale() * r));
1712 double phi = length(p);
1713 double c = asin(phi);
1714 double sinTheta1 = sin(_projectionCenter.getLatitudeRad());
1715 double cosTheta1 = cos(_projectionCenter.getLatitudeRad());
1717 double lat = asin(cos(c) * sinTheta1 + ((unscaled.y() * sin(c) * cosTheta1) / phi));
1718 double lon = _projectionCenter.getLongitudeRad() +
1719 atan((unscaled.x()* sin(c)) / (phi * cosTheta1 * cos(c) - unscaled.y() * sinTheta1 * sin(c)));
1720 return SGGeod::fromRad(lon, lat);
1723 case PROJECTION_SPHERICAL:
1725 SGVec2d unscaled = ur * (1.0 / currentScale());
1726 SGQuatd orient = SGQuatd::fromLonLat(_projectionCenter);
1727 SGVec3d cartCenter = SGVec3d::fromGeod(_projectionCenter);
1728 SGVec3d cartPt = orient.rotate(SGVec3d(unscaled.x(), unscaled.y(), 0.0));
1729 return SGGeod::fromCart(cartPt + cartCenter);
1733 throw sg_exception("MapWidget::unproject(): requested unknown projection");
1734 } // of projection mode switch
1737 double MapWidget::currentScale() const
1739 return 1.0 / pow(2.0, _cachedZoom);
1742 void MapWidget::circleAt(const SGVec2d& center, int nSides, double r)
1744 glBegin(GL_LINE_LOOP);
1745 double advance = (SGD_PI * 2) / nSides;
1746 glVertex2d(center.x() +r, center.y());
1748 for (int i=1; i<nSides; ++i) {
1749 glVertex2d(center.x() + (cos(t) * r), center.y() + (sin(t) * r));
1755 void MapWidget::squareAt(const SGVec2d& center, double r)
1757 glBegin(GL_LINE_LOOP);
1758 glVertex2d(center.x() + r, center.y() + r);
1759 glVertex2d(center.x() + r, center.y() - r);
1760 glVertex2d(center.x() - r, center.y() - r);
1761 glVertex2d(center.x() - r, center.y() + r);
1765 void MapWidget::circleAtAlt(const SGVec2d& center, int nSides, double r, double r2)
1767 glBegin(GL_LINE_LOOP);
1768 double advance = (SGD_PI * 2) / nSides;
1769 glVertex2d(center.x(), center.y() + r);
1771 for (int i=1; i<nSides; ++i) {
1772 double rr = (i%2 == 0) ? r : r2;
1773 glVertex2d(center.x() + (sin(t) * rr), center.y() + (cos(t) * rr));
1779 void MapWidget::drawLine(const SGVec2d& p1, const SGVec2d& p2)
1782 glVertex2dv(p1.data());
1783 glVertex2dv(p2.data());
1787 void MapWidget::drawLegendBox(const SGVec2d& pos, const std::string& t)
1789 std::vector<std::string> lines(simgear::strutils::split(t, "\n"));
1790 const int LINE_LEADING = 4;
1791 const int MARGIN = 4;
1794 int maxWidth = -1, totalHeight = 0;
1795 int lineHeight = legendFont.getStringHeight();
1797 for (unsigned int ln=0; ln<lines.size(); ++ln) {
1798 totalHeight += lineHeight;
1800 totalHeight += LINE_LEADING;
1803 int lw = legendFont.getStringWidth(lines[ln].c_str());
1804 maxWidth = std::max(maxWidth, lw);
1805 } // of line measurement
1808 return; // all lines are empty, don't draw
1811 totalHeight += MARGIN * 2;
1816 box.min[1] = -totalHeight;
1817 box.max[0] = maxWidth + (MARGIN * 2);
1820 box.draw (pos.x(), pos.y(), PUSTYLE_DROPSHADOW, colour, FALSE, border);
1823 int xPos = pos.x() + MARGIN;
1824 int yPos = pos.y() - (lineHeight + MARGIN);
1825 glColor3f(0.8, 0.8, 0.8);
1827 for (unsigned int ln=0; ln<lines.size(); ++ln) {
1828 legendFont.drawString(lines[ln].c_str(), xPos, yPos);
1829 yPos -= lineHeight + LINE_LEADING;
1833 void MapWidget::drawData()
1835 std::sort(_dataQueue.begin(), _dataQueue.end(), MapData::order);
1837 int hw = _width >> 1,
1839 puBox visBox(makePuBox(-hw, -hh, _width, _height));
1843 std::vector<MapData*> drawQueue;
1845 bool drawData = _root->getBoolValue("draw-data");
1846 const int MAX_DRAW_DATA = 25;
1847 const int MAX_DRAW = 50;
1849 for (; (d < _dataQueue.size()) && (drawn < MAX_DRAW); ++d) {
1850 MapData* md = _dataQueue[d];
1851 md->setDataVisible(drawData);
1853 if (md->isClipped(visBox)) {
1857 if (md->overlaps(drawQueue)) {
1858 if (drawData) { // overlapped with data, let's try just the label
1859 md->setDataVisible(false);
1860 if (md->overlaps(drawQueue)) {
1866 } // of overlaps case
1868 drawQueue.push_back(md);
1870 if (drawData && (drawn >= MAX_DRAW_DATA)) {
1875 // draw lowest-priority first, so higher-priorty items appear on top
1876 std::vector<MapData*>::reverse_iterator r;
1877 for (r = drawQueue.rbegin(); r!= drawQueue.rend(); ++r) {
1882 KeyDataMap::iterator it = _mapData.begin();
1883 for (; it != _mapData.end(); ) {
1885 if (it->second->isExpired()) {
1887 KeyDataMap::iterator cur = it++;
1888 _mapData.erase(cur);
1892 } // of expiry iteration
1895 bool MapWidget::validDataForKey(void* key)
1897 KeyDataMap::iterator it = _mapData.find(key);
1898 if (it == _mapData.end()) {
1899 return false; // no valid data for the key!
1902 it->second->resetAge(); // mark data as valid this frame
1903 _dataQueue.push_back(it->second);
1907 void MapWidget::setAnchorForKey(void* key, const SGVec2d& anchor)
1909 KeyDataMap::iterator it = _mapData.find(key);
1910 if (it == _mapData.end()) {
1911 throw sg_exception("no valid data for key!");
1914 it->second->setAnchor(anchor);
1917 MapData* MapWidget::getOrCreateDataForKey(void* key)
1919 KeyDataMap::iterator it = _mapData.find(key);
1920 if (it == _mapData.end()) {
1921 return createDataForKey(key);
1924 it->second->resetAge(); // mark data as valid this frame
1925 _dataQueue.push_back(it->second);
1929 MapData* MapWidget::createDataForKey(void* key)
1931 KeyDataMap::iterator it = _mapData.find(key);
1932 if (it != _mapData.end()) {
1933 throw sg_exception("duplicate data requested for key!");
1936 MapData* d = new MapData(0);
1938 _dataQueue.push_back(d);
1943 void MapWidget::clearData()
1945 KeyDataMap::iterator it = _mapData.begin();
1946 for (; it != _mapData.end(); ++it) {
1953 int MapWidget::displayHeading(double h) const
1955 if (_magneticHeadings) {
1956 h -= _magVar->get_magvar() * SG_RADIANS_TO_DEGREES;
1959 SG_NORMALIZE_RANGE(h, 0.0, 360.0);
1960 return SGMiscd::roundToInt(h);
1963 MapWidget::DrawAIObject::DrawAIObject(SGPropertyNode* m, const SGGeod& g) :
1969 std::string name(model->getNameString());
1970 heading = model->getDoubleValue("orientation/true-heading-deg");
1972 if ((name == "aircraft") || (name == "multiplayer") ||
1973 (name == "wingman") || (name == "tanker"))
1975 speedKts = static_cast<int>(model->getDoubleValue("velocities/true-airspeed-kt"));
1976 label = model->getStringValue("callsign", "<>");
1978 // try to access the flight-plan of the aircraft. There are several layers
1979 // of potential NULL-ness here, so we have to be defensive at each stage.
1980 std::string originICAO, destinationICAO;
1981 FGAIManager* aiManager = globals->get_subsystem<FGAIManager>();
1982 FGAIBasePtr aircraft = aiManager ? aiManager->getObjectFromProperty(model) : NULL;
1984 FGAIAircraft* p = static_cast<FGAIAircraft*>(aircraft.get());
1985 if (p->GetFlightPlan()) {
1986 if (p->GetFlightPlan()->departureAirport()) {
1987 originICAO = p->GetFlightPlan()->departureAirport()->ident();
1990 if (p->GetFlightPlan()->arrivalAirport()) {
1991 destinationICAO = p->GetFlightPlan()->arrivalAirport()->ident();
1993 } // of flight-plan exists
1994 } // of check for AIBase-derived instance
1996 // draw callsign / altitude / speed
1997 int altFt50 = static_cast<int>(pos.getElevationFt() / 50.0) * 50;
1998 std::ostringstream ss;
1999 ss << model->getStringValue("callsign", "<>");
2001 ss << "\n" << altFt50 << "' " << speedKts << "kts";
2004 if (!originICAO.empty() || ! destinationICAO.empty()) {
2005 ss << "\n" << originICAO << " -> " << destinationICAO;
2009 } else if ((name == "ship") || (name == "carrier") || (name == "escort")) {
2011 speedKts = static_cast<int>(model->getDoubleValue("velocities/speed-kts"));
2012 label = model->getStringValue("name", "<>");
2015 ::snprintf(buffer, 1024, "%s\n%dkts",
2016 model->getStringValue("name", "<>"),