1 #ifdef HAVE_CONFIG_H
2 #  include "config.h"
3 #endif
4 
5 #include "MapWidget.hxx"
6 
7 #include <sstream>
8 #include <algorithm> // for std::sort
9 #include <plib/puAux.h>
10 
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>
16 
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>
32 
33 const char* RULER_LEGEND_KEY = "ruler-legend";
34 
35 /* equatorial and polar earth radius */
36 const float rec  = 6378137;          // earth radius, equator (?)
37 const float rpol = 6356752.314f;      // earth radius, polar   (?)
38 
39 /************************************************************************
40   some trigonometric helper functions
41   (translated more or less directly from Alexei Novikovs perl original)
42 *************************************************************************/
43 
44 //Returns Earth radius at a given latitude (Ellipsoide equation with two equal axis)
earth_radius_lat(float lat)45 static float earth_radius_lat( float lat )
46 {
47   double a = cos(lat)/rec;
48   double b = sin(lat)/rpol;
49   return 1.0f / sqrt( a * a + b * b );
50 }
51 
52 ///////////////////////////////////////////////////////////////////////////
53 
makePuBox(int x,int y,int w,int h)54 static puBox makePuBox(int x, int y, int w, int h)
55 {
56   puBox r;
57   r.min[0] = x;
58   r.min[1] = y;
59   r.max[0] =  x + w;
60   r.max[1] = y + h;
61   return r;
62 }
63 
puBoxIntersect(const puBox & a,const puBox & b)64 static bool puBoxIntersect(const puBox& a, const puBox& b)
65 {
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]);
70 
71   return (x0 <= x1) && (y0 <= y1);
72 }
73 
74 class MapData;
75 typedef std::vector<MapData*> MapDataVec;
76 
77 class MapData
78 {
79 public:
80   static const int HALIGN_LEFT = 1;
81   static const int HALIGN_CENTER = 2;
82   static const int HALIGN_RIGHT = 3;
83 
84   static const int VALIGN_TOP = 1 << 4;
85   static const int VALIGN_CENTER = 2 << 4;
86   static const int VALIGN_BOTTOM = 3 << 4;
87 
MapData(int priority)88   MapData(int priority) :
89     _dirtyText(true),
90     _age(0),
91     _priority(priority),
92     _width(0),
93     _height(0),
94     _offsetDir(HALIGN_LEFT | VALIGN_CENTER),
95     _offsetPx(10),
96     _dataVisible(false)
97   {
98   }
99 
setLabel(const std::string & label)100   void setLabel(const std::string& label)
101   {
102     if (label == _label) {
103       return; // common case, and saves invalidation
104     }
105 
106     _label = label;
107     _dirtyText = true;
108   }
109 
setText(const std::string & text)110   void setText(const std::string &text)
111   {
112     if (_rawText == text) {
113       return; // common case, and saves invalidation
114     }
115 
116     _rawText = text;
117     _dirtyText = true;
118   }
119 
setDataVisible(bool vis)120   void setDataVisible(bool vis) {
121     if (vis == _dataVisible) {
122       return;
123     }
124 
125     if (_rawText.empty()) {
126       vis = false;
127     }
128 
129     _dataVisible = vis;
130     _dirtyText = true;
131   }
132 
setFont(puFont f)133   static void setFont(puFont f)
134   {
135     _font = f;
136     _fontHeight = f.getStringHeight();
137     _fontDescender = f.getStringDescender();
138   }
139 
setPalette(puColor * pal)140   static void setPalette(puColor* pal)
141   {
142     _palette = pal;
143   }
144 
setPriority(int pri)145   void setPriority(int pri)
146   {
147     _priority = pri;
148   }
149 
priority() const150   int priority() const
151   { return _priority; }
152 
setAnchor(const SGVec2d & anchor)153   void setAnchor(const SGVec2d& anchor)
154   {
155     _anchor = anchor;
156   }
157 
setOffset(int direction,int px)158   void setOffset(int direction, int px)
159   {
160     if ((_offsetPx == px) && (_offsetDir == direction)) {
161       return;
162     }
163 
164     _dirtyOffset = true;
165     _offsetDir = direction;
166     _offsetPx = px;
167   }
168 
isClipped(const puBox & vis) const169   bool isClipped(const puBox& vis) const
170   {
171     validate();
172     if ((_width < 1) || (_height < 1)) {
173       return true;
174     }
175 
176     return !puBoxIntersect(vis, box());
177   }
178 
overlaps(const MapDataVec & l) const179   bool overlaps(const MapDataVec& l) const
180   {
181     validate();
182     puBox b(box());
183 
184     MapDataVec::const_iterator it;
185     for (it = l.begin(); it != l.end(); ++it) {
186       if (puBoxIntersect(b, (*it)->box())) {
187         return true;
188       }
189     } // of list iteration
190 
191     return false;
192   }
193 
box() const194   puBox box() const
195   {
196     validate();
197     return makePuBox(
198       _anchor.x() + _offset.x(),
199       _anchor.y() + _offset.y(),
200       _width, _height);
201   }
202 
drawStringUtf8(std::string & utf8Str,double x,double y,puFont fnt)203   void drawStringUtf8(std::string& utf8Str, double x, double y, puFont fnt)
204   {
205     fnt.drawString(simgear::strutils::utf8ToLatin1(utf8Str).c_str(), x, y);
206   }
207 
draw()208   void draw()
209   {
210     validate();
211 
212     int xx = _anchor.x() + _offset.x();
213     int yy = _anchor.y() + _offset.y();
214 
215     if (_dataVisible) {
216       puBox box(makePuBox(0,0,_width, _height));
217       int border = 1;
218       box.draw(xx, yy, PUSTYLE_DROPSHADOW, _palette, FALSE, border);
219 
220       // draw lines
221       int lineHeight = _fontHeight;
222       int xPos = xx + MARGIN;
223       int yPos = yy + _height - (lineHeight + MARGIN);
224       glColor3f(0.8, 0.8, 0.8);
225 
226       for (unsigned int ln=0; ln<_lines.size(); ++ln) {
227         drawStringUtf8(_lines[ln], xPos, yPos, _font);
228         yPos -= lineHeight + LINE_LEADING;
229       }
230     } else {
231       glColor3f(0.8, 0.8, 0.8);
232       drawStringUtf8(_label, xx, yy + _fontDescender, _font);
233     }
234   }
235 
age()236   void age()
237   {
238     ++_age;
239   }
240 
resetAge()241   void resetAge()
242   {
243     _age = 0;
244   }
245 
isExpired() const246   bool isExpired() const
247   { return (_age > 100); }
248 
order(MapData * a,MapData * b)249   static bool order(MapData* a, MapData* b)
250   {
251     return a->_priority > b->_priority;
252   }
253 private:
validate() const254   void validate() const
255   {
256     if (!_dirtyText) {
257       if (_dirtyOffset) {
258         computeOffset();
259       }
260 
261       return;
262     }
263 
264     if (_dataVisible) {
265       measureData();
266     } else {
267       measureLabel();
268     }
269 
270     computeOffset();
271     _dirtyText = false;
272   }
273 
measureData() const274   void measureData() const
275   {
276     _lines = simgear::strutils::split(_rawText, "\n");
277   // measure text to find width and height
278     _width = -1;
279     _height = 0;
280 
281     for (unsigned int ln=0; ln<_lines.size(); ++ln) {
282       _height += _fontHeight;
283       if (ln > 0) {
284         _height += LINE_LEADING;
285       }
286 
287       int lw = _font.getStringWidth(_lines[ln].c_str());
288       _width = std::max(_width, lw);
289     } // of line measurement
290 
291     if ((_width < 1) || (_height < 1)) {
292       // will be clipped
293       return;
294     }
295 
296     _height += MARGIN * 2;
297     _width += MARGIN * 2;
298   }
299 
measureLabel() const300   void measureLabel() const
301   {
302     if (_label.empty()) {
303       _width = _height = -1;
304       return;
305     }
306 
307     _height = _fontHeight;
308     _width = _font.getStringWidth(_label.c_str());
309   }
310 
computeOffset() const311   void computeOffset() const
312   {
313     _dirtyOffset = false;
314     if ((_width <= 0) || (_height <= 0)) {
315       return;
316     }
317 
318     int hOffset = 0;
319     int vOffset = 0;
320 
321     switch (_offsetDir & 0x0f) {
322     default:
323     case HALIGN_LEFT:
324       hOffset = _offsetPx;
325       break;
326 
327     case HALIGN_CENTER:
328       hOffset = -(_width>>1);
329       break;
330 
331     case HALIGN_RIGHT:
332       hOffset = -(_offsetPx + _width);
333       break;
334     }
335 
336     switch (_offsetDir & 0xf0) {
337     default:
338     case VALIGN_TOP:
339       vOffset = -(_offsetPx + _height);
340       break;
341 
342     case VALIGN_CENTER:
343       vOffset = -(_height>>1);
344       break;
345 
346     case VALIGN_BOTTOM:
347       vOffset = _offsetPx;
348       break;
349     }
350 
351     _offset = SGVec2d(hOffset, vOffset);
352   }
353 
354   static const int LINE_LEADING = 3;
355 	static const int MARGIN = 3;
356 
357   mutable bool _dirtyText;
358   mutable bool _dirtyOffset;
359   int _age;
360   std::string _rawText;
361   std::string _label;
362   mutable std::vector<std::string> _lines;
363   int _priority;
364   mutable int _width, _height;
365   SGVec2d _anchor;
366   int _offsetDir;
367   int _offsetPx;
368   mutable SGVec2d _offset;
369   bool _dataVisible;
370 
371   static puFont _font;
372   static puColor* _palette;
373   static int _fontHeight;
374   static int _fontDescender;
375 };
376 
377 puFont MapData::_font;
378 puColor* MapData::_palette;
379 int MapData::_fontHeight = 0;
380 int MapData::_fontDescender = 0;
381 
382 ///////////////////////////////////////////////////////////////////////////
383 
384 // anonymous namespace
385 namespace
386 {
387 
388 class MapAirportFilter : public FGAirport::AirportFilter
389 {
390 public:
MapAirportFilter(SGPropertyNode_ptr nd)391     MapAirportFilter(SGPropertyNode_ptr nd) :
392         _heliports(nd->getBoolValue("draw-heliports", false)),
393         _hardRunwaysOnly( nd->getBoolValue("hard-surfaced-airports", true)),
394         _minLengthFt(fgGetDouble("/sim/navdb/min-runway-length-ft", 2000))
395     {
396     }
397 
maxType() const398     FGPositioned::Type maxType() const override
399     {
400         return _heliports ? FGPositioned::HELIPORT : FGPositioned::AIRPORT;
401     }
402 
minType() const403     FGPositioned::Type minType() const override
404     {
405         return FGPositioned::AIRPORT;
406     }
407 
passAirport(FGAirport * aApt) const408     bool passAirport(FGAirport* aApt) const override
409     {
410         if (_hardRunwaysOnly && !aApt->isHeliport()) {
411             return aApt->hasHardRunwayOfLengthFt(_minLengthFt);
412         }
413         return (aApt->type() <= maxType()) && (aApt->type() >= minType());
414     }
415 
showAll()416     void showAll()
417     {
418         _hardRunwaysOnly = false;
419     }
420 
421 private:
422     const bool _heliports;
423     bool _hardRunwaysOnly;
424     const double _minLengthFt;
425 };
426 
427 class NavaidFilter : public FGPositioned::Filter
428 {
429 public:
NavaidFilter(bool fixesEnabled,bool navaidsEnabled)430     NavaidFilter(bool fixesEnabled, bool navaidsEnabled) :
431     _fixes(fixesEnabled),
432     _navaids(navaidsEnabled)
433     {}
434 
pass(FGPositioned * aPos) const435     virtual bool pass(FGPositioned* aPos) const {
436         if (_fixes && (aPos->type() == FGPositioned::FIX)) {
437             // ignore fixes which end in digits - expirmental
438             if (aPos->ident().length() > 4 && isdigit(aPos->ident()[3]) && isdigit(aPos->ident()[4])) {
439                 return false;
440             }
441         }
442 
443         return true;
444     }
445 
minType() const446     virtual FGPositioned::Type minType() const {
447         return _fixes ? FGPositioned::FIX : FGPositioned::NDB;
448     }
449 
maxType() const450     virtual FGPositioned::Type maxType() const {
451         return _navaids ? FGPositioned::VOR : FGPositioned::FIX;
452     }
453 
454 private:
455     bool _fixes, _navaids;
456 };
457 
458 } // of anonymous namespace
459 
460 const int MAX_ZOOM = 12;
461 const int SHOW_DETAIL_ZOOM = 8;
462 const int SHOW_DETAIL2_ZOOM = 5;
463 //const int CURSOR_PAN_STEP = 32;
464 
MapWidget(int x,int y,int maxX,int maxY)465 MapWidget::MapWidget(int x, int y, int maxX, int maxY) :
466   puObject(x,y,maxX, maxY)
467 {
468   _route = static_cast<FGRouteMgr*>(globals->get_subsystem("route-manager"));
469   _gps = fgGetNode("/instrumentation/gps");
470 
471   _width = maxX - x;
472   _height = maxY - y;
473   _hasPanned = false;
474   _projection = PROJECTION_AZIMUTHAL_EQUIDISTANT;
475   _magneticHeadings = false;
476 
477   MapData::setFont(legendFont);
478   MapData::setPalette(colour);
479 
480   _magVar = new SGMagVar();
481 }
482 
~MapWidget()483 MapWidget::~MapWidget()
484 {
485   delete _magVar;
486   clearData();
487 }
488 
setProperty(SGPropertyNode_ptr prop)489 void MapWidget::setProperty(SGPropertyNode_ptr prop)
490 {
491   _root = prop;
492   int zoom = _root->getIntValue("zoom", -1);
493   if (zoom < 0) {
494     _root->setIntValue("zoom", 6); // default zoom
495   }
496 
497 // expose MAX_ZOOM to the UI
498   _root->setIntValue("max-zoom", MAX_ZOOM);
499   _root->setBoolValue("centre-on-aircraft", true);
500   _root->setBoolValue("draw-data", false);
501   _root->setBoolValue("draw-flight-history", false);
502   _root->setBoolValue("magnetic-headings", true);
503 
504   /* If /gui/map/key-pan is undefined, fgdata's gui/dialogs/map.xml will set it
505   to "" when it opens map, so if we see this we change to default value of 1.
506   */
507   if (!strcmp( _root->getStringValue("key-pan"), "")) {
508     _root->setIntValue("key-pan", 1);
509   }
510 }
511 
setSize(int w,int h)512 void MapWidget::setSize(int w, int h)
513 {
514   puObject::setSize(w, h);
515 
516   _width = w;
517   _height = h;
518 
519 }
520 
doHit(int button,int updown,int x,int y)521 void MapWidget::doHit( int button, int updown, int x, int y )
522 {
523   puObject::doHit(button, updown, x, y);
524   if (updown == PU_DRAG) {
525     handlePan(x, y);
526     return;
527   }
528   if (updown == PU_DOWN)
529   {
530       if (button == 3) { // mouse-wheel up
531           zoomIn();
532       }
533       else if (button == 4) { // mouse-wheel down
534           zoomOut();
535       }
536   }
537   _hitLocation = SGVec2d(x - abox.min[0], y - abox.min[1]);
538   if ((button == 2) && (updown == PU_DOWN)) {
539     _clickGeod = unproject(_hitLocation - SGVec2d(_width>>1, _height>>1));
540   }
541 
542   if (button != active_mouse_button) {
543     return;
544   }
545 
546   if (updown == PU_UP) {
547     puDeactivateWidget();
548   } else if (updown == PU_DOWN) {
549     puSetActiveWidget(this, x, y);
550   }
551 }
552 
handlePan(int x,int y)553 void MapWidget::handlePan(int x, int y)
554 {
555   SGVec2d delta = SGVec2d(x, y) - _hitLocation;
556   pan(delta);
557   _hitLocation = SGVec2d(x,y);
558 }
559 
560 #if 0
561 int MapWidget::checkKey (int key, int updown )
562 {
563   if ((updown == PU_UP) || !isVisible () || !isActive () || (window != puGetWindow())) {
564     return FALSE ;
565   }
566 
567   bool key_pan = _root->getIntValue("key-pan");
568   if (!key_pan && (0
569         || key == PU_KEY_UP
570         || key == PU_KEY_DOWN
571         || key == PU_KEY_LEFT
572         || key == PU_KEY_RIGHT
573         )) {
574     return FALSE;
575   }
576 
577   switch (key)
578   {
579 
580   case PU_KEY_UP:
581     pan(SGVec2d(0, -CURSOR_PAN_STEP));
582     break;
583 
584   case PU_KEY_DOWN:
585     pan(SGVec2d(0, CURSOR_PAN_STEP));
586     break ;
587 
588   case PU_KEY_LEFT:
589     pan(SGVec2d(CURSOR_PAN_STEP, 0));
590     break;
591 
592   case PU_KEY_RIGHT:
593     pan(SGVec2d(-CURSOR_PAN_STEP, 0));
594     break;
595 
596   case '-':
597     zoomOut();
598 
599     break;
600 
601   case '=':
602     zoomIn();
603     break;
604 
605   default :
606     return FALSE;
607   }
608 
609   return TRUE ;
610 }
611 #endif
612 
613 
pan(const SGVec2d & delta)614 void MapWidget::pan(const SGVec2d& delta)
615 {
616   _hasPanned = true;
617   _projectionCenter = unproject(-delta);
618 }
619 
zoom() const620 int MapWidget::zoom() const
621 {
622   int z = _root->getIntValue("zoom");
623   SG_CLAMP_RANGE(z, 0, MAX_ZOOM);
624   return z;
625 }
626 
zoomIn()627 void MapWidget::zoomIn()
628 {
629   if (zoom() >= MAX_ZOOM) {
630     return;
631   }
632 
633   _root->setIntValue("zoom", zoom() + 1);
634 }
635 
zoomOut()636 void MapWidget::zoomOut()
637 {
638   if (zoom() <= 0) {
639     return;
640   }
641 
642   _root->setIntValue("zoom", zoom() - 1);
643 }
644 
update()645 void MapWidget::update()
646 {
647     _aircraft = globals->get_aircraft_position();
648 
649     bool mag = _root->getBoolValue("magnetic-headings");
650     if (mag != _magneticHeadings) {
651         clearData(); // flush cached data text, since it often includes heading
652         _magneticHeadings =  mag;
653     }
654 
655     if (_hasPanned) {
656         _root->setBoolValue("centre-on-aircraft", false);
657         _hasPanned = false;
658     }
659     else if (_root->getBoolValue("centre-on-aircraft")) {
660         _projectionCenter = _aircraft;
661     }
662 
663     double julianDate = globals->get_time_params()->getJD();
664     _magVar->update(_projectionCenter, julianDate);
665 
666     _aircraftUp = _root->getBoolValue("aircraft-heading-up");
667     if (_aircraftUp) {
668         _upHeading = fgGetDouble("/orientation/heading-deg");
669     } else {
670         _upHeading = 0.0;
671     }
672 
673     if (_magneticHeadings) {
674         _displayHeading = (int) fgGetDouble("/orientation/heading-magnetic-deg");
675     } else {
676         _displayHeading = (int) _upHeading;
677     }
678 
679     _cachedZoom = MAX_ZOOM - zoom();
680     SGGeod topLeft = unproject(SGVec2d(_width/2, _height/2));
681     // compute draw range, including a fudge factor for ILSs and other 'long'
682     // symbols.
683     _drawRangeNm = SGGeodesy::distanceNm(_projectionCenter, topLeft) + 10.0;
684 
685     FGFlightHistory* history = (FGFlightHistory*) globals->get_subsystem("history");
686     if (history && _root->getBoolValue("draw-flight-history")) {
687         _flightHistoryPath = history->pathForHistory();
688     } else {
689         _flightHistoryPath.clear();
690     }
691 
692 // make spatial queries. This can trigger loading of XML files, etc, so we do
693 // not want to do it in draw(), which can be called from an arbitrary OSG
694 // rendering thread.
695 
696     MapAirportFilter af(_root);
697     if (_cachedZoom <= SHOW_DETAIL2_ZOOM) {
698         // show all airports when zoomed in sufficently
699         af.showAll();
700     }
701 
702     bool partial = false;
703     FGPositionedList newItemsToDraw =
704         FGPositioned::findWithinRangePartial(_projectionCenter, _drawRangeNm, &af, partial);
705 
706     bool fixes = _root->getBoolValue("draw-fixes");
707     NavaidFilter f(fixes, _root->getBoolValue("draw-navaids"));
708     if (f.minType() <= f.maxType()) {
709         FGPositionedList navs = FGPositioned::findWithinRange(_projectionCenter, _drawRangeNm, &f);
710         newItemsToDraw.insert(newItemsToDraw.end(), navs.begin(), navs.end());
711     }
712 
713     FGPositioned::TypeFilter tf(FGPositioned::COUNTRY);
714     if (_cachedZoom <= SHOW_DETAIL_ZOOM) {
715         tf.addType(FGPositioned::CITY);
716     }
717 
718     if (_cachedZoom <= SHOW_DETAIL2_ZOOM) {
719         tf.addType(FGPositioned::TOWN);
720     }
721 
722     FGPositionedList poi = FGPositioned::findWithinRange(_projectionCenter, _drawRangeNm, &tf);
723     newItemsToDraw.insert(newItemsToDraw.end(), poi.begin(), poi.end());
724 
725     _itemsToDraw.swap(newItemsToDraw);
726 
727     updateAIObjects();
728 }
729 
updateAIObjects()730 void MapWidget::updateAIObjects()
731 {
732     if (!_root->getBoolValue("draw-traffic") || (_cachedZoom > SHOW_DETAIL_ZOOM)) {
733         _aiDrawVec.clear();
734         return;
735     }
736 
737     AIDrawVec newDrawVec;
738 
739     const SGPropertyNode* ai = fgGetNode("/ai/models", true);
740     for (int i = 0; i < ai->nChildren(); ++i) {
741         const SGPropertyNode *model = ai->getChild(i);
742         // skip bad or dead entries
743         if (!model || model->getIntValue("id", -1) == -1) {
744             continue;
745         }
746 
747         SGGeod pos = SGGeod::fromDegFt(
748                                        model->getDoubleValue("position/longitude-deg"),
749                                        model->getDoubleValue("position/latitude-deg"),
750                                        model->getDoubleValue("position/altitude-ft"));
751 
752         double dist = SGGeodesy::distanceNm(_projectionCenter, pos);
753         if (dist > _drawRangeNm) {
754             continue;
755         }
756 
757         newDrawVec.push_back(DrawAIObject((SGPropertyNode*) model, pos));
758     } // of ai/models iteration
759 
760     _aiDrawVec.swap(newDrawVec);
761 }
762 
draw(int dx,int dy)763 void MapWidget::draw(int dx, int dy)
764 {
765   GLint sx = (int) abox.min[0],
766     sy = (int) abox.min[1];
767   glScissor(dx + sx, dy + sy, _width, _height);
768   glEnable(GL_SCISSOR_TEST);
769 
770   glMatrixMode(GL_MODELVIEW);
771   glPushMatrix();
772   // center drawing about the widget center (which is also the
773   // projection centre)
774   glTranslated(dx + sx + (_width/2), dy + sy + (_height/2), 0.0);
775 
776   drawLatLonGrid();
777 
778   if (_aircraftUp) {
779     int textHeight = legendFont.getStringHeight() + 5;
780 
781     // draw heading line
782     SGVec2d loc = project(_aircraft);
783     glColor3f(1.0, 1.0, 1.0);
784     drawLine(loc, SGVec2d(loc.x(), (_height / 2) - textHeight));
785 
786     double y = (_height / 2) - textHeight;
787     char buf[16];
788     ::snprintf(buf, 16, "%d", _displayHeading);
789     int sw = legendFont.getStringWidth(buf);
790     legendFont.drawString(buf, loc.x() - sw/2, y);
791   }
792 
793   drawPositioned();
794   drawTraffic();
795   drawGPSData();
796   drawNavRadio(fgGetNode("/instrumentation/nav[0]", false));
797   drawNavRadio(fgGetNode("/instrumentation/nav[1]", false));
798   paintAircraftLocation(_aircraft);
799   drawFlightHistory();
800   paintRoute();
801   paintRuler();
802 
803   drawData();
804 
805   glPopMatrix();
806   glDisable(GL_SCISSOR_TEST);
807 }
808 
paintRuler()809 void MapWidget::paintRuler()
810 {
811   if (_clickGeod == SGGeod()) {
812     return;
813   }
814 
815   SGVec2d acftPos = project(_aircraft);
816   SGVec2d clickPos = project(_clickGeod);
817 
818   glColor3f(0.0, 1.0, 1.0);
819   drawLine(acftPos, clickPos);
820 
821   circleAtAlt(clickPos, 8, 10, 5);
822 
823   double dist, az, az2;
824   SGGeodesy::inverse(_aircraft, _clickGeod, az, az2, dist);
825   char buffer[1024];
826 	::snprintf(buffer, 1024, "%03d/%.1fnm",
827 		displayHeading(az), dist * SG_METER_TO_NM);
828 
829   MapData* d = getOrCreateDataForKey((void*) RULER_LEGEND_KEY);
830   d->setLabel(buffer);
831   d->setAnchor(clickPos);
832   d->setOffset(MapData::VALIGN_TOP | MapData::HALIGN_CENTER, 15);
833   d->setPriority(20000);
834 }
835 
paintAircraftLocation(const SGGeod & aircraftPos)836 void MapWidget::paintAircraftLocation(const SGGeod& aircraftPos)
837 {
838   SGVec2d loc = project(aircraftPos);
839 
840   double hdg = fgGetDouble("/orientation/heading-deg");
841 
842   glLineWidth(2.0);
843   glColor3f(1.0, 1.0, 0.0);
844   glPushMatrix();
845   glTranslated(loc.x(), loc.y(), 0.0);
846   glRotatef(hdg - _upHeading, 0.0, 0.0, -1.0);
847 
848   const SGVec2d wingspan(12, 0);
849   const SGVec2d nose(0, 8);
850   const SGVec2d tail(0, -14);
851   const SGVec2d tailspan(4,0);
852 
853   drawLine(-wingspan, wingspan);
854   drawLine(nose, tail);
855   drawLine(tail - tailspan, tail + tailspan);
856 
857   glPopMatrix();
858   glLineWidth(1.0);
859 }
860 
paintRoute()861 void MapWidget::paintRoute()
862 {
863   if (_route->numWaypts() < 2) {
864     return;
865   }
866 
867   RoutePath path(_route->flightPlan());
868 
869 // first pass, draw the actual lines
870   glLineWidth(2.0);
871 
872   for (int w=0; w<_route->numWaypts(); ++w) {
873     SGGeodVec gv(path.pathForIndex(w));
874     if (gv.empty()) {
875       continue;
876     }
877 
878     if (w < _route->currentIndex()) {
879       glColor3f(0.5, 0.5, 0.5);
880     } else {
881       glColor3f(1.0, 0.0, 1.0);
882     }
883 
884     flightgear::WayptRef wpt(_route->wayptAtIndex(w));
885     if (wpt->flag(flightgear::WPT_MISS)) {
886       glEnable(GL_LINE_STIPPLE);
887       glLineStipple(1, 0x00FF);
888     }
889 
890     glBegin(GL_LINE_STRIP);
891     for (unsigned int i=0; i<gv.size(); ++i) {
892       SGVec2d p = project(gv[i]);
893       glVertex2d(p.x(), p.y());
894     }
895 
896     glEnd();
897     glDisable(GL_LINE_STIPPLE);
898   }
899 
900   glLineWidth(1.0);
901 // second pass, draw waypoint symbols and data
902   for (int w=0; w < _route->numWaypts(); ++w) {
903     flightgear::WayptRef wpt(_route->wayptAtIndex(w));
904     SGGeod g = path.positionForIndex(w);
905     if (g == SGGeod()) {
906       continue; // Vectors or similar
907     }
908 
909     SGVec2d p = project(g);
910     glColor3f(1.0, 0.0, 1.0);
911     circleAtAlt(p, 8, 12, 5);
912 
913     std::ostringstream legend;
914     legend << wpt->ident();
915     if (wpt->altitudeRestriction() != flightgear::RESTRICT_NONE) {
916       legend << '\n' << SGMiscd::roundToInt(wpt->altitudeFt()) << '\'';
917     }
918 
919     if (wpt->speedRestriction() == flightgear::SPEED_RESTRICT_MACH) {
920       legend << '\n' << wpt->speedMach() << "M";
921     } else if (wpt->speedRestriction() != flightgear::RESTRICT_NONE) {
922       legend << '\n' << SGMiscd::roundToInt(wpt->speedKts()) << "Kts";
923     }
924 
925     MapData* d = getOrCreateDataForKey(reinterpret_cast<void*>(w * 2));
926     d->setText(legend.str());
927     d->setLabel(wpt->ident());
928     d->setAnchor(p);
929     d->setOffset(MapData::VALIGN_TOP | MapData::HALIGN_CENTER, 15);
930     d->setPriority(w < _route->currentIndex() ? 9000 : 12000);
931 
932   } // of second waypoint iteration
933 }
934 
drawFlightHistory()935 void MapWidget::drawFlightHistory()
936 {
937   if (_flightHistoryPath.empty())
938     return;
939 
940   // first pass, draw the actual lines
941   glLineWidth(2.0);
942 
943   glColor3f(0.0, 0.0, 1.0);
944 
945   glBegin(GL_LINE_STRIP);
946   for (unsigned int i=0; i<_flightHistoryPath.size(); ++i) {
947     SGVec2d p = project(_flightHistoryPath[i]);
948     glVertex2d(p.x(), p.y());
949   }
950 
951   glEnd();
952 }
953 
954 /**
955  * Round a SGGeod to an arbitrary precision.
956  * For example, passing precision of 0.5 will round to the nearest 0.5 of
957  * a degree in both lat and lon - passing in 3.0 rounds to the nearest 3 degree
958  * multiple, and so on.
959  */
roundGeod(double precision,const SGGeod & g)960 static SGGeod roundGeod(double precision, const SGGeod& g)
961 {
962   double lon = SGMiscd::round(g.getLongitudeDeg() / precision);
963   double lat = SGMiscd::round(g.getLatitudeDeg() / precision);
964 
965   return SGGeod::fromDeg(lon * precision, lat * precision);
966 }
967 
drawLineClipped(const SGVec2d & a,const SGVec2d & b)968 bool MapWidget::drawLineClipped(const SGVec2d& a, const SGVec2d& b)
969 {
970   double minX = SGMiscd::min(a.x(), b.x()),
971     minY = SGMiscd::min(a.y(), b.y()),
972     maxX = SGMiscd::max(a.x(), b.x()),
973     maxY = SGMiscd::max(a.y(), b.y());
974 
975   int hh = _height >> 1, hw = _width >> 1;
976 
977   if ((maxX < -hw) || (minX > hw) || (minY > hh) || (maxY < -hh)) {
978     return false;
979   }
980 
981   glVertex2dv(a.data());
982   glVertex2dv(b.data());
983   return true;
984 }
985 
gridPoint(int ix,int iy)986 SGVec2d MapWidget::gridPoint(int ix, int iy)
987 {
988 	int key = (ix + 0x7fff) | ((iy + 0x7fff) << 16);
989 	GridPointCache::iterator it = _gridCache.find(key);
990 	if (it != _gridCache.end()) {
991 		return it->second;
992 	}
993 
994 	SGGeod gp = SGGeod::fromDeg(
995     _gridCenter.getLongitudeDeg() + ix * _gridSpacing,
996 		_gridCenter.getLatitudeDeg() + iy * _gridSpacing);
997 
998 	SGVec2d proj = project(gp);
999 	_gridCache[key] = proj;
1000 	return proj;
1001 }
1002 
drawLatLonGrid()1003 void MapWidget::drawLatLonGrid()
1004 {
1005   // Larger grid spacing when zoomed out, to prevent clutter
1006   if (_cachedZoom < SHOW_DETAIL_ZOOM) {
1007     _gridSpacing = 1.0;
1008   } else {
1009     _gridSpacing = 5.0;
1010   }
1011 
1012   _gridCenter = roundGeod(_gridSpacing, _projectionCenter);
1013   _gridCache.clear();
1014 
1015   int ix = 0;
1016   int iy = 0;
1017 
1018   glColor3f(0.8, 0.8, 0.8);
1019   glBegin(GL_LINES);
1020   bool didDraw;
1021   do {
1022     didDraw = false;
1023     ++ix;
1024     ++iy;
1025 
1026     for (int x = -ix; x < ix; ++x) {
1027       didDraw |= drawLineClipped(gridPoint(x, -iy), gridPoint(x+1, -iy));
1028       didDraw |= drawLineClipped(gridPoint(x, iy), gridPoint(x+1, iy));
1029       didDraw |= drawLineClipped(gridPoint(x, -iy), gridPoint(x, -iy + 1));
1030       didDraw |= drawLineClipped(gridPoint(x, iy), gridPoint(x, iy - 1));
1031     }
1032 
1033     for (int y = -iy; y < iy; ++y) {
1034       didDraw |= drawLineClipped(gridPoint(-ix, y), gridPoint(-ix, y+1));
1035       didDraw |= drawLineClipped(gridPoint(-ix, y), gridPoint(-ix + 1, y));
1036       didDraw |= drawLineClipped(gridPoint(ix, y), gridPoint(ix, y+1));
1037       didDraw |= drawLineClipped(gridPoint(ix, y), gridPoint(ix - 1, y));
1038     }
1039 
1040     if (ix > (90/_gridSpacing)-1) {
1041       break;
1042     }
1043   } while (didDraw);
1044 
1045   glEnd();
1046 }
1047 
drawGPSData()1048 void MapWidget::drawGPSData()
1049 {
1050   std::string gpsMode = _gps->getStringValue("mode");
1051 
1052   SGGeod wp0Geod = SGGeod::fromDeg(
1053         _gps->getDoubleValue("wp/wp[0]/longitude-deg"),
1054         _gps->getDoubleValue("wp/wp[0]/latitude-deg"));
1055 
1056   SGGeod wp1Geod = SGGeod::fromDeg(
1057         _gps->getDoubleValue("wp/wp[1]/longitude-deg"),
1058         _gps->getDoubleValue("wp/wp[1]/latitude-deg"));
1059 
1060 // draw track line
1061   double gpsTrackDeg = _gps->getDoubleValue("indicated-track-true-deg");
1062   double gpsSpeed = _gps->getDoubleValue("indicated-ground-speed-kt");
1063   double az2;
1064 
1065   if (gpsSpeed > 3.0) { // only draw track line if valid
1066     SGGeod trackRadial;
1067     SGGeodesy::direct(_aircraft, gpsTrackDeg, _drawRangeNm * SG_NM_TO_METER, trackRadial, az2);
1068 
1069     glColor3f(1.0, 1.0, 0.0);
1070     glEnable(GL_LINE_STIPPLE);
1071     glLineStipple(1, 0x00FF);
1072     drawLine(project(_aircraft), project(trackRadial));
1073     glDisable(GL_LINE_STIPPLE);
1074   }
1075 
1076   if (gpsMode == "dto") {
1077     SGVec2d wp0Pos = project(wp0Geod);
1078     SGVec2d wp1Pos = project(wp1Geod);
1079 
1080     glColor3f(1.0, 0.0, 1.0);
1081     drawLine(wp0Pos, wp1Pos);
1082 
1083   }
1084 
1085   if (_gps->getBoolValue("scratch/valid")) {
1086     // draw scratch data
1087 
1088   }
1089 }
1090 
drawPositioned()1091 void MapWidget::drawPositioned()
1092 {
1093   for (unsigned int i=0; i<_itemsToDraw.size(); ++i) {
1094       FGPositionedRef p = _itemsToDraw[i];
1095       switch (p->type()) {
1096           case FGPositioned::AIRPORT:
1097               drawAirport(fgpositioned_cast<FGAirport>(p));
1098               break;
1099           case FGPositioned::HELIPORT:
1100               drawHeliport(fgpositioned_cast<FGAirport>(p));
1101               break;
1102           case FGPositioned::NDB:
1103               drawNDB(false, fgpositioned_cast<FGNavRecord>(p));
1104               break;
1105           case FGPositioned::VOR:
1106               drawVOR(false, fgpositioned_cast<FGNavRecord>(p));
1107               break;
1108           case FGPositioned::FIX:
1109               drawFix(fgpositioned_cast<FGFix>(p));
1110               break;
1111          case FGPositioned::TOWN:
1112           case FGPositioned::CITY:
1113           case FGPositioned::COUNTRY:
1114               drawPOI(p);
1115               break;
1116 
1117           default:
1118               SG_LOG(SG_GENERAL, SG_WARN, "unhandled type in MapWidget::drawPositioned");
1119       } // of positioned type switch
1120   } // of items to draw iteration
1121 }
1122 
drawNDB(bool tuned,FGNavRecord * ndb)1123 void MapWidget::drawNDB(bool tuned, FGNavRecord* ndb)
1124 {
1125   SGVec2d pos = project(ndb->geod());
1126 
1127   if (tuned) {
1128     glColor3f(0.0, 1.0, 1.0);
1129   } else {
1130     glColor3f(0.0, 0.0, 0.0);
1131   }
1132 
1133   glEnable(GL_LINE_STIPPLE);
1134   glLineStipple(1, 0x00FF);
1135   circleAt(pos, 20, 6);
1136   circleAt(pos, 20, 10);
1137   glDisable(GL_LINE_STIPPLE);
1138 
1139   if (validDataForKey(ndb)) {
1140     setAnchorForKey(ndb, pos);
1141     return;
1142   }
1143 
1144   char buffer[1024];
1145 	::snprintf(buffer, 1024, "%s\n%s %3.0fKhz",
1146 		ndb->name().c_str(), ndb->ident().c_str(),ndb->get_freq()/100.0);
1147 
1148   MapData* d = createDataForKey(ndb);
1149   d->setPriority(40);
1150   d->setLabel(ndb->ident());
1151   d->setText(buffer);
1152   d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 10);
1153   d->setAnchor(pos);
1154 
1155 }
1156 
drawVOR(bool tuned,FGNavRecord * vor)1157 void MapWidget::drawVOR(bool tuned, FGNavRecord* vor)
1158 {
1159   SGVec2d pos = project(vor->geod());
1160   if (tuned) {
1161     glColor3f(0.0, 1.0, 1.0);
1162   } else {
1163     glColor3f(0.0, 0.0, 1.0);
1164   }
1165 
1166   circleAt(pos, 6, 9);
1167   circleAt(pos, 8, 1);
1168 
1169   if (vor->hasDME())
1170   squareAt(pos, 9);
1171 
1172   if (validDataForKey(vor)) {
1173     setAnchorForKey(vor, pos);
1174     return;
1175   }
1176 
1177   char buffer[1024];
1178 	::snprintf(buffer, 1024, "%s\n%s %6.3fMhz",
1179 		vor->name().c_str(), vor->ident().c_str(),
1180     vor->get_freq() / 100.0);
1181 
1182   MapData* d = createDataForKey(vor);
1183   d->setText(buffer);
1184   d->setLabel(vor->ident());
1185   d->setPriority(tuned ? 10000 : 100);
1186   d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 12);
1187   d->setAnchor(pos);
1188 }
1189 
drawFix(FGFix * fix)1190 void MapWidget::drawFix(FGFix* fix)
1191 {
1192   SGVec2d pos = project(fix->geod());
1193   glColor3f(0.0, 0.0, 0.0);
1194   circleAt(pos, 3, 6);
1195 
1196   if (_cachedZoom > SHOW_DETAIL_ZOOM) {
1197     return; // hide fix labels beyond a certain zoom level
1198   }
1199 
1200   if (validDataForKey(fix)) {
1201     setAnchorForKey(fix, pos);
1202     return;
1203   }
1204 
1205   MapData* d = createDataForKey(fix);
1206   d->setLabel(fix->ident());
1207   d->setPriority(20);
1208   d->setOffset(MapData::VALIGN_CENTER | MapData::HALIGN_LEFT, 10);
1209   d->setAnchor(pos);
1210 }
1211 
drawNavRadio(SGPropertyNode_ptr radio)1212 void MapWidget::drawNavRadio(SGPropertyNode_ptr radio)
1213 {
1214   if (!radio || radio->getBoolValue("slaved-to-gps", false)
1215         || !radio->getBoolValue("in-range", false)) {
1216     return;
1217   }
1218 
1219   if (radio->getBoolValue("nav-loc", false)) {
1220     drawTunedLocalizer(radio);
1221   }
1222 
1223   // identify the tuned station - unfortunately we don't get lat/lon directly,
1224   // need to do the frequency search again
1225   double mhz = radio->getDoubleValue("frequencies/selected-mhz", 0.0);
1226 
1227   FGNavRecord* nav = FGNavList::findByFreq(mhz, _aircraft,
1228                                            FGNavList::navFilter());
1229   if (!nav || (nav->ident() != radio->getStringValue("nav-id"))) {
1230     // mismatch between navradio selection logic and ours!
1231     return;
1232   }
1233 
1234   glLineWidth(1.0);
1235   drawVOR(true, nav);
1236 
1237   SGVec2d pos = project(nav->geod());
1238   SGGeod range;
1239   double az2;
1240   double trueRadial = radio->getDoubleValue("radials/target-radial-deg");
1241   SGGeodesy::direct(nav->geod(), trueRadial, nav->get_range() * SG_NM_TO_METER, range, az2);
1242   SGVec2d prange = project(range);
1243 
1244   SGVec2d norm = normalize(prange - pos);
1245   SGVec2d perp(norm.y(), -norm.x());
1246 
1247   circleAt(pos, 64, length(prange - pos));
1248   drawLine(pos, prange);
1249 
1250 // draw to/from arrows
1251   SGVec2d midPoint = (pos + prange) * 0.5;
1252   if (radio->getBoolValue("from-flag")) {
1253     norm = -norm;
1254     perp = -perp;
1255   }
1256 
1257   int sz = 10;
1258   SGVec2d arrowB = midPoint - (norm * sz) + (perp * sz);
1259   SGVec2d arrowC = midPoint - (norm * sz) - (perp * sz);
1260   drawLine(midPoint, arrowB);
1261   drawLine(arrowB, arrowC);
1262   drawLine(arrowC, midPoint);
1263 
1264   drawLine(pos, (2 * pos) - prange); // reciprocal radial
1265 }
1266 
drawTunedLocalizer(SGPropertyNode_ptr radio)1267 void MapWidget::drawTunedLocalizer(SGPropertyNode_ptr radio)
1268 {
1269   double mhz = radio->getDoubleValue("frequencies/selected-mhz", 0.0);
1270   FGNavRecord* loc = FGNavList::findByFreq(mhz, _aircraft, FGNavList::locFilter());
1271   if (!loc || (loc->ident() != radio->getStringValue("nav-id"))) {
1272     // mismatch between navradio selection logic and ours!
1273     return;
1274   }
1275 
1276   if (loc->runway()) {
1277     drawILS(true, loc->runway());
1278   }
1279 }
1280 
drawPOI(FGPositioned * poi)1281 void MapWidget::drawPOI(FGPositioned* poi)
1282 {
1283   SGVec2d pos = project(poi->geod());
1284   glColor3f(1.0, 1.0, 0.0);
1285   glLineWidth(1.0);
1286 
1287     int radius = 10;
1288     if (poi->type() == FGPositioned::CITY) {
1289         radius = 8;
1290         glColor3f(0.0, 1.0, 0.0);
1291     } else if (poi->type() == FGPositioned::TOWN) {
1292         radius =  5;
1293         glColor3f(0.2, 1.0, 0.0);
1294     }
1295 
1296   circleAt(pos, 4, radius);
1297 
1298   if (validDataForKey(poi)) {
1299     setAnchorForKey(poi, pos);
1300     return;
1301   }
1302 
1303   char buffer[1024];
1304         ::snprintf(buffer, 1024, "%s",
1305                 poi->name().c_str());
1306 
1307   MapData* d = createDataForKey(poi);
1308   d->setPriority(200);
1309   d->setLabel(poi->ident());
1310   d->setText(buffer);
1311   d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 10);
1312   d->setAnchor(pos);
1313 }
1314 
1315 /*
1316 void MapWidget::drawObstacle(FGPositioned* obs)
1317 {
1318   SGVec2d pos = project(obs->geod());
1319   glColor3f(0.0, 0.0, 0.0);
1320   glLineWidth(2.0);
1321   drawLine(pos, pos + SGVec2d());
1322 }
1323 */
1324 
drawAirport(FGAirport * apt)1325 void MapWidget::drawAirport(FGAirport* apt)
1326 {
1327 	// draw tower location
1328 	SGVec2d towerPos = project(apt->getTowerLocation());
1329 
1330   if (_cachedZoom <= SHOW_DETAIL_ZOOM) {
1331     glColor3f(1.0, 1.0, 1.0);
1332     glLineWidth(1.0);
1333 
1334     drawLine(towerPos + SGVec2d(3, 0), towerPos + SGVec2d(3, 10));
1335     drawLine(towerPos + SGVec2d(-3, 0), towerPos + SGVec2d(-3, 10));
1336     drawLine(towerPos + SGVec2d(-6, 20), towerPos + SGVec2d(-3, 10));
1337     drawLine(towerPos + SGVec2d(6, 20), towerPos + SGVec2d(3, 10));
1338     drawLine(towerPos + SGVec2d(-6, 20), towerPos + SGVec2d(6, 20));
1339   }
1340 
1341   if (validDataForKey(apt)) {
1342     setAnchorForKey(apt, towerPos);
1343   } else {
1344     char buffer[1024];
1345     ::snprintf(buffer, 1024, "%s\n%s",
1346       apt->ident().c_str(), apt->name().c_str());
1347 
1348     MapData* d = createDataForKey(apt);
1349     d->setText(buffer);
1350     d->setLabel(apt->ident());
1351     d->setPriority(100 + scoreAirportRunways(apt));
1352     d->setOffset(MapData::VALIGN_TOP | MapData::HALIGN_CENTER, 6);
1353     d->setAnchor(towerPos);
1354   }
1355 
1356   if (_cachedZoom > SHOW_DETAIL_ZOOM) {
1357     return;
1358   }
1359 
1360   FGRunwayList runways(apt->getRunwaysWithoutReciprocals());
1361 
1362   for (unsigned int r=0; r<runways.size(); ++r) {
1363     drawRunwayPre(runways[r]);
1364   }
1365 
1366   for (unsigned int r=0; r<runways.size(); ++r) {
1367     FGRunway* rwy = runways[r];
1368     drawRunway(rwy);
1369 
1370     if (rwy->ILS()) {
1371         drawILS(false, rwy);
1372     }
1373 
1374     if (rwy->reciprocalRunway()) {
1375       FGRunway* recip = rwy->reciprocalRunway();
1376       if (recip->ILS()) {
1377         drawILS(false, recip);
1378       }
1379     }
1380   }
1381 
1382   for (unsigned int r=0; r<apt->numHelipads(); ++r) {
1383       FGHelipad* hp = apt->getHelipadByIndex(r);
1384       drawHelipad(hp);
1385   }  // of runway iteration
1386 
1387 }
1388 
1389 
drawHeliport(FGAirport * apt)1390 void MapWidget::drawHeliport(FGAirport* apt)
1391 {
1392   SGVec2d pos = project(apt->geod());
1393   glLineWidth(1.0);
1394   glColor3f(1.0, 0.0, 1.0);
1395   circleAt(pos, 16, 5.0);
1396 
1397   if (validDataForKey(apt)) {
1398     setAnchorForKey(apt, pos);
1399     return;
1400   }
1401 
1402   MapData* d = createDataForKey(apt);
1403   d->setLabel(apt->ident());
1404   d->setPriority(40);
1405   d->setOffset(MapData::VALIGN_CENTER | MapData::HALIGN_LEFT, 10);
1406   d->setAnchor(pos);
1407 
1408 }
1409 
1410 
scoreAirportRunways(FGAirport * apt)1411 int MapWidget::scoreAirportRunways(FGAirport* apt)
1412 {
1413   bool needHardSurface = _root->getBoolValue("hard-surfaced-airports", true);
1414   double minLength = _root->getDoubleValue("min-runway-length-ft", 2000.0);
1415 
1416   FGRunwayList runways(apt->getRunwaysWithoutReciprocals());
1417 
1418   int score = 0;
1419   for (unsigned int r=0; r<runways.size(); ++r) {
1420     FGRunway* rwy = runways[r];
1421     if (needHardSurface && !rwy->isHardSurface()) {
1422       continue;
1423     }
1424 
1425     if (rwy->lengthFt() < minLength) {
1426       continue;
1427     }
1428 
1429     int scoreLength = SGMiscd::roundToInt(rwy->lengthFt() / 200.0);
1430     score += scoreLength;
1431   } // of runways iteration
1432 
1433   return score;
1434 }
1435 
drawRunwayPre(FGRunway * rwy)1436 void MapWidget::drawRunwayPre(FGRunway* rwy)
1437 {
1438   SGVec2d p1 = project(rwy->begin());
1439 	SGVec2d p2 = project(rwy->end());
1440 
1441   glLineWidth(4.0);
1442   glColor3f(1.0, 0.0, 1.0);
1443   drawLine(p1, p2);
1444 }
1445 
drawRunway(FGRunway * rwy)1446 void MapWidget::drawRunway(FGRunway* rwy)
1447 {
1448 	// line for runway
1449 	// optionally show active, stopway, etc
1450 	// in legend, show published heading and length
1451 	// and threshold elevation
1452 
1453   SGVec2d p1 = project(rwy->begin());
1454 	SGVec2d p2 = project(rwy->end());
1455   glLineWidth(2.0);
1456   glColor3f(1.0, 1.0, 1.0);
1457   SGVec2d inset = normalize(p2 - p1) * 2;
1458 
1459 	drawLine(p1 + inset, p2 - inset);
1460 
1461   if (validDataForKey(rwy)) {
1462     setAnchorForKey(rwy, (p1 + p2) * 0.5);
1463     return;
1464   }
1465 
1466 	char buffer[1024];
1467 	::snprintf(buffer, 1024, "%s/%s\n%03d/%03d\n%.0f'",
1468 		rwy->ident().c_str(),
1469 		rwy->reciprocalRunway()->ident().c_str(),
1470 		displayHeading(rwy->headingDeg()),
1471 		displayHeading(rwy->reciprocalRunway()->headingDeg()),
1472 		rwy->lengthFt());
1473 
1474   MapData* d = createDataForKey(rwy);
1475   d->setText(buffer);
1476   d->setLabel(rwy->ident() + "/" + rwy->reciprocalRunway()->ident());
1477   d->setPriority(50);
1478   d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 12);
1479   d->setAnchor((p1 + p2) * 0.5);
1480 }
1481 
drawILS(bool tuned,FGRunway * rwy)1482 void MapWidget::drawILS(bool tuned, FGRunway* rwy)
1483 {
1484 	// arrow, tip centered on the landing threshold
1485   // using LOC transmitter position would be more accurate, but
1486   // is visually cluttered
1487 	// arrow width is based upon the computed localizer width
1488 
1489 	FGNavRecord* loc = rwy->ILS();
1490 	double halfBeamWidth = loc->localizerWidth() * 0.5;
1491 	SGVec2d t = project(rwy->threshold());
1492 	SGGeod locEnd;
1493 	double rangeM = loc->get_range() * SG_NM_TO_METER;
1494 	double radial = loc->get_multiuse();
1495   SG_NORMALIZE_RANGE(radial, 0.0, 360.0);
1496 	double az2;
1497 
1498 // compute the three end points at the widge end of the arrow
1499 	SGGeodesy::direct(loc->geod(), radial, -rangeM, locEnd, az2);
1500 	SGVec2d endCentre = project(locEnd);
1501 
1502 	SGGeodesy::direct(loc->geod(), radial + halfBeamWidth, -rangeM * 1.1, locEnd, az2);
1503 	SGVec2d endR = project(locEnd);
1504 
1505 	SGGeodesy::direct(loc->geod(), radial - halfBeamWidth, -rangeM * 1.1, locEnd, az2);
1506 	SGVec2d endL = project(locEnd);
1507 
1508 // outline two triangles
1509   glLineWidth(1.0);
1510   if (tuned) {
1511     glColor3f(0.0, 1.0, 1.0);
1512   } else {
1513     glColor3f(0.0, 0.0, 1.0);
1514 	}
1515 
1516   glBegin(GL_LINE_LOOP);
1517 		glVertex2dv(t.data());
1518 		glVertex2dv(endCentre.data());
1519 		glVertex2dv(endL.data());
1520 	glEnd();
1521 	glBegin(GL_LINE_LOOP);
1522 		glVertex2dv(t.data());
1523 		glVertex2dv(endCentre.data());
1524 		glVertex2dv(endR.data());
1525 	glEnd();
1526 
1527 	if (validDataForKey(loc)) {
1528     setAnchorForKey(loc, endR);
1529     return;
1530   }
1531 
1532 	char buffer[1024];
1533 	::snprintf(buffer, 1024, "%s\n%s\n%03d - %3.2fMHz",
1534 		loc->ident().c_str(), loc->name().c_str(),
1535     displayHeading(radial),
1536     loc->get_freq()/100.0);
1537 
1538   MapData* d = createDataForKey(loc);
1539   d->setPriority(40);
1540   d->setLabel(loc->ident());
1541   d->setText(buffer);
1542   d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 10);
1543   d->setAnchor(endR);
1544 }
1545 
drawTraffic()1546 void MapWidget::drawTraffic()
1547 {
1548     AIDrawVec::const_iterator it;
1549     for (it = _aiDrawVec.begin(); it != _aiDrawVec.end(); ++it) {
1550         drawAI(*it);
1551     }
1552 }
1553 
drawHelipad(FGHelipad * hp)1554 void MapWidget::drawHelipad(FGHelipad* hp)
1555 {
1556   SGVec2d pos = project(hp->geod());
1557   glLineWidth(1.0);
1558   glColor3f(1.0, 0.0, 1.0);
1559   circleAt(pos, 16, 5.0);
1560 
1561   if (validDataForKey(hp)) {
1562     setAnchorForKey(hp, pos);
1563     return;
1564   }
1565 
1566   char buffer[1024];
1567   ::snprintf(buffer, 1024, "%s\n%03d\n%.0f'",
1568              hp->ident().c_str(),
1569              displayHeading(hp->headingDeg()),
1570              hp->lengthFt());
1571 
1572   MapData* d = createDataForKey(hp);
1573   d->setText(buffer);
1574   d->setLabel(hp->ident());
1575   d->setPriority(40);
1576   d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 8);
1577   d->setAnchor(pos);
1578 }
1579 
drawAI(const DrawAIObject & dai)1580 void MapWidget::drawAI(const DrawAIObject& dai)
1581 {
1582   SGVec2d p = project(dai.pos);
1583 
1584     if (dai.boat) {
1585         glColor3f(0.0, 0.0, 0.5);
1586 
1587     } else {
1588         glColor3f(0.0, 0.0, 0.0);
1589     }
1590   glLineWidth(2.0);
1591   circleAt(p, 4, 6.0); // black diamond
1592 
1593 // draw heading vector
1594   if (dai.speedKts > 1) {
1595     glLineWidth(1.0);
1596     const double dt = 15.0 / (3600.0); // 15 seconds look-ahead
1597     double distanceM = dai.speedKts * SG_NM_TO_METER * dt;
1598     SGGeod advance = SGGeodesy::direct(dai.pos, dai.heading, distanceM);
1599     drawLine(p, project(advance));
1600   }
1601 
1602     MapData* d = getOrCreateDataForKey((void*) dai.model);
1603     d->setText(dai.legend);
1604     d->setLabel(dai.label);
1605     d->setPriority(dai.speedKts > 5 ? 60 : 10); // low priority for parked aircraft
1606     d->setOffset(MapData::VALIGN_CENTER | MapData::HALIGN_LEFT, 10);
1607     d->setAnchor(p);
1608 }
1609 
project(const SGGeod & geod) const1610 SGVec2d MapWidget::project(const SGGeod& geod) const
1611 {
1612   SGVec2d p;
1613   double r = earth_radius_lat(geod.getLatitudeRad());
1614 
1615     switch (_projection) {
1616     case PROJECTION_SAMSON_FLAMSTEED:
1617     {
1618         // Sanson-Flamsteed projection, relative to the projection center
1619         double lonDiff = geod.getLongitudeRad() - _projectionCenter.getLongitudeRad(),
1620         latDiff = geod.getLatitudeRad() - _projectionCenter.getLatitudeRad();
1621 
1622         p = SGVec2d(cos(geod.getLatitudeRad()) * lonDiff, latDiff) * r * currentScale();
1623         break;
1624     }
1625 
1626     case PROJECTION_AZIMUTHAL_EQUIDISTANT:
1627     {
1628         // Azimuthal Equidistant projection, relative to the projection center
1629       // http://www.globmaritime.com/martech/marine-navigation/general-concepts/626-azimuthal-equidistant-projection
1630         double ref_lat = _projectionCenter.getLatitudeRad(),
1631                ref_lon = _projectionCenter.getLongitudeRad(),
1632                lat = geod.getLatitudeRad(),
1633                lon = geod.getLongitudeRad(),
1634                lonDiff = lon - ref_lon;
1635 
1636         double c = acos( sin(ref_lat) * sin(lat) + cos(ref_lat) * cos(lat) * cos(lonDiff) );
1637         if (c == 0.0){
1638             // angular distance from center is 0
1639             p= SGVec2d(0.0, 0.0);
1640             break;
1641         }
1642 
1643         double k = c / sin(c);
1644         double x, y;
1645         if (ref_lat == (90 * SG_DEGREES_TO_RADIANS))
1646         {
1647             x = (SGD_PI / 2 - lat) * sin(lonDiff);
1648             y = -(SGD_PI / 2 - lat) * cos(lonDiff);
1649         }
1650         else if (ref_lat == -(90 * SG_DEGREES_TO_RADIANS))
1651         {
1652             x = (SGD_PI / 2 + lat) * sin(lonDiff);
1653             y = (SGD_PI / 2 + lat) * cos(lonDiff);
1654         }
1655         else
1656         {
1657             x = k * cos(lat) * sin(lonDiff);
1658             y = k * ( cos(ref_lat) * sin(lat) - sin(ref_lat) * cos(lat) * cos(lonDiff) );
1659         }
1660         p = SGVec2d(x, y) * r * currentScale();
1661 
1662         break;
1663     }
1664 
1665     case PROJECTION_ORTHO_AZIMUTH:
1666     {
1667         // http://mathworld.wolfram.com/OrthographicProjection.html
1668         double cosTheta = cos(geod.getLatitudeRad());
1669         double sinDLambda = sin(geod.getLongitudeRad() - _projectionCenter.getLongitudeRad());
1670         double cosDLambda = cos(geod.getLongitudeRad() - _projectionCenter.getLongitudeRad());
1671         double sinTheta1 = sin(_projectionCenter.getLatitudeRad());
1672         double sinTheta = sin(geod.getLatitudeRad());
1673         double cosTheta1 = cos(_projectionCenter.getLatitudeRad());
1674 
1675         p = SGVec2d(cosTheta * sinDLambda,
1676                     (cosTheta1 * sinTheta) - (sinTheta1 * cosTheta * cosDLambda)) * r * currentScale();
1677         break;
1678     }
1679 
1680     case PROJECTION_SPHERICAL:
1681     {
1682         SGVec3d cartCenter = SGVec3d::fromGeod(_projectionCenter);
1683         SGVec3d cartPt = SGVec3d::fromGeod(geod) - cartCenter;
1684 
1685         // rotate relative to projection center
1686         SGQuatd orient = SGQuatd::fromLonLat(_projectionCenter);
1687         cartPt = orient.rotateBack(cartPt);
1688         return SGVec2d(cartPt.y(), cartPt.x()) * currentScale();
1689         break;
1690     }
1691     } // of projection mode switch
1692 
1693 
1694 // rotate as necessary
1695   double cost = cos(_upHeading * SG_DEGREES_TO_RADIANS),
1696     sint = sin(_upHeading * SG_DEGREES_TO_RADIANS);
1697   double rx = cost * p.x() - sint * p.y();
1698   double ry = sint * p.x() + cost * p.y();
1699   return SGVec2d(rx, ry);
1700 }
1701 
unproject(const SGVec2d & p) const1702 SGGeod MapWidget::unproject(const SGVec2d& p) const
1703 {
1704   // unrotate, if necessary
1705   double cost = cos(-_upHeading * SG_DEGREES_TO_RADIANS),
1706     sint = sin(-_upHeading * SG_DEGREES_TO_RADIANS);
1707   SGVec2d ur(cost * p.x() - sint * p.y(),
1708              sint * p.x() + cost * p.y());
1709 
1710 
1711 
1712     switch (_projection) {
1713     case PROJECTION_SAMSON_FLAMSTEED:
1714     {
1715         double r = earth_radius_lat(_projectionCenter.getLatitudeRad());
1716         SGVec2d unscaled = ur * (1.0 / (currentScale() * r));
1717         double lat = unscaled.y() + _projectionCenter.getLatitudeRad();
1718         double lon = (unscaled.x() / cos(lat)) + _projectionCenter.getLongitudeRad();
1719         return SGGeod::fromRad(lon, lat);
1720     }
1721 
1722     case PROJECTION_AZIMUTHAL_EQUIDISTANT:
1723     {
1724         double r = earth_radius_lat(_projectionCenter.getLatitudeRad());
1725         SGVec2d unscaled = ur * (1.0 / currentScale());
1726         double lat = 0,
1727                lon = 0,
1728                ref_lat = _projectionCenter.getLatitudeRad(),
1729                ref_lon = _projectionCenter.getLongitudeRad(),
1730                rho = sqrt(unscaled.x() * unscaled.x() + unscaled.y() * unscaled.y()),
1731                c = rho/r;
1732 
1733         if (rho == 0)
1734         {
1735             lat = ref_lat;
1736             lon = ref_lon;
1737         }
1738         else
1739         {
1740             lat = asin( cos(c) * sin(ref_lat) + (unscaled.y()  * sin(c) * cos(ref_lat)) / rho);
1741 
1742             if (ref_lat == (90 * SG_DEGREES_TO_RADIANS))
1743             {
1744                 lon = ref_lon + atan(-unscaled.x()/unscaled.y());
1745             }
1746             else if (ref_lat == -(90 * SG_DEGREES_TO_RADIANS))
1747             {
1748                 lon = ref_lon + atan(unscaled.x()/unscaled.y());
1749             }
1750             else
1751             {
1752                 lon = ref_lon + atan(unscaled.x() * sin(c) / (rho * cos(ref_lat) * cos(c) - unscaled.y() * sin(ref_lat) * sin(c)));
1753             }
1754          }
1755 
1756         return SGGeod::fromRad(lon, lat);
1757     }
1758 
1759     case PROJECTION_ORTHO_AZIMUTH:
1760     {
1761         double r = earth_radius_lat(_projectionCenter.getLatitudeRad());
1762         SGVec2d unscaled = ur * (1.0 / (currentScale() * r));
1763 
1764         double phi = length(p);
1765         double c = asin(phi);
1766         double sinTheta1 = sin(_projectionCenter.getLatitudeRad());
1767         double cosTheta1 = cos(_projectionCenter.getLatitudeRad());
1768 
1769         double lat = asin(cos(c) * sinTheta1 + ((unscaled.y() * sin(c) * cosTheta1) / phi));
1770         double lon = _projectionCenter.getLongitudeRad() +
1771         atan((unscaled.x()* sin(c)) / (phi  * cosTheta1 * cos(c) - unscaled.y() * sinTheta1 * sin(c)));
1772         return SGGeod::fromRad(lon, lat);
1773     }
1774 
1775     case PROJECTION_SPHERICAL:
1776     {
1777         SGVec2d unscaled = ur * (1.0 / currentScale());
1778         SGQuatd orient = SGQuatd::fromLonLat(_projectionCenter);
1779         SGVec3d cartCenter = SGVec3d::fromGeod(_projectionCenter);
1780         SGVec3d cartPt = orient.rotate(SGVec3d(unscaled.x(), unscaled.y(), 0.0));
1781         return SGGeod::fromCart(cartPt + cartCenter);
1782     }
1783 
1784     default:
1785       throw sg_exception("MapWidget::unproject(): requested unknown projection");
1786     } // of projection mode switch
1787 }
1788 
currentScale() const1789 double MapWidget::currentScale() const
1790 {
1791   return 1.0 / pow(2.0, _cachedZoom);
1792 }
1793 
circleAt(const SGVec2d & center,int nSides,double r)1794 void MapWidget::circleAt(const SGVec2d& center, int nSides, double r)
1795 {
1796   glBegin(GL_LINE_LOOP);
1797   double advance = (SGD_PI * 2) / nSides;
1798   glVertex2d(center.x() +r, center.y());
1799   double t=advance;
1800   for (int i=1; i<nSides; ++i) {
1801     glVertex2d(center.x() + (cos(t) * r), center.y() + (sin(t) * r));
1802     t += advance;
1803   }
1804   glEnd();
1805 }
1806 
squareAt(const SGVec2d & center,double r)1807 void MapWidget::squareAt(const SGVec2d& center, double r)
1808 {
1809   glBegin(GL_LINE_LOOP);
1810   glVertex2d(center.x() + r, center.y() + r);
1811   glVertex2d(center.x() + r, center.y() - r);
1812   glVertex2d(center.x() - r, center.y() - r);
1813   glVertex2d(center.x() - r, center.y() + r);
1814   glEnd();
1815 }
1816 
circleAtAlt(const SGVec2d & center,int nSides,double r,double r2)1817 void MapWidget::circleAtAlt(const SGVec2d& center, int nSides, double r, double r2)
1818 {
1819   glBegin(GL_LINE_LOOP);
1820   double advance = (SGD_PI * 2) / nSides;
1821   glVertex2d(center.x(), center.y() + r);
1822   double t=advance;
1823   for (int i=1; i<nSides; ++i) {
1824     double rr = (i%2 == 0) ? r : r2;
1825     glVertex2d(center.x() + (sin(t) * rr), center.y() + (cos(t) * rr));
1826     t += advance;
1827   }
1828   glEnd();
1829 }
1830 
drawLine(const SGVec2d & p1,const SGVec2d & p2)1831 void MapWidget::drawLine(const SGVec2d& p1, const SGVec2d& p2)
1832 {
1833   glBegin(GL_LINES);
1834     glVertex2dv(p1.data());
1835     glVertex2dv(p2.data());
1836   glEnd();
1837 }
1838 
drawLegendBox(const SGVec2d & pos,const std::string & t)1839 void MapWidget::drawLegendBox(const SGVec2d& pos, const std::string& t)
1840 {
1841 	std::vector<std::string> lines(simgear::strutils::split(t, "\n"));
1842 	const int LINE_LEADING = 4;
1843 	const int MARGIN = 4;
1844 
1845 // measure
1846 	int maxWidth = -1, totalHeight = 0;
1847 	int lineHeight = legendFont.getStringHeight();
1848 
1849 	for (unsigned int ln=0; ln<lines.size(); ++ln) {
1850 		totalHeight += lineHeight;
1851 		if (ln > 0) {
1852 			totalHeight += LINE_LEADING;
1853 		}
1854 
1855 		int lw = legendFont.getStringWidth(lines[ln].c_str());
1856 		maxWidth = std::max(maxWidth, lw);
1857 	} // of line measurement
1858 
1859 	if (maxWidth < 0) {
1860 		return; // all lines are empty, don't draw
1861 	}
1862 
1863 	totalHeight += MARGIN * 2;
1864 
1865 // draw box
1866 	puBox box;
1867 	box.min[0] = 0;
1868 	box.min[1] = -totalHeight;
1869 	box.max[0] = maxWidth + (MARGIN * 2);
1870 	box.max[1] = 0;
1871 	int border = 1;
1872 	box.draw (pos.x(), pos.y(), PUSTYLE_DROPSHADOW, colour, FALSE, border);
1873 
1874 // draw lines
1875 	int xPos = pos.x() + MARGIN;
1876 	int yPos = pos.y() - (lineHeight + MARGIN);
1877 	glColor3f(0.8, 0.8, 0.8);
1878 
1879 	for (unsigned int ln=0; ln<lines.size(); ++ln) {
1880 		legendFont.drawString(lines[ln].c_str(), xPos, yPos);
1881 		yPos -= lineHeight + LINE_LEADING;
1882 	}
1883 }
1884 
drawData()1885 void MapWidget::drawData()
1886 {
1887   std::sort(_dataQueue.begin(), _dataQueue.end(), MapData::order);
1888 
1889   int hw = _width >> 1,
1890     hh = _height >> 1;
1891   puBox visBox(makePuBox(-hw, -hh, _width, _height));
1892 
1893   unsigned int d = 0;
1894   int drawn = 0;
1895   std::vector<MapData*> drawQueue;
1896 
1897   bool drawData = _root->getBoolValue("draw-data");
1898   const int MAX_DRAW_DATA = 25;
1899   const int MAX_DRAW = 50;
1900 
1901   for (; (d < _dataQueue.size()) && (drawn < MAX_DRAW); ++d) {
1902     MapData* md = _dataQueue[d];
1903     md->setDataVisible(drawData);
1904 
1905     if (md->isClipped(visBox)) {
1906       continue;
1907     }
1908 
1909     if (md->overlaps(drawQueue)) {
1910       if (drawData) { // overlapped with data, let's try just the label
1911         md->setDataVisible(false);
1912         if (md->overlaps(drawQueue)) {
1913           continue;
1914         }
1915       } else {
1916         continue;
1917       }
1918     } // of overlaps case
1919 
1920     drawQueue.push_back(md);
1921     ++drawn;
1922     if (drawData && (drawn >= MAX_DRAW_DATA)) {
1923       drawData = false;
1924     }
1925   }
1926 
1927   // draw lowest-priority first, so higher-priorty items appear on top
1928   std::vector<MapData*>::reverse_iterator r;
1929   for (r = drawQueue.rbegin(); r!= drawQueue.rend(); ++r) {
1930     (*r)->draw();
1931   }
1932 
1933   _dataQueue.clear();
1934   KeyDataMap::iterator it = _mapData.begin();
1935   for (; it != _mapData.end(); ) {
1936     it->second->age();
1937     if (it->second->isExpired()) {
1938       delete it->second;
1939       KeyDataMap::iterator cur = it++;
1940       _mapData.erase(cur);
1941     } else {
1942       ++it;
1943     }
1944   } // of expiry iteration
1945 }
1946 
validDataForKey(void * key)1947 bool MapWidget::validDataForKey(void* key)
1948 {
1949   KeyDataMap::iterator it = _mapData.find(key);
1950   if (it == _mapData.end()) {
1951     return false; // no valid data for the key!
1952   }
1953 
1954   it->second->resetAge(); // mark data as valid this frame
1955   _dataQueue.push_back(it->second);
1956   return true;
1957 }
1958 
setAnchorForKey(void * key,const SGVec2d & anchor)1959 void MapWidget::setAnchorForKey(void* key, const SGVec2d& anchor)
1960 {
1961   KeyDataMap::iterator it = _mapData.find(key);
1962   if (it == _mapData.end()) {
1963     throw sg_exception("no valid data for key!");
1964   }
1965 
1966   it->second->setAnchor(anchor);
1967 }
1968 
getOrCreateDataForKey(void * key)1969 MapData* MapWidget::getOrCreateDataForKey(void* key)
1970 {
1971   KeyDataMap::iterator it = _mapData.find(key);
1972   if (it == _mapData.end()) {
1973     return createDataForKey(key);
1974   }
1975 
1976   it->second->resetAge(); // mark data as valid this frame
1977   _dataQueue.push_back(it->second);
1978   return it->second;
1979 }
1980 
createDataForKey(void * key)1981 MapData* MapWidget::createDataForKey(void* key)
1982 {
1983   KeyDataMap::iterator it = _mapData.find(key);
1984   if (it != _mapData.end()) {
1985     throw sg_exception("duplicate data requested for key!");
1986   }
1987 
1988   MapData* d =  new MapData(0);
1989   _mapData[key] = d;
1990   _dataQueue.push_back(d);
1991   d->resetAge();
1992   return d;
1993 }
1994 
clearData()1995 void MapWidget::clearData()
1996 {
1997   KeyDataMap::iterator it = _mapData.begin();
1998   for (; it != _mapData.end(); ++it) {
1999     delete it->second;
2000   }
2001 
2002   _mapData.clear();
2003 }
2004 
displayHeading(double h) const2005 int MapWidget::displayHeading(double h) const
2006 {
2007   if (_magneticHeadings) {
2008     h -= _magVar->get_magvar() * SG_RADIANS_TO_DEGREES;
2009   }
2010 
2011   SG_NORMALIZE_RANGE(h, 0.0, 360.0);
2012   return SGMiscd::roundToInt(h);
2013 }
2014 
DrawAIObject(SGPropertyNode * m,const SGGeod & g)2015 MapWidget::DrawAIObject::DrawAIObject(SGPropertyNode* m, const SGGeod& g) :
2016     model(m),
2017     boat(false),
2018     pos(g),
2019     speedKts(0)
2020 {
2021     std::string name(model->getNameString());
2022     heading = model->getDoubleValue("orientation/true-heading-deg");
2023 
2024     if ((name == "aircraft") || (name == "multiplayer") ||
2025         (name == "wingman") || (name == "tanker") || (name == "swift"))
2026     {
2027         speedKts = static_cast<int>(model->getDoubleValue("velocities/true-airspeed-kt"));
2028         label = model->getStringValue("callsign", "<>");
2029 
2030         // try to access the flight-plan of the aircraft. There are several layers
2031         // of potential NULL-ness here, so we have to be defensive at each stage.
2032         std::string originICAO, destinationICAO;
2033         FGAIManager* aiManager = globals->get_subsystem<FGAIManager>();
2034         FGAIBasePtr aircraft = aiManager ? aiManager->getObjectFromProperty(model) : NULL;
2035         if (aircraft) {
2036             FGAIAircraft* p = static_cast<FGAIAircraft*>(aircraft.get());
2037             if (p->GetFlightPlan()) {
2038                 if (p->GetFlightPlan()->departureAirport()) {
2039                     originICAO = p->GetFlightPlan()->departureAirport()->ident();
2040                 }
2041 
2042                 if (p->GetFlightPlan()->arrivalAirport()) {
2043                     destinationICAO = p->GetFlightPlan()->arrivalAirport()->ident();
2044                 }
2045             } // of flight-plan exists
2046         } // of check for AIBase-derived instance
2047 
2048         // draw callsign / altitude / speed
2049         int altFt50 = static_cast<int>(pos.getElevationFt() / 50.0) * 50;
2050         std::ostringstream ss;
2051         ss << model->getStringValue("callsign", "<>");
2052         if (speedKts > 1) {
2053             ss << "\n" << altFt50 << "' " << speedKts << "kts";
2054         }
2055 
2056         if (!originICAO.empty() || ! destinationICAO.empty()) {
2057             ss << "\n" << originICAO << " -> " << destinationICAO;
2058         }
2059 
2060         legend = ss.str();
2061     } else if ((name == "ship") || (name == "carrier") || (name == "escort")) {
2062         boat = true;
2063         speedKts = static_cast<int>(model->getDoubleValue("velocities/speed-kts"));
2064         label = model->getStringValue("name", "<>");
2065 
2066         char buffer[1024];
2067         ::snprintf(buffer, 1024, "%s\n%dkts",
2068                    model->getStringValue("name", "<>"),
2069                    speedKts);
2070         legend = buffer;
2071     }
2072 }
2073 
2074