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