5 #include "MapWidget.hxx"
8 #include <algorithm> // for std::sort
9 #include <plib/puAux.h>
11 #include <simgear/route/waypoint.hxx>
12 #include <simgear/sg_inlines.h>
13 #include <simgear/misc/strutils.hxx>
14 #include <simgear/magvar/magvar.hxx>
15 #include <simgear/timing/sg_time.hxx> // for magVar julianDate
16 #include <simgear/structure/exception.hxx>
18 #include <Main/globals.hxx>
19 #include <Main/fg_props.hxx>
20 #include <Autopilot/route_mgr.hxx>
21 #include <Navaids/positioned.hxx>
22 #include <Navaids/navrecord.hxx>
23 #include <Navaids/navlist.hxx>
24 #include <Navaids/fix.hxx>
25 #include <Airports/simple.hxx>
26 #include <Airports/runways.hxx>
27 #include <Main/fg_os.hxx> // fgGetKeyModifiers()
29 const char* RULER_LEGEND_KEY = "ruler-legend";
31 /* equatorial and polar earth radius */
32 const float rec = 6378137; // earth radius, equator (?)
33 const float rpol = 6356752.314f; // earth radius, polar (?)
35 /************************************************************************
36 some trigonometric helper functions
37 (translated more or less directly from Alexei Novikovs perl original)
38 *************************************************************************/
40 //Returns Earth radius at a given latitude (Ellipsoide equation with two equal axis)
41 static float earth_radius_lat( float lat )
43 double a = cos(lat)/rec;
44 double b = sin(lat)/rpol;
45 return 1.0f / sqrt( a * a + b * b );
48 ///////////////////////////////////////////////////////////////////////////
50 static puBox makePuBox(int x, int y, int w, int h)
60 static bool puBoxIntersect(const puBox& a, const puBox& b)
62 int x0 = SG_MAX2(a.min[0], b.min[0]);
63 int y0 = SG_MAX2(a.min[1], b.min[1]);
64 int x1 = SG_MIN2(a.max[0], b.max[0]);
65 int y1 = SG_MIN2(a.max[1], b.max[1]);
67 return (x0 <= x1) && (y0 <= y1);
71 typedef std::vector<MapData*> MapDataVec;
76 static const int HALIGN_LEFT = 1;
77 static const int HALIGN_CENTER = 2;
78 static const int HALIGN_RIGHT = 3;
80 static const int VALIGN_TOP = 1 << 4;
81 static const int VALIGN_CENTER = 2 << 4;
82 static const int VALIGN_BOTTOM = 3 << 4;
84 MapData(int priority) :
90 _offsetDir(HALIGN_LEFT | VALIGN_CENTER),
96 void setLabel(const std::string& label)
98 if (label == _label) {
99 return; // common case, and saves invalidation
106 void setText(const std::string &text)
108 if (_rawText == text) {
109 return; // common case, and saves invalidation
116 void setDataVisible(bool vis) {
117 if (vis == _dataVisible) {
121 if (_rawText.empty()) {
129 static void setFont(puFont f)
132 _fontHeight = f.getStringHeight();
133 _fontDescender = f.getStringDescender();
136 static void setPalette(puColor* pal)
141 void setPriority(int pri)
147 { return _priority; }
149 void setAnchor(const SGVec2d& anchor)
154 void setOffset(int direction, int px)
156 if ((_offsetPx == px) && (_offsetDir == direction)) {
161 _offsetDir = direction;
165 bool isClipped(const puBox& vis) const
168 if ((_width < 1) || (_height < 1)) {
172 return !puBoxIntersect(vis, box());
175 bool overlaps(const MapDataVec& l) const
180 MapDataVec::const_iterator it;
181 for (it = l.begin(); it != l.end(); ++it) {
182 if (puBoxIntersect(b, (*it)->box())) {
185 } // of list iteration
194 _anchor.x() + _offset.x(),
195 _anchor.y() + _offset.y(),
203 int xx = _anchor.x() + _offset.x();
204 int yy = _anchor.y() + _offset.y();
207 puBox box(makePuBox(0,0,_width, _height));
209 box.draw(xx, yy, PUSTYLE_DROPSHADOW, _palette, FALSE, border);
212 int lineHeight = _fontHeight;
213 int xPos = xx + MARGIN;
214 int yPos = yy + _height - (lineHeight + MARGIN);
215 glColor3f(0.8, 0.8, 0.8);
217 for (unsigned int ln=0; ln<_lines.size(); ++ln) {
218 _font.drawString(_lines[ln].c_str(), xPos, yPos);
219 yPos -= lineHeight + LINE_LEADING;
222 glColor3f(0.8, 0.8, 0.8);
223 _font.drawString(_label.c_str(), xx, yy + _fontDescender);
237 bool isExpired() const
238 { return (_age > 100); }
240 static bool order(MapData* a, MapData* b)
242 return a->_priority > b->_priority;
245 void validate() const
265 void measureData() const
267 _lines = simgear::strutils::split(_rawText, "\n");
268 // measure text to find width and height
272 for (unsigned int ln=0; ln<_lines.size(); ++ln) {
273 _height += _fontHeight;
275 _height += LINE_LEADING;
278 int lw = _font.getStringWidth(_lines[ln].c_str());
279 _width = std::max(_width, lw);
280 } // of line measurement
282 if ((_width < 1) || (_height < 1)) {
287 _height += MARGIN * 2;
288 _width += MARGIN * 2;
291 void measureLabel() const
293 if (_label.empty()) {
294 _width = _height = -1;
298 _height = _fontHeight;
299 _width = _font.getStringWidth(_label.c_str());
302 void computeOffset() const
304 _dirtyOffset = false;
305 if ((_width <= 0) || (_height <= 0)) {
312 switch (_offsetDir & 0x0f) {
319 hOffset = -(_width>>1);
323 hOffset = -(_offsetPx + _width);
327 switch (_offsetDir & 0xf0) {
330 vOffset = -(_offsetPx + _height);
334 vOffset = -(_height>>1);
342 _offset = SGVec2d(hOffset, vOffset);
345 static const int LINE_LEADING = 3;
346 static const int MARGIN = 3;
348 mutable bool _dirtyText;
349 mutable bool _dirtyOffset;
351 std::string _rawText;
353 mutable std::vector<std::string> _lines;
355 mutable int _width, _height;
359 mutable SGVec2d _offset;
363 static puColor* _palette;
364 static int _fontHeight;
365 static int _fontDescender;
368 puFont MapData::_font;
369 puColor* MapData::_palette;
370 int MapData::_fontHeight = 0;
371 int MapData::_fontDescender = 0;
373 ///////////////////////////////////////////////////////////////////////////
375 const int MAX_ZOOM = 16;
376 const int SHOW_DETAIL_ZOOM = 8;
377 const int CURSOR_PAN_STEP = 32;
379 MapWidget::MapWidget(int x, int y, int maxX, int maxY) :
380 puObject(x,y,maxX, maxY)
382 _route = static_cast<FGRouteMgr*>(globals->get_subsystem("route-manager"));
383 _gps = fgGetNode("/instrumentation/gps");
389 MapData::setFont(legendFont);
390 MapData::setPalette(colour);
392 _magVar = new SGMagVar();
395 MapWidget::~MapWidget()
400 void MapWidget::setProperty(SGPropertyNode_ptr prop)
403 _root->setBoolValue("centre-on-aircraft", true);
404 _root->setBoolValue("draw-data", false);
405 _root->setBoolValue("magnetic-headings", true);
408 void MapWidget::setSize(int w, int h)
410 puObject::setSize(w, h);
417 void MapWidget::doHit( int button, int updown, int x, int y )
419 puObject::doHit(button, updown, x, y);
420 if (updown == PU_DRAG) {
425 if (button == 3) { // mouse-wheel up
427 } else if (button == 4) { // mouse-wheel down
431 if (button != active_mouse_button) {
435 _hitLocation = SGVec2d(x - abox.min[0], y - abox.min[1]);
437 if (updown == PU_UP) {
438 puDeactivateWidget();
439 } else if (updown == PU_DOWN) {
440 puSetActiveWidget(this, x, y);
442 if (fgGetKeyModifiers() & KEYMOD_CTRL) {
443 _clickGeod = unproject(_hitLocation - SGVec2d(_width>>1, _height>>1));
448 void MapWidget::handlePan(int x, int y)
450 SGVec2d delta = SGVec2d(x, y) - _hitLocation;
452 _hitLocation = SGVec2d(x,y);
455 int MapWidget::checkKey (int key, int updown )
457 if ((updown == PU_UP) || !isVisible () || !isActive () || (window != puGetWindow())) {
465 pan(SGVec2d(0, -CURSOR_PAN_STEP));
469 pan(SGVec2d(0, CURSOR_PAN_STEP));
473 pan(SGVec2d(CURSOR_PAN_STEP, 0));
477 pan(SGVec2d(-CURSOR_PAN_STEP, 0));
496 void MapWidget::pan(const SGVec2d& delta)
498 _projectionCenter = unproject(-delta);
501 void MapWidget::zoomIn()
508 SG_LOG(SG_GENERAL, SG_INFO, "zoom is now:" << _zoom);
511 void MapWidget::zoomOut()
513 if (_zoom >= MAX_ZOOM) {
518 SG_LOG(SG_GENERAL, SG_INFO, "zoom is now:" << _zoom);
521 void MapWidget::draw(int dx, int dy)
523 _aircraft = SGGeod::fromDeg(fgGetDouble("/position/longitude-deg"),
524 fgGetDouble("/position/latitude-deg"));
525 _magneticHeadings = _root->getBoolValue("magnetic-headings");
527 if (_root->getBoolValue("centre-on-aircraft")) {
528 _projectionCenter = _aircraft;
529 _root->setBoolValue("centre-on-aircraft", false);
532 double julianDate = globals->get_time_params()->getJD();
533 _magVar->update(_projectionCenter, julianDate);
535 SGGeod topLeft = unproject(SGVec2d(_width/2, _height/2));
536 // compute draw range, including a fudge factor for ILSs and other 'long'
538 _drawRangeNm = SGGeodesy::distanceNm(_projectionCenter, topLeft) + 10.0;
540 bool aircraftUp = _root->getBoolValue("aircraft-heading-up");
542 _upHeading = fgGetDouble("/orientation/heading-deg");
547 // drawing operations
548 GLint sx = (int) abox.min[0],
549 sy = (int) abox.min[1];
550 glScissor(dx + sx, dy + sy, _width, _height);
551 glEnable(GL_SCISSOR_TEST);
553 glMatrixMode(GL_MODELVIEW);
555 // cetere drawing about the widget center (which is also the
556 // projection centre)
557 glTranslated(dx + sx + (_width/2), dy + sy + (_height/2), 0.0);
562 int textHeight = legendFont.getStringHeight() + 5;
565 SGVec2d loc = project(_aircraft);
566 glColor3f(1.0, 1.0, 1.0);
567 drawLine(loc, SGVec2d(loc.x(), (_height / 2) - textHeight));
570 if (_magneticHeadings) {
571 displayHdg = (int) fgGetDouble("/orientation/heading-magnetic-deg");
573 displayHdg = (int) _upHeading;
576 double y = (_height / 2) - textHeight;
578 ::snprintf(buf, 16, "%d", displayHdg);
579 int sw = legendFont.getStringWidth(buf);
580 legendFont.drawString(buf, loc.x() - sw/2, y);
587 drawNavRadio(fgGetNode("/instrumentation/nav[0]", false));
588 drawNavRadio(fgGetNode("/instrumentation/nav[1]", false));
589 paintAircraftLocation(_aircraft);
596 glDisable(GL_SCISSOR_TEST);
599 void MapWidget::paintRuler()
601 if (_clickGeod == SGGeod()) {
605 SGVec2d acftPos = project(_aircraft);
606 SGVec2d clickPos = project(_clickGeod);
608 glColor4f(0.0, 1.0, 1.0, 0.6);
609 drawLine(acftPos, clickPos);
611 circleAtAlt(clickPos, 8, 10, 5);
613 double dist, az, az2;
614 SGGeodesy::inverse(_aircraft, _clickGeod, az, az2, dist);
615 if (_magneticHeadings) {
616 az -= _magVar->get_magvar();
617 SG_NORMALIZE_RANGE(az, 0.0, 360.0);
621 ::snprintf(buffer, 1024, "%03d/%.1fnm",
622 SGMiscd::roundToInt(az), dist * SG_METER_TO_NM);
624 MapData* d = getOrCreateDataForKey((void*) RULER_LEGEND_KEY);
626 d->setAnchor(clickPos);
627 d->setOffset(MapData::VALIGN_TOP | MapData::HALIGN_CENTER, 15);
628 d->setPriority(20000);
633 void MapWidget::paintAircraftLocation(const SGGeod& aircraftPos)
635 SGVec2d loc = project(aircraftPos);
637 double hdg = fgGetDouble("/orientation/heading-deg");
640 glColor4f(1.0, 1.0, 0.0, 1.0);
642 glTranslated(loc.x(), loc.y(), 0.0);
643 glRotatef(hdg - _upHeading, 0.0, 0.0, -1.0);
645 const SGVec2d wingspan(12, 0);
646 const SGVec2d nose(0, 8);
647 const SGVec2d tail(0, -14);
648 const SGVec2d tailspan(4,0);
650 drawLine(-wingspan, wingspan);
651 drawLine(nose, tail);
652 drawLine(tail - tailspan, tail + tailspan);
658 void MapWidget::paintRoute()
660 if (_route->size() < 2) {
664 // first pass, draw the actual line
666 glBegin(GL_LINE_STRIP);
668 SGVec2d prev = project(_route->get_waypoint(0).get_target());
669 glVertex2d(prev.x(), prev.y());
671 for (int w=1; w < _route->size(); ++w) {
673 SGVec2d p = project(_route->get_waypoint(w).get_target());
675 if (w < _route->currentWaypoint()) {
676 glColor4f(0.5, 0.5, 0.5, 0.7);
678 glColor4f(1.0, 0.0, 1.0, 1.0);
681 glVertex2d(p.x(), p.y());
687 // second pass, draw waypoint symbols and data
688 for (int w=0; w < _route->size(); ++w) {
689 const SGWayPoint& wpt(_route->get_waypoint(w));
690 SGVec2d p = project(wpt.get_target());
691 glColor4f(1.0, 0.0, 1.0, 1.0);
692 circleAtAlt(p, 8, 12, 5);
694 std::ostringstream legend;
695 legend << wpt.get_id();
696 if (wpt.get_target_alt() > -9990.0) {
697 legend << '\n' << SGMiscd::roundToInt(wpt.get_target_alt()) << '\'';
700 if (wpt.get_speed() > 0.0) {
701 legend << '\n' << SGMiscd::roundToInt(wpt.get_speed()) << "Kts";
704 MapData* d = getOrCreateDataForKey(reinterpret_cast<void*>(w * 2));
705 d->setText(legend.str());
706 d->setLabel(wpt.get_id());
708 d->setOffset(MapData::VALIGN_TOP | MapData::HALIGN_CENTER, 15);
709 d->setPriority(w < _route->currentWaypoint() ? 9000 : 12000);
712 SGVec2d legMid = (prev + p) * 0.5;
713 std::ostringstream legLegend;
715 double track = wpt.get_track();
716 if (_magneticHeadings) {
717 track -= _magVar->get_magvar(); // show magnetic track for leg
720 legLegend << SGMiscd::roundToInt(track) << " "
721 << SGMiscd::roundToInt(wpt.get_distance() * SG_METER_TO_NM) << "Nm";
723 MapData* ld = getOrCreateDataForKey(reinterpret_cast<void*>(w * 2 + 1));
724 ld->setText(legLegend.str());
725 ld->setAnchor(legMid);
726 ld->setOffset(MapData::VALIGN_TOP | MapData::HALIGN_CENTER, 15);
727 ld->setPriority(w < _route->currentWaypoint() ? 8000 : 11000);
728 } // of draw leg data
731 } // of second waypoint iteration
735 * Round a SGGeod to an arbitrary precision.
736 * For example, passing precision of 0.5 will round to the nearest 0.5 of
737 * a degree in both lat and lon - passing in 3.0 rounds to the nearest 3 degree
738 * multiple, and so on.
740 static SGGeod roundGeod(double precision, const SGGeod& g)
742 double lon = SGMiscd::round(g.getLongitudeDeg() / precision);
743 double lat = SGMiscd::round(g.getLatitudeDeg() / precision);
745 return SGGeod::fromDeg(lon * precision, lat * precision);
748 bool MapWidget::drawLineClipped(const SGVec2d& a, const SGVec2d& b)
750 double minX = SGMiscd::min(a.x(), b.x()),
751 minY = SGMiscd::min(a.y(), b.y()),
752 maxX = SGMiscd::max(a.x(), b.x()),
753 maxY = SGMiscd::max(a.y(), b.y());
755 int hh = _height >> 1, hw = _width >> 1;
757 if ((maxX < -hw) || (minX > hw) || (minY > hh) || (maxY < -hh)) {
761 glVertex2dv(a.data());
762 glVertex2dv(b.data());
766 SGVec2d MapWidget::gridPoint(int ix, int iy)
768 int key = (ix + 0x7fff) | ((iy + 0x7fff) << 16);
769 GridPointCache::iterator it = _gridCache.find(key);
770 if (it != _gridCache.end()) {
774 SGGeod gp = SGGeod::fromDeg(
775 _gridCenter.getLongitudeDeg() + ix * _gridSpacing,
776 _gridCenter.getLatitudeDeg() + iy * _gridSpacing);
778 SGVec2d proj = project(gp);
779 _gridCache[key] = proj;
783 void MapWidget::drawLatLonGrid()
786 _gridCenter = roundGeod(_gridSpacing, _projectionCenter);
792 glColor4f(0.8, 0.8, 0.8, 0.4);
800 for (int x = -ix; x < ix; ++x) {
801 didDraw |= drawLineClipped(gridPoint(x, -iy), gridPoint(x+1, -iy));
802 didDraw |= drawLineClipped(gridPoint(x, iy), gridPoint(x+1, iy));
803 didDraw |= drawLineClipped(gridPoint(x, -iy), gridPoint(x, -iy + 1));
804 didDraw |= drawLineClipped(gridPoint(x, iy), gridPoint(x, iy - 1));
808 for (int y = -iy; y < iy; ++y) {
809 didDraw |= drawLineClipped(gridPoint(-ix, y), gridPoint(-ix, y+1));
810 didDraw |= drawLineClipped(gridPoint(-ix, y), gridPoint(-ix + 1, y));
811 didDraw |= drawLineClipped(gridPoint(ix, y), gridPoint(ix, y+1));
812 didDraw |= drawLineClipped(gridPoint(ix, y), gridPoint(ix - 1, y));
823 void MapWidget::drawGPSData()
825 std::string gpsMode = _gps->getStringValue("mode");
827 SGGeod wp0Geod = SGGeod::fromDeg(
828 _gps->getDoubleValue("wp/wp[0]/longitude-deg"),
829 _gps->getDoubleValue("wp/wp[0]/latitude-deg"));
831 SGGeod wp1Geod = SGGeod::fromDeg(
832 _gps->getDoubleValue("wp/wp[1]/longitude-deg"),
833 _gps->getDoubleValue("wp/wp[1]/latitude-deg"));
836 double gpsTrackDeg = _gps->getDoubleValue("indicated-track-true-deg");
837 double gpsSpeed = _gps->getDoubleValue("indicated-ground-speed-kt");
840 if (gpsSpeed > 3.0) { // only draw track line if valid
842 SGGeodesy::direct(_aircraft, gpsTrackDeg, _drawRangeNm * SG_NM_TO_METER, trackRadial, az2);
844 glColor4f(1.0, 1.0, 0.0, 1.0);
845 glEnable(GL_LINE_STIPPLE);
846 glLineStipple(1, 0x00FF);
847 drawLine(project(_aircraft), project(trackRadial));
848 glDisable(GL_LINE_STIPPLE);
851 if (gpsMode == "dto") {
852 SGVec2d wp0Pos = project(wp0Geod);
853 SGVec2d wp1Pos = project(wp1Geod);
855 glColor4f(1.0, 0.0, 1.0, 1.0);
856 drawLine(wp0Pos, wp1Pos);
860 if (_gps->getBoolValue("scratch/valid")) {
866 class MapAirportFilter : public FGAirport::AirportFilter
869 MapAirportFilter(SGPropertyNode_ptr nd)
871 _heliports = nd->getBoolValue("show-heliports", false);
872 _hardRunwaysOnly = nd->getBoolValue("hard-surfaced-airports", true);
873 _minLengthFt = nd->getDoubleValue("min-runway-length-ft", 2000.0);
876 virtual FGPositioned::Type maxType() const {
877 return _heliports ? FGPositioned::HELIPORT : FGPositioned::AIRPORT;
880 virtual bool passAirport(FGAirport* aApt) const {
881 if (_hardRunwaysOnly) {
882 return aApt->hasHardRunwayOfLengthFt(_minLengthFt);
890 bool _hardRunwaysOnly;
894 void MapWidget::drawAirports()
896 MapAirportFilter af(_root);
897 FGPositioned::List apts = FGPositioned::findWithinRange(_projectionCenter, _drawRangeNm, &af);
898 for (unsigned int i=0; i<apts.size(); ++i) {
899 drawAirport((FGAirport*) apts[i].get());
903 class NavaidFilter : public FGPositioned::Filter
906 NavaidFilter(bool fixesEnabled, bool navaidsEnabled) :
907 _fixes(fixesEnabled),
908 _navaids(navaidsEnabled)
911 virtual bool pass(FGPositioned* aPos) const {
912 if (_fixes && (aPos->type() == FGPositioned::FIX)) {
913 // ignore fixes which end in digits - expirmental
914 if (isdigit(aPos->ident()[3]) && isdigit(aPos->ident()[4])) {
922 virtual FGPositioned::Type minType() const {
923 return _fixes ? FGPositioned::FIX : FGPositioned::VOR;
926 virtual FGPositioned::Type maxType() const {
927 return _navaids ? FGPositioned::NDB : FGPositioned::FIX;
931 bool _fixes, _navaids;
934 void MapWidget::drawNavaids()
936 bool fixes = _root->getBoolValue("draw-fixes");
937 NavaidFilter f(fixes, _root->getBoolValue("draw-navaids"));
939 if (f.minType() <= f.maxType()) {
940 FGPositioned::List navs = FGPositioned::findWithinRange(_projectionCenter, _drawRangeNm, &f);
943 for (unsigned int i=0; i<navs.size(); ++i) {
944 FGPositioned::Type ty = navs[i]->type();
945 if (ty == FGPositioned::NDB) {
946 drawNDB(false, (FGNavRecord*) navs[i].get());
947 } else if (ty == FGPositioned::VOR) {
948 drawVOR(false, (FGNavRecord*) navs[i].get());
949 } else if (ty == FGPositioned::FIX) {
950 drawFix((FGFix*) navs[i].get());
952 } // of navaid iteration
953 } // of navaids || fixes are drawn test
956 void MapWidget::drawNDB(bool tuned, FGNavRecord* ndb)
958 SGVec2d pos = project(ndb->geod());
961 glColor3f(0.0, 1.0, 1.0);
963 glColor3f(0.0, 0.0, 0.0);
966 glEnable(GL_LINE_STIPPLE);
967 glLineStipple(1, 0x00FF);
968 circleAt(pos, 20, 6);
969 circleAt(pos, 20, 10);
970 glDisable(GL_LINE_STIPPLE);
972 if (validDataForKey(ndb)) {
973 setAnchorForKey(ndb, pos);
978 ::snprintf(buffer, 1024, "%s\n%s %3.0fKhz",
979 ndb->name().c_str(), ndb->ident().c_str(),ndb->get_freq()/100.0);
981 MapData* d = createDataForKey(ndb);
983 d->setLabel(ndb->ident());
985 d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 10);
990 void MapWidget::drawVOR(bool tuned, FGNavRecord* vor)
992 SGVec2d pos = project(vor->geod());
994 glColor3f(0.0, 1.0, 1.0);
996 glColor3f(0.0, 0.0, 1.0);
1001 if (validDataForKey(vor)) {
1002 setAnchorForKey(vor, pos);
1007 ::snprintf(buffer, 1024, "%s\n%s %6.3fMhz",
1008 vor->name().c_str(), vor->ident().c_str(),
1009 vor->get_freq() / 100.0);
1011 MapData* d = createDataForKey(vor);
1013 d->setLabel(vor->ident());
1014 d->setPriority(tuned ? 10000 : 100);
1015 d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 12);
1019 void MapWidget::drawFix(FGFix* fix)
1021 SGVec2d pos = project(fix->geod());
1022 glColor3f(0.0, 0.0, 0.0);
1023 circleAt(pos, 3, 6);
1025 if (_zoom > SHOW_DETAIL_ZOOM) {
1026 return; // hide fix labels beyond a certain zoom level
1029 if (validDataForKey(fix)) {
1030 setAnchorForKey(fix, pos);
1034 MapData* d = createDataForKey(fix);
1035 d->setLabel(fix->ident());
1037 d->setOffset(MapData::VALIGN_CENTER | MapData::HALIGN_LEFT, 10);
1041 void MapWidget::drawNavRadio(SGPropertyNode_ptr radio)
1043 if (!radio || radio->getBoolValue("slaved-to-gps", false)
1044 || !radio->getBoolValue("in-range", false)) {
1048 if (radio->getBoolValue("nav-loc", false)) {
1049 drawTunedLocalizer(radio);
1052 // identify the tuned station - unfortunately we don't get lat/lon directly,
1053 // need to do the frequency search again
1054 double mhz = radio->getDoubleValue("frequencies/selected-mhz", 0.0);
1055 FGNavRecord* nav = globals->get_navlist()->findByFreq(mhz, _aircraft);
1056 if (!nav || (nav->ident() != radio->getStringValue("nav-id"))) {
1057 // mismatch between navradio selection logic and ours!
1064 SGVec2d pos = project(nav->geod());
1067 double trueRadial = radio->getDoubleValue("radials/target-radial-deg");
1068 SGGeodesy::direct(nav->geod(), trueRadial, nav->get_range() * SG_NM_TO_METER, range, az2);
1069 SGVec2d prange = project(range);
1071 SGVec2d norm = normalize(prange - pos);
1072 SGVec2d perp(norm.y(), -norm.x());
1074 circleAt(pos, 64, length(prange - pos));
1075 drawLine(pos, prange);
1077 // draw to/from arrows
1078 SGVec2d midPoint = (pos + prange) * 0.5;
1079 if (radio->getBoolValue("from-flag")) {
1085 SGVec2d arrowB = midPoint - (norm * sz) + (perp * sz);
1086 SGVec2d arrowC = midPoint - (norm * sz) - (perp * sz);
1087 drawLine(midPoint, arrowB);
1088 drawLine(arrowB, arrowC);
1089 drawLine(arrowC, midPoint);
1091 drawLine(pos, (2 * pos) - prange); // reciprocal radial
1094 void MapWidget::drawTunedLocalizer(SGPropertyNode_ptr radio)
1096 double mhz = radio->getDoubleValue("frequencies/selected-mhz", 0.0);
1097 FGNavRecord* loc = globals->get_loclist()->findByFreq(mhz, _aircraft);
1098 if (!loc || (loc->ident() != radio->getStringValue("nav-id"))) {
1099 // mismatch between navradio selection logic and ours!
1103 if (loc->runway()) {
1104 drawILS(true, loc->runway());
1109 void MapWidget::drawObstacle(FGPositioned* obs)
1111 SGVec2d pos = project(obs->geod());
1112 glColor3f(0.0, 0.0, 0.0);
1114 drawLine(pos, pos + SGVec2d());
1118 void MapWidget::drawAirport(FGAirport* apt)
1120 // draw tower location
1121 SGVec2d towerPos = project(apt->getTowerLocation());
1123 if (_zoom <= SHOW_DETAIL_ZOOM) {
1124 glColor3f(1.0, 1.0, 1.0);
1127 drawLine(towerPos + SGVec2d(3, 0), towerPos + SGVec2d(3, 10));
1128 drawLine(towerPos + SGVec2d(-3, 0), towerPos + SGVec2d(-3, 10));
1129 drawLine(towerPos + SGVec2d(-6, 20), towerPos + SGVec2d(-3, 10));
1130 drawLine(towerPos + SGVec2d(6, 20), towerPos + SGVec2d(3, 10));
1131 drawLine(towerPos + SGVec2d(-6, 20), towerPos + SGVec2d(6, 20));
1134 if (validDataForKey(apt)) {
1135 setAnchorForKey(apt, towerPos);
1138 ::snprintf(buffer, 1024, "%s\n%s",
1139 apt->ident().c_str(), apt->name().c_str());
1141 MapData* d = createDataForKey(apt);
1143 d->setLabel(apt->ident());
1144 d->setPriority(100 + scoreAirportRunways(apt));
1145 d->setOffset(MapData::VALIGN_TOP | MapData::HALIGN_CENTER, 6);
1146 d->setAnchor(towerPos);
1149 if (_zoom > SHOW_DETAIL_ZOOM) {
1153 for (unsigned int r=0; r<apt->numRunways(); ++r) {
1154 FGRunway* rwy = apt->getRunwayByIndex(r);
1155 if (!rwy->isReciprocal()) {
1160 for (unsigned int r=0; r<apt->numRunways(); ++r) {
1161 FGRunway* rwy = apt->getRunwayByIndex(r);
1162 if (!rwy->isReciprocal()) {
1167 drawILS(false, rwy);
1169 } // of runway iteration
1173 int MapWidget::scoreAirportRunways(FGAirport* apt)
1175 bool needHardSurface = _root->getBoolValue("hard-surfaced-airports", true);
1176 double minLength = _root->getDoubleValue("min-runway-length-ft", 2000.0);
1179 unsigned int numRunways(apt->numRunways());
1180 for (unsigned int r=0; r<numRunways; ++r) {
1181 FGRunway* rwy = apt->getRunwayByIndex(r);
1182 if (rwy->isReciprocal()) {
1186 if (needHardSurface && !rwy->isHardSurface()) {
1190 if (rwy->lengthFt() < minLength) {
1194 int scoreLength = SGMiscd::roundToInt(rwy->lengthFt() / 200.0);
1195 score += scoreLength;
1196 } // of runways iteration
1201 void MapWidget::drawRunwayPre(FGRunway* rwy)
1203 SGVec2d p1 = project(rwy->begin());
1204 SGVec2d p2 = project(rwy->end());
1207 glColor3f(1.0, 0.0, 1.0);
1211 void MapWidget::drawRunway(FGRunway* rwy)
1214 // optionally show active, stopway, etc
1215 // in legend, show published heading and length
1216 // and threshold elevation
1218 SGVec2d p1 = project(rwy->begin());
1219 SGVec2d p2 = project(rwy->end());
1221 glColor3f(1.0, 1.0, 1.0);
1222 SGVec2d inset = normalize(p2 - p1) * 2;
1224 drawLine(p1 + inset, p2 - inset);
1226 if (validDataForKey(rwy)) {
1227 setAnchorForKey(rwy, (p1 + p2) * 0.5);
1232 ::snprintf(buffer, 1024, "%s/%s\n%3.0f/%3.0f\n%.0f'",
1233 rwy->ident().c_str(),
1234 rwy->reciprocalRunway()->ident().c_str(),
1236 rwy->reciprocalRunway()->headingDeg(),
1239 MapData* d = createDataForKey(rwy);
1241 d->setLabel(rwy->ident() + "/" + rwy->reciprocalRunway()->ident());
1243 d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 12);
1244 d->setAnchor((p1 + p2) * 0.5);
1247 void MapWidget::drawILS(bool tuned, FGRunway* rwy)
1249 // arrow, tip centered on the landing threshold
1250 // using LOC transmitter position would be more accurate, but
1251 // is visually cluttered
1252 // arrow width is based upon the computed localizer width
1254 FGNavRecord* loc = rwy->ILS();
1255 double halfBeamWidth = loc->localizerWidth() * 0.5;
1256 SGVec2d t = project(rwy->threshold());
1258 double rangeM = loc->get_range() * SG_NM_TO_METER;
1259 double radial = loc->get_multiuse();
1260 SG_NORMALIZE_RANGE(radial, 0.0, 360.0);
1263 // compute the three end points at the widge end of the arrow
1264 SGGeodesy::direct(loc->geod(), radial, -rangeM, locEnd, az2);
1265 SGVec2d endCentre = project(locEnd);
1267 SGGeodesy::direct(loc->geod(), radial + halfBeamWidth, -rangeM * 1.1, locEnd, az2);
1268 SGVec2d endR = project(locEnd);
1270 SGGeodesy::direct(loc->geod(), radial - halfBeamWidth, -rangeM * 1.1, locEnd, az2);
1271 SGVec2d endL = project(locEnd);
1273 // outline two triangles
1276 glColor3f(0.0, 1.0, 1.0);
1278 glColor3f(0.0, 0.0, 1.0);
1281 glBegin(GL_LINE_LOOP);
1282 glVertex2dv(t.data());
1283 glVertex2dv(endCentre.data());
1284 glVertex2dv(endL.data());
1286 glBegin(GL_LINE_LOOP);
1287 glVertex2dv(t.data());
1288 glVertex2dv(endCentre.data());
1289 glVertex2dv(endR.data());
1293 void MapWidget::drawTraffic()
1295 if (!_root->getBoolValue("draw-traffic")) {
1299 if (_zoom > SHOW_DETAIL_ZOOM) {
1303 const SGPropertyNode* ai = fgGetNode("/ai/models", true);
1305 for (int i = 0; i < ai->nChildren(); ++i) {
1306 const SGPropertyNode *model = ai->getChild(i);
1307 // skip bad or dead entries
1308 if (!model || model->getIntValue("id", -1) < 0) {
1312 const std::string& name(model->getName());
1313 SGGeod pos = SGGeod::fromDegFt(
1314 model->getDoubleValue("position/longitude-deg"),
1315 model->getDoubleValue("position/latitude-deg"),
1316 model->getDoubleValue("position/altitude-ft"));
1318 double dist = SGGeodesy::distanceNm(_projectionCenter, pos);
1319 if (dist > _drawRangeNm) {
1323 double heading = model->getDoubleValue("orientation/true-heading-deg");
1324 if ((name == "aircraft") || (name == "multiplayer") ||
1325 (name == "wingman") || (name == "tanker")) {
1326 drawAIAircraft(model, pos, heading);
1327 } else if ((name == "ship") || (name == "carrier") || (name == "escort")) {
1328 drawAIShip(model, pos, heading);
1330 } // of ai/models iteration
1333 void MapWidget::drawAIAircraft(const SGPropertyNode* model, const SGGeod& pos, double hdg)
1336 SGVec2d p = project(pos);
1338 glColor3f(0.0, 0.0, 0.0);
1340 circleAt(p, 4, 6.0); // black diamond
1342 // draw heading vector
1343 int speedKts = static_cast<int>(model->getDoubleValue("velocities/true-airspeed-kt"));
1347 const double dt = 15.0 / (3600.0); // 15 seconds look-ahead
1348 double distanceM = speedKts * SG_NM_TO_METER * dt;
1352 SGGeodesy::direct(pos, hdg, distanceM, advance, az2);
1354 drawLine(p, project(advance));
1357 if (validDataForKey((void*) model)) {
1358 setAnchorForKey((void*) model, p);
1362 // draw callsign / altitude / speed
1366 ::snprintf(buffer, 1024, "%s\n%d'\n%dkts",
1367 model->getStringValue("callsign", "<>"),
1368 static_cast<int>(pos.getElevationFt() / 50.0) * 50,
1371 MapData* d = createDataForKey((void*) model);
1373 d->setLabel(model->getStringValue("callsign", "<>"));
1374 d->setPriority(speedKts > 5 ? 60 : 10); // low priority for parked aircraft
1375 d->setOffset(MapData::VALIGN_CENTER | MapData::HALIGN_LEFT, 10);
1380 void MapWidget::drawAIShip(const SGPropertyNode* model, const SGGeod& pos, double hdg)
1385 SGVec2d MapWidget::project(const SGGeod& geod) const
1387 // Sanson-Flamsteed projection, relative to the projection center
1388 double r = earth_radius_lat(geod.getLatitudeRad());
1389 double lonDiff = geod.getLongitudeRad() - _projectionCenter.getLongitudeRad(),
1390 latDiff = geod.getLatitudeRad() - _projectionCenter.getLatitudeRad();
1392 SGVec2d p = SGVec2d(cos(geod.getLatitudeRad()) * lonDiff, latDiff) * r * currentScale();
1394 // rotate as necessary
1395 double cost = cos(_upHeading * SG_DEGREES_TO_RADIANS),
1396 sint = sin(_upHeading * SG_DEGREES_TO_RADIANS);
1397 double rx = cost * p.x() - sint * p.y();
1398 double ry = sint * p.x() + cost * p.y();
1399 return SGVec2d(rx, ry);
1402 SGGeod MapWidget::unproject(const SGVec2d& p) const
1404 // unrotate, if necessary
1405 double cost = cos(-_upHeading * SG_DEGREES_TO_RADIANS),
1406 sint = sin(-_upHeading * SG_DEGREES_TO_RADIANS);
1407 SGVec2d ur(cost * p.x() - sint * p.y(),
1408 sint * p.x() + cost * p.y());
1410 double r = earth_radius_lat(_projectionCenter.getLatitudeRad());
1411 SGVec2d unscaled = ur * (1.0 / (currentScale() * r));
1413 double lat = unscaled.y() + _projectionCenter.getLatitudeRad();
1414 double lon = (unscaled.x() / cos(lat)) + _projectionCenter.getLongitudeRad();
1416 return SGGeod::fromRad(lon, lat);
1419 double MapWidget::currentScale() const
1421 return 1.0 / pow(2.0, _zoom);
1424 void MapWidget::circleAt(const SGVec2d& center, int nSides, double r)
1426 glBegin(GL_LINE_LOOP);
1427 double advance = (SGD_PI * 2) / nSides;
1428 glVertex2d(center.x(), center.y() + r);
1430 for (int i=1; i<nSides; ++i) {
1431 glVertex2d(center.x() + (sin(t) * r), center.y() + (cos(t) * r));
1437 void MapWidget::circleAtAlt(const SGVec2d& center, int nSides, double r, double r2)
1439 glBegin(GL_LINE_LOOP);
1440 double advance = (SGD_PI * 2) / nSides;
1441 glVertex2d(center.x(), center.y() + r);
1443 for (int i=1; i<nSides; ++i) {
1444 double rr = (i%2 == 0) ? r : r2;
1445 glVertex2d(center.x() + (sin(t) * rr), center.y() + (cos(t) * rr));
1451 void MapWidget::drawLine(const SGVec2d& p1, const SGVec2d& p2)
1454 glVertex2dv(p1.data());
1455 glVertex2dv(p2.data());
1459 void MapWidget::drawLegendBox(const SGVec2d& pos, const std::string& t)
1461 std::vector<std::string> lines(simgear::strutils::split(t, "\n"));
1462 const int LINE_LEADING = 4;
1463 const int MARGIN = 4;
1466 int maxWidth = -1, totalHeight = 0;
1467 int lineHeight = legendFont.getStringHeight();
1469 for (unsigned int ln=0; ln<lines.size(); ++ln) {
1470 totalHeight += lineHeight;
1472 totalHeight += LINE_LEADING;
1475 int lw = legendFont.getStringWidth(lines[ln].c_str());
1476 maxWidth = std::max(maxWidth, lw);
1477 } // of line measurement
1480 return; // all lines are empty, don't draw
1483 totalHeight += MARGIN * 2;
1488 box.min[1] = -totalHeight;
1489 box.max[0] = maxWidth + (MARGIN * 2);
1492 box.draw (pos.x(), pos.y(), PUSTYLE_DROPSHADOW, colour, FALSE, border);
1495 int xPos = pos.x() + MARGIN;
1496 int yPos = pos.y() - (lineHeight + MARGIN);
1497 glColor3f(0.8, 0.8, 0.8);
1499 for (unsigned int ln=0; ln<lines.size(); ++ln) {
1500 legendFont.drawString(lines[ln].c_str(), xPos, yPos);
1501 yPos -= lineHeight + LINE_LEADING;
1505 void MapWidget::drawData()
1507 std::sort(_dataQueue.begin(), _dataQueue.end(), MapData::order);
1509 int hw = _width >> 1,
1511 puBox visBox(makePuBox(-hw, -hh, _width, _height));
1515 std::vector<MapData*> drawQueue;
1517 bool drawData = _root->getBoolValue("draw-data");
1518 const int MAX_DRAW_DATA = 25;
1519 const int MAX_DRAW = 50;
1521 for (; (d < _dataQueue.size()) && (drawn < MAX_DRAW); ++d) {
1522 MapData* md = _dataQueue[d];
1523 md->setDataVisible(drawData);
1525 if (md->isClipped(visBox)) {
1529 if (md->overlaps(drawQueue)) {
1530 if (drawData) { // overlapped with data, let's try just the label
1531 md->setDataVisible(false);
1532 if (md->overlaps(drawQueue)) {
1538 } // of overlaps case
1540 drawQueue.push_back(md);
1542 if (drawData && (drawn >= MAX_DRAW_DATA)) {
1547 // draw lowest-priority first, so higher-priorty items appear on top
1548 std::vector<MapData*>::reverse_iterator r;
1549 for (r = drawQueue.rbegin(); r!= drawQueue.rend(); ++r) {
1554 KeyDataMap::iterator it = _mapData.begin();
1555 for (; it != _mapData.end(); ) {
1557 if (it->second->isExpired()) {
1559 KeyDataMap::iterator cur = it++;
1560 _mapData.erase(cur);
1564 } // of expiry iteration
1567 bool MapWidget::validDataForKey(void* key)
1569 KeyDataMap::iterator it = _mapData.find(key);
1570 if (it == _mapData.end()) {
1571 return false; // no valid data for the key!
1574 it->second->resetAge(); // mark data as valid this frame
1575 _dataQueue.push_back(it->second);
1579 void MapWidget::setAnchorForKey(void* key, const SGVec2d& anchor)
1581 KeyDataMap::iterator it = _mapData.find(key);
1582 if (it == _mapData.end()) {
1583 throw sg_exception("no valid data for key!");
1586 it->second->setAnchor(anchor);
1589 MapData* MapWidget::getOrCreateDataForKey(void* key)
1591 KeyDataMap::iterator it = _mapData.find(key);
1592 if (it == _mapData.end()) {
1593 return createDataForKey(key);
1596 it->second->resetAge(); // mark data as valid this frame
1597 _dataQueue.push_back(it->second);
1601 MapData* MapWidget::createDataForKey(void* key)
1603 KeyDataMap::iterator it = _mapData.find(key);
1604 if (it != _mapData.end()) {
1605 throw sg_exception("duplicate data requested for key!");
1608 MapData* d = new MapData(0);
1610 _dataQueue.push_back(d);