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/AIFlightPlan.hxx>
32 const char* RULER_LEGEND_KEY = "ruler-legend";
34 /* equatorial and polar earth radius */
35 const float rec = 6378137; // earth radius, equator (?)
36 const float rpol = 6356752.314f; // earth radius, polar (?)
38 /************************************************************************
39 some trigonometric helper functions
40 (translated more or less directly from Alexei Novikovs perl original)
41 *************************************************************************/
43 //Returns Earth radius at a given latitude (Ellipsoide equation with two equal axis)
44 static float earth_radius_lat( float lat )
46 double a = cos(lat)/rec;
47 double b = sin(lat)/rpol;
48 return 1.0f / sqrt( a * a + b * b );
51 ///////////////////////////////////////////////////////////////////////////
53 static puBox makePuBox(int x, int y, int w, int h)
63 static bool puBoxIntersect(const puBox& a, const puBox& b)
65 int x0 = SG_MAX2(a.min[0], b.min[0]);
66 int y0 = SG_MAX2(a.min[1], b.min[1]);
67 int x1 = SG_MIN2(a.max[0], b.max[0]);
68 int y1 = SG_MIN2(a.max[1], b.max[1]);
70 return (x0 <= x1) && (y0 <= y1);
74 typedef std::vector<MapData*> MapDataVec;
79 static const int HALIGN_LEFT = 1;
80 static const int HALIGN_CENTER = 2;
81 static const int HALIGN_RIGHT = 3;
83 static const int VALIGN_TOP = 1 << 4;
84 static const int VALIGN_CENTER = 2 << 4;
85 static const int VALIGN_BOTTOM = 3 << 4;
87 MapData(int priority) :
93 _offsetDir(HALIGN_LEFT | VALIGN_CENTER),
99 void setLabel(const std::string& label)
101 if (label == _label) {
102 return; // common case, and saves invalidation
109 void setText(const std::string &text)
111 if (_rawText == text) {
112 return; // common case, and saves invalidation
119 void setDataVisible(bool vis) {
120 if (vis == _dataVisible) {
124 if (_rawText.empty()) {
132 static void setFont(puFont f)
135 _fontHeight = f.getStringHeight();
136 _fontDescender = f.getStringDescender();
139 static void setPalette(puColor* pal)
144 void setPriority(int pri)
150 { return _priority; }
152 void setAnchor(const SGVec2d& anchor)
157 void setOffset(int direction, int px)
159 if ((_offsetPx == px) && (_offsetDir == direction)) {
164 _offsetDir = direction;
168 bool isClipped(const puBox& vis) const
171 if ((_width < 1) || (_height < 1)) {
175 return !puBoxIntersect(vis, box());
178 bool overlaps(const MapDataVec& l) const
183 MapDataVec::const_iterator it;
184 for (it = l.begin(); it != l.end(); ++it) {
185 if (puBoxIntersect(b, (*it)->box())) {
188 } // of list iteration
197 _anchor.x() + _offset.x(),
198 _anchor.y() + _offset.y(),
202 void drawStringUtf8(std::string& utf8Str, double x, double y, puFont fnt)
204 fnt.drawString(simgear::strutils::utf8ToLatin1(utf8Str).c_str(), x, y);
211 int xx = _anchor.x() + _offset.x();
212 int yy = _anchor.y() + _offset.y();
215 puBox box(makePuBox(0,0,_width, _height));
217 box.draw(xx, yy, PUSTYLE_DROPSHADOW, _palette, FALSE, border);
220 int lineHeight = _fontHeight;
221 int xPos = xx + MARGIN;
222 int yPos = yy + _height - (lineHeight + MARGIN);
223 glColor3f(0.8, 0.8, 0.8);
225 for (unsigned int ln=0; ln<_lines.size(); ++ln) {
226 drawStringUtf8(_lines[ln], xPos, yPos, _font);
227 yPos -= lineHeight + LINE_LEADING;
230 glColor3f(0.8, 0.8, 0.8);
231 drawStringUtf8(_label, xx, yy + _fontDescender, _font);
245 bool isExpired() const
246 { return (_age > 100); }
248 static bool order(MapData* a, MapData* b)
250 return a->_priority > b->_priority;
253 void validate() const
273 void measureData() const
275 _lines = simgear::strutils::split(_rawText, "\n");
276 // measure text to find width and height
280 for (unsigned int ln=0; ln<_lines.size(); ++ln) {
281 _height += _fontHeight;
283 _height += LINE_LEADING;
286 int lw = _font.getStringWidth(_lines[ln].c_str());
287 _width = std::max(_width, lw);
288 } // of line measurement
290 if ((_width < 1) || (_height < 1)) {
295 _height += MARGIN * 2;
296 _width += MARGIN * 2;
299 void measureLabel() const
301 if (_label.empty()) {
302 _width = _height = -1;
306 _height = _fontHeight;
307 _width = _font.getStringWidth(_label.c_str());
310 void computeOffset() const
312 _dirtyOffset = false;
313 if ((_width <= 0) || (_height <= 0)) {
320 switch (_offsetDir & 0x0f) {
327 hOffset = -(_width>>1);
331 hOffset = -(_offsetPx + _width);
335 switch (_offsetDir & 0xf0) {
338 vOffset = -(_offsetPx + _height);
342 vOffset = -(_height>>1);
350 _offset = SGVec2d(hOffset, vOffset);
353 static const int LINE_LEADING = 3;
354 static const int MARGIN = 3;
356 mutable bool _dirtyText;
357 mutable bool _dirtyOffset;
359 std::string _rawText;
361 mutable std::vector<std::string> _lines;
363 mutable int _width, _height;
367 mutable SGVec2d _offset;
371 static puColor* _palette;
372 static int _fontHeight;
373 static int _fontDescender;
376 puFont MapData::_font;
377 puColor* MapData::_palette;
378 int MapData::_fontHeight = 0;
379 int MapData::_fontDescender = 0;
381 ///////////////////////////////////////////////////////////////////////////
383 const int MAX_ZOOM = 12;
384 const int SHOW_DETAIL_ZOOM = 8;
385 const int SHOW_DETAIL2_ZOOM = 5;
386 const int CURSOR_PAN_STEP = 32;
388 MapWidget::MapWidget(int x, int y, int maxX, int maxY) :
389 puObject(x,y,maxX, maxY)
391 _route = static_cast<FGRouteMgr*>(globals->get_subsystem("route-manager"));
392 _gps = fgGetNode("/instrumentation/gps");
397 _projection = PROJECTION_SAMSON_FLAMSTEED;
398 _magneticHeadings = false;
400 MapData::setFont(legendFont);
401 MapData::setPalette(colour);
403 _magVar = new SGMagVar();
406 MapWidget::~MapWidget()
412 void MapWidget::setProperty(SGPropertyNode_ptr prop)
415 int zoom = _root->getIntValue("zoom", -1);
417 _root->setIntValue("zoom", 6); // default zoom
420 // expose MAX_ZOOM to the UI
421 _root->setIntValue("max-zoom", MAX_ZOOM);
422 _root->setBoolValue("centre-on-aircraft", true);
423 _root->setBoolValue("draw-data", false);
424 _root->setBoolValue("draw-flight-history", false);
425 _root->setBoolValue("magnetic-headings", true);
428 void MapWidget::setSize(int w, int h)
430 puObject::setSize(w, h);
437 void MapWidget::doHit( int button, int updown, int x, int y )
439 puObject::doHit(button, updown, x, y);
440 if (updown == PU_DRAG) {
445 if (button == 3) { // mouse-wheel up
447 } else if (button == 4) { // mouse-wheel down
451 if (button != active_mouse_button) {
455 _hitLocation = SGVec2d(x - abox.min[0], y - abox.min[1]);
457 if (updown == PU_UP) {
458 puDeactivateWidget();
459 } else if (updown == PU_DOWN) {
460 puSetActiveWidget(this, x, y);
462 if (fgGetKeyModifiers() & KEYMOD_CTRL) {
463 _clickGeod = unproject(_hitLocation - SGVec2d(_width>>1, _height>>1));
468 void MapWidget::handlePan(int x, int y)
470 SGVec2d delta = SGVec2d(x, y) - _hitLocation;
472 _hitLocation = SGVec2d(x,y);
475 int MapWidget::checkKey (int key, int updown )
477 if ((updown == PU_UP) || !isVisible () || !isActive () || (window != puGetWindow())) {
485 pan(SGVec2d(0, -CURSOR_PAN_STEP));
489 pan(SGVec2d(0, CURSOR_PAN_STEP));
493 pan(SGVec2d(CURSOR_PAN_STEP, 0));
497 pan(SGVec2d(-CURSOR_PAN_STEP, 0));
516 void MapWidget::pan(const SGVec2d& delta)
519 _projectionCenter = unproject(-delta);
522 int MapWidget::zoom() const
524 int z = _root->getIntValue("zoom");
525 SG_CLAMP_RANGE(z, 0, MAX_ZOOM);
529 void MapWidget::zoomIn()
531 if (zoom() >= MAX_ZOOM) {
535 _root->setIntValue("zoom", zoom() + 1);
538 void MapWidget::zoomOut()
544 _root->setIntValue("zoom", zoom() - 1);
547 void MapWidget::draw(int dx, int dy)
549 _aircraft = globals->get_aircraft_position();
551 bool mag = _root->getBoolValue("magnetic-headings");
552 if (mag != _magneticHeadings) {
553 clearData(); // flush cached data text, since it often includes heading
554 _magneticHeadings = mag;
558 _root->setBoolValue("centre-on-aircraft", false);
561 else if (_root->getBoolValue("centre-on-aircraft")) {
562 _projectionCenter = _aircraft;
565 double julianDate = globals->get_time_params()->getJD();
566 _magVar->update(_projectionCenter, julianDate);
568 bool aircraftUp = _root->getBoolValue("aircraft-heading-up");
570 _upHeading = fgGetDouble("/orientation/heading-deg");
575 _cachedZoom = MAX_ZOOM - zoom();
576 SGGeod topLeft = unproject(SGVec2d(_width/2, _height/2));
577 // compute draw range, including a fudge factor for ILSs and other 'long'
579 _drawRangeNm = SGGeodesy::distanceNm(_projectionCenter, topLeft) + 10.0;
581 // drawing operations
582 GLint sx = (int) abox.min[0],
583 sy = (int) abox.min[1];
584 glScissor(dx + sx, dy + sy, _width, _height);
585 glEnable(GL_SCISSOR_TEST);
587 glMatrixMode(GL_MODELVIEW);
589 // cetere drawing about the widget center (which is also the
590 // projection centre)
591 glTranslated(dx + sx + (_width/2), dy + sy + (_height/2), 0.0);
596 int textHeight = legendFont.getStringHeight() + 5;
599 SGVec2d loc = project(_aircraft);
600 glColor3f(1.0, 1.0, 1.0);
601 drawLine(loc, SGVec2d(loc.x(), (_height / 2) - textHeight));
604 if (_magneticHeadings) {
605 displayHdg = (int) fgGetDouble("/orientation/heading-magnetic-deg");
607 displayHdg = (int) _upHeading;
610 double y = (_height / 2) - textHeight;
612 ::snprintf(buf, 16, "%d", displayHdg);
613 int sw = legendFont.getStringWidth(buf);
614 legendFont.drawString(buf, loc.x() - sw/2, y);
622 drawNavRadio(fgGetNode("/instrumentation/nav[0]", false));
623 drawNavRadio(fgGetNode("/instrumentation/nav[1]", false));
624 paintAircraftLocation(_aircraft);
632 glDisable(GL_SCISSOR_TEST);
635 void MapWidget::paintRuler()
637 if (_clickGeod == SGGeod()) {
641 SGVec2d acftPos = project(_aircraft);
642 SGVec2d clickPos = project(_clickGeod);
644 glColor4f(0.0, 1.0, 1.0, 0.6);
645 drawLine(acftPos, clickPos);
647 circleAtAlt(clickPos, 8, 10, 5);
649 double dist, az, az2;
650 SGGeodesy::inverse(_aircraft, _clickGeod, az, az2, dist);
652 ::snprintf(buffer, 1024, "%03d/%.1fnm",
653 displayHeading(az), dist * SG_METER_TO_NM);
655 MapData* d = getOrCreateDataForKey((void*) RULER_LEGEND_KEY);
657 d->setAnchor(clickPos);
658 d->setOffset(MapData::VALIGN_TOP | MapData::HALIGN_CENTER, 15);
659 d->setPriority(20000);
664 void MapWidget::paintAircraftLocation(const SGGeod& aircraftPos)
666 SGVec2d loc = project(aircraftPos);
668 double hdg = fgGetDouble("/orientation/heading-deg");
671 glColor4f(1.0, 1.0, 0.0, 1.0);
673 glTranslated(loc.x(), loc.y(), 0.0);
674 glRotatef(hdg - _upHeading, 0.0, 0.0, -1.0);
676 const SGVec2d wingspan(12, 0);
677 const SGVec2d nose(0, 8);
678 const SGVec2d tail(0, -14);
679 const SGVec2d tailspan(4,0);
681 drawLine(-wingspan, wingspan);
682 drawLine(nose, tail);
683 drawLine(tail - tailspan, tail + tailspan);
689 void MapWidget::paintRoute()
691 if (_route->numWaypts() < 2) {
695 RoutePath path(_route->flightPlan());
697 // first pass, draw the actual lines
700 for (int w=0; w<_route->numWaypts(); ++w) {
701 SGGeodVec gv(path.pathForIndex(w));
706 if (w < _route->currentIndex()) {
707 glColor4f(0.5, 0.5, 0.5, 0.7);
709 glColor4f(1.0, 0.0, 1.0, 1.0);
712 flightgear::WayptRef wpt(_route->wayptAtIndex(w));
713 if (wpt->flag(flightgear::WPT_MISS)) {
714 glEnable(GL_LINE_STIPPLE);
715 glLineStipple(1, 0x00FF);
718 glBegin(GL_LINE_STRIP);
719 for (unsigned int i=0; i<gv.size(); ++i) {
720 SGVec2d p = project(gv[i]);
721 glVertex2d(p.x(), p.y());
725 glDisable(GL_LINE_STIPPLE);
729 // second pass, draw waypoint symbols and data
730 for (int w=0; w < _route->numWaypts(); ++w) {
731 flightgear::WayptRef wpt(_route->wayptAtIndex(w));
732 SGGeod g = path.positionForIndex(w);
734 continue; // Vectors or similar
737 SGVec2d p = project(g);
738 glColor4f(1.0, 0.0, 1.0, 1.0);
739 circleAtAlt(p, 8, 12, 5);
741 std::ostringstream legend;
742 legend << wpt->ident();
743 if (wpt->altitudeRestriction() != flightgear::RESTRICT_NONE) {
744 legend << '\n' << SGMiscd::roundToInt(wpt->altitudeFt()) << '\'';
747 if (wpt->speedRestriction() == flightgear::SPEED_RESTRICT_MACH) {
748 legend << '\n' << wpt->speedMach() << "M";
749 } else if (wpt->speedRestriction() != flightgear::RESTRICT_NONE) {
750 legend << '\n' << SGMiscd::roundToInt(wpt->speedKts()) << "Kts";
753 MapData* d = getOrCreateDataForKey(reinterpret_cast<void*>(w * 2));
754 d->setText(legend.str());
755 d->setLabel(wpt->ident());
757 d->setOffset(MapData::VALIGN_TOP | MapData::HALIGN_CENTER, 15);
758 d->setPriority(w < _route->currentIndex() ? 9000 : 12000);
760 } // of second waypoint iteration
763 void MapWidget::drawFlightHistory()
765 FGFlightHistory* history = (FGFlightHistory*) globals->get_subsystem("history");
766 if (!history || !_root->getBoolValue("draw-flight-history")) {
770 // first pass, draw the actual lines
773 SGGeodVec gv(history->pathForHistory());
774 glColor4f(0.0, 0.0, 1.0, 0.7);
776 glBegin(GL_LINE_STRIP);
777 for (unsigned int i=0; i<gv.size(); ++i) {
778 SGVec2d p = project(gv[i]);
779 glVertex2d(p.x(), p.y());
786 * Round a SGGeod to an arbitrary precision.
787 * For example, passing precision of 0.5 will round to the nearest 0.5 of
788 * a degree in both lat and lon - passing in 3.0 rounds to the nearest 3 degree
789 * multiple, and so on.
791 static SGGeod roundGeod(double precision, const SGGeod& g)
793 double lon = SGMiscd::round(g.getLongitudeDeg() / precision);
794 double lat = SGMiscd::round(g.getLatitudeDeg() / precision);
796 return SGGeod::fromDeg(lon * precision, lat * precision);
799 bool MapWidget::drawLineClipped(const SGVec2d& a, const SGVec2d& b)
801 double minX = SGMiscd::min(a.x(), b.x()),
802 minY = SGMiscd::min(a.y(), b.y()),
803 maxX = SGMiscd::max(a.x(), b.x()),
804 maxY = SGMiscd::max(a.y(), b.y());
806 int hh = _height >> 1, hw = _width >> 1;
808 if ((maxX < -hw) || (minX > hw) || (minY > hh) || (maxY < -hh)) {
812 glVertex2dv(a.data());
813 glVertex2dv(b.data());
817 SGVec2d MapWidget::gridPoint(int ix, int iy)
819 int key = (ix + 0x7fff) | ((iy + 0x7fff) << 16);
820 GridPointCache::iterator it = _gridCache.find(key);
821 if (it != _gridCache.end()) {
825 SGGeod gp = SGGeod::fromDeg(
826 _gridCenter.getLongitudeDeg() + ix * _gridSpacing,
827 _gridCenter.getLatitudeDeg() + iy * _gridSpacing);
829 SGVec2d proj = project(gp);
830 _gridCache[key] = proj;
834 void MapWidget::drawLatLonGrid()
837 _gridCenter = roundGeod(_gridSpacing, _projectionCenter);
843 glColor4f(0.8, 0.8, 0.8, 0.4);
851 for (int x = -ix; x < ix; ++x) {
852 didDraw |= drawLineClipped(gridPoint(x, -iy), gridPoint(x+1, -iy));
853 didDraw |= drawLineClipped(gridPoint(x, iy), gridPoint(x+1, iy));
854 didDraw |= drawLineClipped(gridPoint(x, -iy), gridPoint(x, -iy + 1));
855 didDraw |= drawLineClipped(gridPoint(x, iy), gridPoint(x, iy - 1));
859 for (int y = -iy; y < iy; ++y) {
860 didDraw |= drawLineClipped(gridPoint(-ix, y), gridPoint(-ix, y+1));
861 didDraw |= drawLineClipped(gridPoint(-ix, y), gridPoint(-ix + 1, y));
862 didDraw |= drawLineClipped(gridPoint(ix, y), gridPoint(ix, y+1));
863 didDraw |= drawLineClipped(gridPoint(ix, y), gridPoint(ix - 1, y));
874 void MapWidget::drawGPSData()
876 std::string gpsMode = _gps->getStringValue("mode");
878 SGGeod wp0Geod = SGGeod::fromDeg(
879 _gps->getDoubleValue("wp/wp[0]/longitude-deg"),
880 _gps->getDoubleValue("wp/wp[0]/latitude-deg"));
882 SGGeod wp1Geod = SGGeod::fromDeg(
883 _gps->getDoubleValue("wp/wp[1]/longitude-deg"),
884 _gps->getDoubleValue("wp/wp[1]/latitude-deg"));
887 double gpsTrackDeg = _gps->getDoubleValue("indicated-track-true-deg");
888 double gpsSpeed = _gps->getDoubleValue("indicated-ground-speed-kt");
891 if (gpsSpeed > 3.0) { // only draw track line if valid
893 SGGeodesy::direct(_aircraft, gpsTrackDeg, _drawRangeNm * SG_NM_TO_METER, trackRadial, az2);
895 glColor4f(1.0, 1.0, 0.0, 1.0);
896 glEnable(GL_LINE_STIPPLE);
897 glLineStipple(1, 0x00FF);
898 drawLine(project(_aircraft), project(trackRadial));
899 glDisable(GL_LINE_STIPPLE);
902 if (gpsMode == "dto") {
903 SGVec2d wp0Pos = project(wp0Geod);
904 SGVec2d wp1Pos = project(wp1Geod);
906 glColor4f(1.0, 0.0, 1.0, 1.0);
907 drawLine(wp0Pos, wp1Pos);
911 if (_gps->getBoolValue("scratch/valid")) {
917 class MapAirportFilter : public FGAirport::AirportFilter
920 MapAirportFilter(SGPropertyNode_ptr nd)
922 _heliports = nd->getBoolValue("show-heliports", false);
923 _hardRunwaysOnly = nd->getBoolValue("hard-surfaced-airports", true);
924 _minLengthFt = fgGetDouble("/sim/navdb/min-runway-length-ft", 2000);
927 virtual FGPositioned::Type maxType() const {
928 return _heliports ? FGPositioned::HELIPORT : FGPositioned::AIRPORT;
931 virtual bool passAirport(FGAirport* aApt) const {
932 if (_hardRunwaysOnly) {
933 return aApt->hasHardRunwayOfLengthFt(_minLengthFt);
941 _hardRunwaysOnly = false;
946 bool _hardRunwaysOnly;
950 void MapWidget::drawAirports()
952 MapAirportFilter af(_root);
953 if (_cachedZoom <= SHOW_DETAIL2_ZOOM) {
954 // show all airports when zoomed in sufficently
958 bool partial = false;
959 FGPositionedList apts = FGPositioned::findWithinRangePartial(_projectionCenter, _drawRangeNm, &af, partial);
960 for (unsigned int i=0; i<apts.size(); ++i) {
961 drawAirport((FGAirport*) apts[i].get());
965 class NavaidFilter : public FGPositioned::Filter
968 NavaidFilter(bool fixesEnabled, bool navaidsEnabled) :
969 _fixes(fixesEnabled),
970 _navaids(navaidsEnabled)
973 virtual bool pass(FGPositioned* aPos) const {
974 if (_fixes && (aPos->type() == FGPositioned::FIX)) {
975 // ignore fixes which end in digits - expirmental
976 if (aPos->ident().length() > 4 && isdigit(aPos->ident()[3]) && isdigit(aPos->ident()[4])) {
984 virtual FGPositioned::Type minType() const {
985 return _fixes ? FGPositioned::FIX : FGPositioned::NDB;
988 virtual FGPositioned::Type maxType() const {
989 return _navaids ? FGPositioned::VOR : FGPositioned::FIX;
993 bool _fixes, _navaids;
996 void MapWidget::drawNavaids()
998 bool fixes = _root->getBoolValue("draw-fixes");
999 NavaidFilter f(fixes, _root->getBoolValue("draw-navaids"));
1001 if (f.minType() <= f.maxType()) {
1002 FGPositionedList navs = FGPositioned::findWithinRange(_projectionCenter, _drawRangeNm, &f);
1005 for (unsigned int i=0; i<navs.size(); ++i) {
1006 FGPositioned::Type ty = navs[i]->type();
1007 if (ty == FGPositioned::NDB) {
1008 drawNDB(false, (FGNavRecord*) navs[i].get());
1009 } else if (ty == FGPositioned::VOR) {
1010 drawVOR(false, (FGNavRecord*) navs[i].get());
1011 } else if (ty == FGPositioned::FIX) {
1012 drawFix((FGFix*) navs[i].get());
1014 } // of navaid iteration
1015 } // of navaids || fixes are drawn test
1018 void MapWidget::drawPOIs()
1020 FGPositioned::TypeFilter f(FGPositioned::COUNTRY);
1021 f.addType(FGPositioned::CITY);
1022 f.addType(FGPositioned::TOWN);
1023 FGPositionedList poi = FGPositioned::findWithinRange(_projectionCenter, _drawRangeNm, &f);
1026 for (unsigned int i=0; i<poi.size(); ++i) {
1027 FGPositioned::Type ty = poi[i]->type();
1028 if (ty == FGPositioned::COUNTRY) {
1029 drawCountries((FGNavRecord*) poi[i].get());
1030 } else if (ty == FGPositioned::CITY) {
1031 drawCities((FGNavRecord*) poi[i].get());
1032 } else if (ty == FGPositioned::TOWN) {
1033 drawTowns((FGNavRecord*) poi[i].get());
1035 } // of navaid iteration
1038 void MapWidget::drawNDB(bool tuned, FGNavRecord* ndb)
1040 SGVec2d pos = project(ndb->geod());
1043 glColor3f(0.0, 1.0, 1.0);
1045 glColor3f(0.0, 0.0, 0.0);
1048 glEnable(GL_LINE_STIPPLE);
1049 glLineStipple(1, 0x00FF);
1050 circleAt(pos, 20, 6);
1051 circleAt(pos, 20, 10);
1052 glDisable(GL_LINE_STIPPLE);
1054 if (validDataForKey(ndb)) {
1055 setAnchorForKey(ndb, pos);
1060 ::snprintf(buffer, 1024, "%s\n%s %3.0fKhz",
1061 ndb->name().c_str(), ndb->ident().c_str(),ndb->get_freq()/100.0);
1063 MapData* d = createDataForKey(ndb);
1065 d->setLabel(ndb->ident());
1067 d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 10);
1072 void MapWidget::drawVOR(bool tuned, FGNavRecord* vor)
1074 SGVec2d pos = project(vor->geod());
1076 glColor3f(0.0, 1.0, 1.0);
1078 glColor3f(0.0, 0.0, 1.0);
1081 circleAt(pos, 6, 9);
1082 circleAt(pos, 8, 1);
1087 if (validDataForKey(vor)) {
1088 setAnchorForKey(vor, pos);
1093 ::snprintf(buffer, 1024, "%s\n%s %6.3fMhz",
1094 vor->name().c_str(), vor->ident().c_str(),
1095 vor->get_freq() / 100.0);
1097 MapData* d = createDataForKey(vor);
1099 d->setLabel(vor->ident());
1100 d->setPriority(tuned ? 10000 : 100);
1101 d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 12);
1105 void MapWidget::drawFix(FGFix* fix)
1107 SGVec2d pos = project(fix->geod());
1108 glColor3f(0.0, 0.0, 0.0);
1109 circleAt(pos, 3, 6);
1111 if (_cachedZoom > SHOW_DETAIL_ZOOM) {
1112 return; // hide fix labels beyond a certain zoom level
1115 if (validDataForKey(fix)) {
1116 setAnchorForKey(fix, pos);
1120 MapData* d = createDataForKey(fix);
1121 d->setLabel(fix->ident());
1123 d->setOffset(MapData::VALIGN_CENTER | MapData::HALIGN_LEFT, 10);
1127 void MapWidget::drawNavRadio(SGPropertyNode_ptr radio)
1129 if (!radio || radio->getBoolValue("slaved-to-gps", false)
1130 || !radio->getBoolValue("in-range", false)) {
1134 if (radio->getBoolValue("nav-loc", false)) {
1135 drawTunedLocalizer(radio);
1138 // identify the tuned station - unfortunately we don't get lat/lon directly,
1139 // need to do the frequency search again
1140 double mhz = radio->getDoubleValue("frequencies/selected-mhz", 0.0);
1142 FGNavRecord* nav = FGNavList::findByFreq(mhz, _aircraft,
1143 FGNavList::navFilter());
1144 if (!nav || (nav->ident() != radio->getStringValue("nav-id"))) {
1145 // mismatch between navradio selection logic and ours!
1152 SGVec2d pos = project(nav->geod());
1155 double trueRadial = radio->getDoubleValue("radials/target-radial-deg");
1156 SGGeodesy::direct(nav->geod(), trueRadial, nav->get_range() * SG_NM_TO_METER, range, az2);
1157 SGVec2d prange = project(range);
1159 SGVec2d norm = normalize(prange - pos);
1160 SGVec2d perp(norm.y(), -norm.x());
1162 circleAt(pos, 64, length(prange - pos));
1163 drawLine(pos, prange);
1165 // draw to/from arrows
1166 SGVec2d midPoint = (pos + prange) * 0.5;
1167 if (radio->getBoolValue("from-flag")) {
1173 SGVec2d arrowB = midPoint - (norm * sz) + (perp * sz);
1174 SGVec2d arrowC = midPoint - (norm * sz) - (perp * sz);
1175 drawLine(midPoint, arrowB);
1176 drawLine(arrowB, arrowC);
1177 drawLine(arrowC, midPoint);
1179 drawLine(pos, (2 * pos) - prange); // reciprocal radial
1182 void MapWidget::drawTunedLocalizer(SGPropertyNode_ptr radio)
1184 double mhz = radio->getDoubleValue("frequencies/selected-mhz", 0.0);
1185 FGNavRecord* loc = FGNavList::findByFreq(mhz, _aircraft, FGNavList::locFilter());
1186 if (!loc || (loc->ident() != radio->getStringValue("nav-id"))) {
1187 // mismatch between navradio selection logic and ours!
1191 if (loc->runway()) {
1192 drawILS(true, loc->runway());
1196 void MapWidget::drawCountries(FGNavRecord* rec)
1198 if (_cachedZoom < 9) {
1199 return; // hide labels beyond a certain zoom level
1202 SGVec2d pos = project(rec->geod());
1203 glColor3f(1.0, 1.0, 0.0);
1205 circleAt(pos, 4, 10);
1207 if (validDataForKey(rec)) {
1208 setAnchorForKey(rec, pos);
1213 ::snprintf(buffer, 1024, "%s",
1214 rec->name().c_str());
1216 MapData* d = createDataForKey(rec);
1217 d->setPriority(200);
1218 d->setLabel(rec->ident());
1220 d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 10);
1225 void MapWidget::drawCities(FGNavRecord* rec)
1227 if (_cachedZoom > SHOW_DETAIL_ZOOM) {
1228 return; // hide labels beyond a certain zoom level
1231 SGVec2d pos = project(rec->geod());
1232 glColor3f(0.0, 1.0, 0.0);
1234 circleAt(pos, 4, 8);
1236 if (validDataForKey(rec)) {
1237 setAnchorForKey(rec, pos);
1242 ::snprintf(buffer, 1024, "%s",
1243 rec->name().c_str());
1245 MapData* d = createDataForKey(rec);
1247 d->setLabel(rec->ident());
1249 d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 10);
1254 void MapWidget::drawTowns(FGNavRecord* rec)
1256 if (_cachedZoom > SHOW_DETAIL2_ZOOM) {
1257 return; // hide labels beyond a certain zoom level
1260 SGVec2d pos = project(rec->geod());
1261 glColor3f(0.2, 1.0, 0.0);
1263 circleAt(pos, 4, 5);
1265 if (validDataForKey(rec)) {
1266 setAnchorForKey(rec, pos);
1271 ::snprintf(buffer, 1024, "%s",
1272 rec->name().c_str());
1274 MapData* d = createDataForKey(rec);
1276 d->setLabel(rec->ident());
1278 d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 10);
1284 void MapWidget::drawObstacle(FGPositioned* obs)
1286 SGVec2d pos = project(obs->geod());
1287 glColor3f(0.0, 0.0, 0.0);
1289 drawLine(pos, pos + SGVec2d());
1293 void MapWidget::drawAirport(FGAirport* apt)
1295 // draw tower location
1296 SGVec2d towerPos = project(apt->getTowerLocation());
1298 if (_cachedZoom <= SHOW_DETAIL_ZOOM) {
1299 glColor3f(1.0, 1.0, 1.0);
1302 drawLine(towerPos + SGVec2d(3, 0), towerPos + SGVec2d(3, 10));
1303 drawLine(towerPos + SGVec2d(-3, 0), towerPos + SGVec2d(-3, 10));
1304 drawLine(towerPos + SGVec2d(-6, 20), towerPos + SGVec2d(-3, 10));
1305 drawLine(towerPos + SGVec2d(6, 20), towerPos + SGVec2d(3, 10));
1306 drawLine(towerPos + SGVec2d(-6, 20), towerPos + SGVec2d(6, 20));
1309 if (validDataForKey(apt)) {
1310 setAnchorForKey(apt, towerPos);
1313 ::snprintf(buffer, 1024, "%s\n%s",
1314 apt->ident().c_str(), apt->name().c_str());
1316 MapData* d = createDataForKey(apt);
1318 d->setLabel(apt->ident());
1319 d->setPriority(100 + scoreAirportRunways(apt));
1320 d->setOffset(MapData::VALIGN_TOP | MapData::HALIGN_CENTER, 6);
1321 d->setAnchor(towerPos);
1324 if (_cachedZoom > SHOW_DETAIL_ZOOM) {
1328 FGRunwayList runways(apt->getRunwaysWithoutReciprocals());
1330 for (unsigned int r=0; r<runways.size(); ++r) {
1331 drawRunwayPre(runways[r]);
1334 for (unsigned int r=0; r<runways.size(); ++r) {
1335 FGRunway* rwy = runways[r];
1339 drawILS(false, rwy);
1342 if (rwy->reciprocalRunway()) {
1343 FGRunway* recip = rwy->reciprocalRunway();
1345 drawILS(false, recip);
1350 for (unsigned int r=0; r<apt->numHelipads(); ++r) {
1351 FGHelipad* hp = apt->getHelipadByIndex(r);
1353 } // of runway iteration
1357 int MapWidget::scoreAirportRunways(FGAirport* apt)
1359 bool needHardSurface = _root->getBoolValue("hard-surfaced-airports", true);
1360 double minLength = _root->getDoubleValue("min-runway-length-ft", 2000.0);
1362 FGRunwayList runways(apt->getRunwaysWithoutReciprocals());
1365 for (unsigned int r=0; r<runways.size(); ++r) {
1366 FGRunway* rwy = runways[r];
1367 if (needHardSurface && !rwy->isHardSurface()) {
1371 if (rwy->lengthFt() < minLength) {
1375 int scoreLength = SGMiscd::roundToInt(rwy->lengthFt() / 200.0);
1376 score += scoreLength;
1377 } // of runways iteration
1382 void MapWidget::drawRunwayPre(FGRunway* rwy)
1384 SGVec2d p1 = project(rwy->begin());
1385 SGVec2d p2 = project(rwy->end());
1388 glColor3f(1.0, 0.0, 1.0);
1392 void MapWidget::drawRunway(FGRunway* rwy)
1395 // optionally show active, stopway, etc
1396 // in legend, show published heading and length
1397 // and threshold elevation
1399 SGVec2d p1 = project(rwy->begin());
1400 SGVec2d p2 = project(rwy->end());
1402 glColor3f(1.0, 1.0, 1.0);
1403 SGVec2d inset = normalize(p2 - p1) * 2;
1405 drawLine(p1 + inset, p2 - inset);
1407 if (validDataForKey(rwy)) {
1408 setAnchorForKey(rwy, (p1 + p2) * 0.5);
1413 ::snprintf(buffer, 1024, "%s/%s\n%03d/%03d\n%.0f'",
1414 rwy->ident().c_str(),
1415 rwy->reciprocalRunway()->ident().c_str(),
1416 displayHeading(rwy->headingDeg()),
1417 displayHeading(rwy->reciprocalRunway()->headingDeg()),
1420 MapData* d = createDataForKey(rwy);
1422 d->setLabel(rwy->ident() + "/" + rwy->reciprocalRunway()->ident());
1424 d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 12);
1425 d->setAnchor((p1 + p2) * 0.5);
1428 void MapWidget::drawILS(bool tuned, FGRunway* rwy)
1430 // arrow, tip centered on the landing threshold
1431 // using LOC transmitter position would be more accurate, but
1432 // is visually cluttered
1433 // arrow width is based upon the computed localizer width
1435 FGNavRecord* loc = rwy->ILS();
1436 double halfBeamWidth = loc->localizerWidth() * 0.5;
1437 SGVec2d t = project(rwy->threshold());
1439 double rangeM = loc->get_range() * SG_NM_TO_METER;
1440 double radial = loc->get_multiuse();
1441 SG_NORMALIZE_RANGE(radial, 0.0, 360.0);
1444 // compute the three end points at the widge end of the arrow
1445 SGGeodesy::direct(loc->geod(), radial, -rangeM, locEnd, az2);
1446 SGVec2d endCentre = project(locEnd);
1448 SGGeodesy::direct(loc->geod(), radial + halfBeamWidth, -rangeM * 1.1, locEnd, az2);
1449 SGVec2d endR = project(locEnd);
1451 SGGeodesy::direct(loc->geod(), radial - halfBeamWidth, -rangeM * 1.1, locEnd, az2);
1452 SGVec2d endL = project(locEnd);
1454 // outline two triangles
1457 glColor3f(0.0, 1.0, 1.0);
1459 glColor3f(0.0, 0.0, 1.0);
1462 glBegin(GL_LINE_LOOP);
1463 glVertex2dv(t.data());
1464 glVertex2dv(endCentre.data());
1465 glVertex2dv(endL.data());
1467 glBegin(GL_LINE_LOOP);
1468 glVertex2dv(t.data());
1469 glVertex2dv(endCentre.data());
1470 glVertex2dv(endR.data());
1473 if (validDataForKey(loc)) {
1474 setAnchorForKey(loc, endR);
1479 ::snprintf(buffer, 1024, "%s\n%s\n%03d - %3.2fMHz",
1480 loc->ident().c_str(), loc->name().c_str(),
1481 displayHeading(radial),
1482 loc->get_freq()/100.0);
1484 MapData* d = createDataForKey(loc);
1486 d->setLabel(loc->ident());
1488 d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 10);
1492 void MapWidget::drawTraffic()
1494 if (!_root->getBoolValue("draw-traffic")) {
1498 if (_cachedZoom > SHOW_DETAIL_ZOOM) {
1502 const SGPropertyNode* ai = fgGetNode("/ai/models", true);
1504 for (int i = 0; i < ai->nChildren(); ++i) {
1505 const SGPropertyNode *model = ai->getChild(i);
1506 // skip bad or dead entries
1507 if (!model || model->getIntValue("id", -1) == -1) {
1511 const std::string& name(model->getName());
1512 SGGeod pos = SGGeod::fromDegFt(
1513 model->getDoubleValue("position/longitude-deg"),
1514 model->getDoubleValue("position/latitude-deg"),
1515 model->getDoubleValue("position/altitude-ft"));
1517 double dist = SGGeodesy::distanceNm(_projectionCenter, pos);
1518 if (dist > _drawRangeNm) {
1522 double heading = model->getDoubleValue("orientation/true-heading-deg");
1523 if ((name == "aircraft") || (name == "multiplayer") ||
1524 (name == "wingman") || (name == "tanker")) {
1525 drawAIAircraft(model, pos, heading);
1526 } else if ((name == "ship") || (name == "carrier") || (name == "escort")) {
1527 drawAIShip(model, pos, heading);
1529 } // of ai/models iteration
1532 void MapWidget::drawHelipad(FGHelipad* hp)
1534 SGVec2d pos = project(hp->geod());
1536 glColor3f(1.0, 0.0, 1.0);
1537 circleAt(pos, 16, 5.0);
1539 if (validDataForKey(hp)) {
1540 setAnchorForKey(hp, pos);
1545 ::snprintf(buffer, 1024, "%s\n%03d\n%.0f'",
1546 hp->ident().c_str(),
1547 displayHeading(hp->headingDeg()),
1550 MapData* d = createDataForKey(hp);
1552 d->setLabel(hp->ident());
1554 d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 8);
1558 void MapWidget::drawAIAircraft(const SGPropertyNode* model, const SGGeod& pos, double hdg)
1561 SGVec2d p = project(pos);
1563 glColor3f(0.0, 0.0, 0.0);
1565 circleAt(p, 4, 6.0); // black diamond
1567 // draw heading vector
1568 int speedKts = static_cast<int>(model->getDoubleValue("velocities/true-airspeed-kt"));
1572 const double dt = 15.0 / (3600.0); // 15 seconds look-ahead
1573 double distanceM = speedKts * SG_NM_TO_METER * dt;
1577 SGGeodesy::direct(pos, hdg, distanceM, advance, az2);
1579 drawLine(p, project(advance));
1582 // try to access the flight-plan of the aircraft. There are several layers
1583 // of potential NULL-ness here, so we have to be defensive at each stage.
1584 std::string originICAO, destinationICAO;
1585 FGAIManager* aiManager = static_cast<FGAIManager*>(globals->get_subsystem("ai-model"));
1586 FGAIBasePtr aircraft = aiManager ? aiManager->getObjectFromProperty(model) : NULL;
1588 FGAIAircraft* p = static_cast<FGAIAircraft*>(aircraft.get());
1589 if (p->GetFlightPlan()) {
1590 if (p->GetFlightPlan()->departureAirport()) {
1591 originICAO = p->GetFlightPlan()->departureAirport()->ident();
1594 if (p->GetFlightPlan()->arrivalAirport()) {
1595 destinationICAO = p->GetFlightPlan()->arrivalAirport()->ident();
1597 } // of flight-plan exists
1598 } // of check for AIBase-derived instance
1600 // draw callsign / altitude / speed
1601 int altFt50 = static_cast<int>(pos.getElevationFt() / 50.0) * 50;
1602 std::ostringstream ss;
1603 ss << model->getStringValue("callsign", "<>");
1605 ss << "\n" << altFt50 << "' " << speedKts << "kts";
1608 if (!originICAO.empty() || ! destinationICAO.empty()) {
1609 ss << "\n" << originICAO << " -> " << destinationICAO;
1612 MapData* d = getOrCreateDataForKey((void*) model);
1613 d->setText(ss.str().c_str());
1614 d->setLabel(model->getStringValue("callsign", "<>"));
1615 d->setPriority(speedKts > 5 ? 60 : 10); // low priority for parked aircraft
1616 d->setOffset(MapData::VALIGN_CENTER | MapData::HALIGN_LEFT, 10);
1620 void MapWidget::drawAIShip(const SGPropertyNode* model, const SGGeod& pos, double hdg)
1622 SGVec2d p = project(pos);
1624 glColor3f(0.0, 0.0, 0.5);
1626 circleAt(p, 4, 6.0); // blue diamond (to differentiate from aircraft.
1628 // draw heading vector
1629 int speedKts = static_cast<int>(model->getDoubleValue("velocities/speed-kts"));
1633 const double dt = 15.0 / (3600.0); // 15 seconds look-ahead
1634 double distanceM = speedKts * SG_NM_TO_METER * dt;
1638 SGGeodesy::direct(pos, hdg, distanceM, advance, az2);
1640 drawLine(p, project(advance));
1643 // draw callsign / speed
1645 ::snprintf(buffer, 1024, "%s\n%dkts",
1646 model->getStringValue("name", "<>"),
1649 MapData* d = getOrCreateDataForKey((void*) model);
1651 d->setLabel(model->getStringValue("name", "<>"));
1652 d->setPriority(speedKts > 2 ? 30 : 10); // low priority for slow moving ships
1653 d->setOffset(MapData::VALIGN_CENTER | MapData::HALIGN_LEFT, 10);
1657 SGVec2d MapWidget::project(const SGGeod& geod) const
1660 double r = earth_radius_lat(geod.getLatitudeRad());
1662 switch (_projection) {
1663 case PROJECTION_SAMSON_FLAMSTEED:
1665 // Sanson-Flamsteed projection, relative to the projection center
1666 double lonDiff = geod.getLongitudeRad() - _projectionCenter.getLongitudeRad(),
1667 latDiff = geod.getLatitudeRad() - _projectionCenter.getLatitudeRad();
1669 p = SGVec2d(cos(geod.getLatitudeRad()) * lonDiff, latDiff) * r * currentScale();
1673 case PROJECTION_ORTHO_AZIMUTH:
1675 // http://mathworld.wolfram.com/OrthographicProjection.html
1676 double cosTheta = cos(geod.getLatitudeRad());
1677 double sinDLambda = sin(geod.getLongitudeRad() - _projectionCenter.getLongitudeRad());
1678 double cosDLambda = cos(geod.getLongitudeRad() - _projectionCenter.getLongitudeRad());
1679 double sinTheta1 = sin(_projectionCenter.getLatitudeRad());
1680 double sinTheta = sin(geod.getLatitudeRad());
1681 double cosTheta1 = cos(_projectionCenter.getLatitudeRad());
1683 p = SGVec2d(cosTheta * sinDLambda,
1684 (cosTheta1 * sinTheta) - (sinTheta1 * cosTheta * cosDLambda)) * r * currentScale();
1688 case PROJECTION_SPHERICAL:
1690 SGVec3d cartCenter = SGVec3d::fromGeod(_projectionCenter);
1691 SGVec3d cartPt = SGVec3d::fromGeod(geod) - cartCenter;
1693 // rotate relative to projection center
1694 SGQuatd orient = SGQuatd::fromLonLat(_projectionCenter);
1695 cartPt = orient.rotateBack(cartPt);
1696 return SGVec2d(cartPt.y(), cartPt.x()) * currentScale();
1699 } // of projection mode switch
1702 // rotate as necessary
1703 double cost = cos(_upHeading * SG_DEGREES_TO_RADIANS),
1704 sint = sin(_upHeading * SG_DEGREES_TO_RADIANS);
1705 double rx = cost * p.x() - sint * p.y();
1706 double ry = sint * p.x() + cost * p.y();
1707 return SGVec2d(rx, ry);
1710 SGGeod MapWidget::unproject(const SGVec2d& p) const
1712 // unrotate, if necessary
1713 double cost = cos(-_upHeading * SG_DEGREES_TO_RADIANS),
1714 sint = sin(-_upHeading * SG_DEGREES_TO_RADIANS);
1715 SGVec2d ur(cost * p.x() - sint * p.y(),
1716 sint * p.x() + cost * p.y());
1720 switch (_projection) {
1721 case PROJECTION_SAMSON_FLAMSTEED:
1723 double r = earth_radius_lat(_projectionCenter.getLatitudeRad());
1724 SGVec2d unscaled = ur * (1.0 / (currentScale() * r));
1725 double lat = unscaled.y() + _projectionCenter.getLatitudeRad();
1726 double lon = (unscaled.x() / cos(lat)) + _projectionCenter.getLongitudeRad();
1727 return SGGeod::fromRad(lon, lat);
1730 case PROJECTION_ORTHO_AZIMUTH:
1732 double r = earth_radius_lat(_projectionCenter.getLatitudeRad());
1733 SGVec2d unscaled = ur * (1.0 / (currentScale() * r));
1735 double phi = length(p);
1736 double c = asin(phi);
1737 double sinTheta1 = sin(_projectionCenter.getLatitudeRad());
1738 double cosTheta1 = cos(_projectionCenter.getLatitudeRad());
1740 double lat = asin(cos(c) * sinTheta1 + ((unscaled.y() * sin(c) * cosTheta1) / phi));
1741 double lon = _projectionCenter.getLongitudeRad() +
1742 atan((unscaled.x()* sin(c)) / (phi * cosTheta1 * cos(c) - unscaled.y() * sinTheta1 * sin(c)));
1743 return SGGeod::fromRad(lon, lat);
1746 case PROJECTION_SPHERICAL:
1748 SGVec2d unscaled = ur * (1.0 / currentScale());
1749 SGQuatd orient = SGQuatd::fromLonLat(_projectionCenter);
1750 SGVec3d cartCenter = SGVec3d::fromGeod(_projectionCenter);
1751 SGVec3d cartPt = orient.rotate(SGVec3d(unscaled.x(), unscaled.y(), 0.0));
1752 return SGGeod::fromCart(cartPt + cartCenter);
1754 } // of projection mode switch
1757 double MapWidget::currentScale() const
1759 return 1.0 / pow(2.0, _cachedZoom);
1762 void MapWidget::circleAt(const SGVec2d& center, int nSides, double r)
1764 glBegin(GL_LINE_LOOP);
1765 double advance = (SGD_PI * 2) / nSides;
1766 glVertex2d(center.x() +r, center.y());
1768 for (int i=1; i<nSides; ++i) {
1769 glVertex2d(center.x() + (cos(t) * r), center.y() + (sin(t) * r));
1775 void MapWidget::squareAt(const SGVec2d& center, double r)
1777 glBegin(GL_LINE_LOOP);
1778 glVertex2d(center.x() + r, center.y() + r);
1779 glVertex2d(center.x() + r, center.y() - r);
1780 glVertex2d(center.x() - r, center.y() - r);
1781 glVertex2d(center.x() - r, center.y() + r);
1785 void MapWidget::circleAtAlt(const SGVec2d& center, int nSides, double r, double r2)
1787 glBegin(GL_LINE_LOOP);
1788 double advance = (SGD_PI * 2) / nSides;
1789 glVertex2d(center.x(), center.y() + r);
1791 for (int i=1; i<nSides; ++i) {
1792 double rr = (i%2 == 0) ? r : r2;
1793 glVertex2d(center.x() + (sin(t) * rr), center.y() + (cos(t) * rr));
1799 void MapWidget::drawLine(const SGVec2d& p1, const SGVec2d& p2)
1802 glVertex2dv(p1.data());
1803 glVertex2dv(p2.data());
1807 void MapWidget::drawLegendBox(const SGVec2d& pos, const std::string& t)
1809 std::vector<std::string> lines(simgear::strutils::split(t, "\n"));
1810 const int LINE_LEADING = 4;
1811 const int MARGIN = 4;
1814 int maxWidth = -1, totalHeight = 0;
1815 int lineHeight = legendFont.getStringHeight();
1817 for (unsigned int ln=0; ln<lines.size(); ++ln) {
1818 totalHeight += lineHeight;
1820 totalHeight += LINE_LEADING;
1823 int lw = legendFont.getStringWidth(lines[ln].c_str());
1824 maxWidth = std::max(maxWidth, lw);
1825 } // of line measurement
1828 return; // all lines are empty, don't draw
1831 totalHeight += MARGIN * 2;
1836 box.min[1] = -totalHeight;
1837 box.max[0] = maxWidth + (MARGIN * 2);
1840 box.draw (pos.x(), pos.y(), PUSTYLE_DROPSHADOW, colour, FALSE, border);
1843 int xPos = pos.x() + MARGIN;
1844 int yPos = pos.y() - (lineHeight + MARGIN);
1845 glColor3f(0.8, 0.8, 0.8);
1847 for (unsigned int ln=0; ln<lines.size(); ++ln) {
1848 legendFont.drawString(lines[ln].c_str(), xPos, yPos);
1849 yPos -= lineHeight + LINE_LEADING;
1853 void MapWidget::drawData()
1855 std::sort(_dataQueue.begin(), _dataQueue.end(), MapData::order);
1857 int hw = _width >> 1,
1859 puBox visBox(makePuBox(-hw, -hh, _width, _height));
1863 std::vector<MapData*> drawQueue;
1865 bool drawData = _root->getBoolValue("draw-data");
1866 const int MAX_DRAW_DATA = 25;
1867 const int MAX_DRAW = 50;
1869 for (; (d < _dataQueue.size()) && (drawn < MAX_DRAW); ++d) {
1870 MapData* md = _dataQueue[d];
1871 md->setDataVisible(drawData);
1873 if (md->isClipped(visBox)) {
1877 if (md->overlaps(drawQueue)) {
1878 if (drawData) { // overlapped with data, let's try just the label
1879 md->setDataVisible(false);
1880 if (md->overlaps(drawQueue)) {
1886 } // of overlaps case
1888 drawQueue.push_back(md);
1890 if (drawData && (drawn >= MAX_DRAW_DATA)) {
1895 // draw lowest-priority first, so higher-priorty items appear on top
1896 std::vector<MapData*>::reverse_iterator r;
1897 for (r = drawQueue.rbegin(); r!= drawQueue.rend(); ++r) {
1902 KeyDataMap::iterator it = _mapData.begin();
1903 for (; it != _mapData.end(); ) {
1905 if (it->second->isExpired()) {
1907 KeyDataMap::iterator cur = it++;
1908 _mapData.erase(cur);
1912 } // of expiry iteration
1915 bool MapWidget::validDataForKey(void* key)
1917 KeyDataMap::iterator it = _mapData.find(key);
1918 if (it == _mapData.end()) {
1919 return false; // no valid data for the key!
1922 it->second->resetAge(); // mark data as valid this frame
1923 _dataQueue.push_back(it->second);
1927 void MapWidget::setAnchorForKey(void* key, const SGVec2d& anchor)
1929 KeyDataMap::iterator it = _mapData.find(key);
1930 if (it == _mapData.end()) {
1931 throw sg_exception("no valid data for key!");
1934 it->second->setAnchor(anchor);
1937 MapData* MapWidget::getOrCreateDataForKey(void* key)
1939 KeyDataMap::iterator it = _mapData.find(key);
1940 if (it == _mapData.end()) {
1941 return createDataForKey(key);
1944 it->second->resetAge(); // mark data as valid this frame
1945 _dataQueue.push_back(it->second);
1949 MapData* MapWidget::createDataForKey(void* key)
1951 KeyDataMap::iterator it = _mapData.find(key);
1952 if (it != _mapData.end()) {
1953 throw sg_exception("duplicate data requested for key!");
1956 MapData* d = new MapData(0);
1958 _dataQueue.push_back(d);
1963 void MapWidget::clearData()
1965 KeyDataMap::iterator it = _mapData.begin();
1966 for (; it != _mapData.end(); ++it) {
1973 int MapWidget::displayHeading(double h) const
1975 if (_magneticHeadings) {
1976 h -= _magVar->get_magvar() * SG_RADIANS_TO_DEGREES;
1979 SG_NORMALIZE_RANGE(h, 0.0, 360.0);
1980 return SGMiscd::roundToInt(h);