Merge branch 'ehofman/atc' into next
[fg:flightgear.git] / src / GUI / MapWidget.cxx
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/route/waypoint.hxx>
12 #include <simgear/sg_inlines.h>
13 #include <simgear/misc/strutils.hxx>
14 #include <simgear/magvar/magvar.hxx>
15 #include <simgear/timing/sg_time.hxx> // for magVar julianDate
16 #include <simgear/structure/exception.hxx>
17
18 #include <Main/globals.hxx>
19 #include <Main/fg_props.hxx>
20 #include <Autopilot/route_mgr.hxx>
21 #include <Navaids/positioned.hxx>
22 #include <Navaids/navrecord.hxx>
23 #include <Navaids/navlist.hxx>
24 #include <Navaids/fix.hxx>
25 #include <Airports/simple.hxx>
26 #include <Airports/runways.hxx>
27 #include <Main/fg_os.hxx>      // fgGetKeyModifiers()
28
29 const char* RULER_LEGEND_KEY = "ruler-legend";
30   
31 /* equatorial and polar earth radius */
32 const float rec  = 6378137;          // earth radius, equator (?)
33 const float rpol = 6356752.314f;      // earth radius, polar   (?)
34
35 /************************************************************************
36   some trigonometric helper functions 
37   (translated more or less directly from Alexei Novikovs perl original)
38 *************************************************************************/
39
40 //Returns Earth radius at a given latitude (Ellipsoide equation with two equal axis)
41 static float earth_radius_lat( float lat )
42 {
43   double a = cos(lat)/rec;
44   double b = sin(lat)/rpol;
45   return 1.0f / sqrt( a * a + b * b );
46 }
47
48 ///////////////////////////////////////////////////////////////////////////
49
50 static puBox makePuBox(int x, int y, int w, int h)
51 {
52   puBox r;
53   r.min[0] = x;
54   r.min[1] = y;
55   r.max[0] =  x + w;
56   r.max[1] = y + h;
57   return r;
58 }
59
60 static bool puBoxIntersect(const puBox& a, const puBox& b)
61 {
62   int x0 = SG_MAX2(a.min[0], b.min[0]);
63   int y0 = SG_MAX2(a.min[1], b.min[1]);
64   int x1 = SG_MIN2(a.max[0], b.max[0]);
65   int y1 = SG_MIN2(a.max[1], b.max[1]);
66   
67   return (x0 <= x1) && (y0 <= y1); 
68 }
69
70 class MapData;
71 typedef std::vector<MapData*> MapDataVec;
72
73 class MapData
74 {
75 public:
76   static const int HALIGN_LEFT = 1;
77   static const int HALIGN_CENTER = 2;
78   static const int HALIGN_RIGHT = 3;
79   
80   static const int VALIGN_TOP = 1 << 4;
81   static const int VALIGN_CENTER = 2 << 4;
82   static const int VALIGN_BOTTOM = 3 << 4;
83   
84   MapData(int priority) :
85     _dirtyText(true),
86     _age(0),
87     _priority(priority),
88     _width(0),
89     _height(0),
90     _offsetDir(HALIGN_LEFT | VALIGN_CENTER),
91     _offsetPx(10),
92     _dataVisible(false)
93   {
94   }
95   
96   void setLabel(const std::string& label)
97   {
98     if (label == _label) {
99       return; // common case, and saves invalidation
100     }
101     
102     _label = label;
103     _dirtyText = true;
104   }
105   
106   void setText(const std::string &text)
107   {
108     if (_rawText == text) {
109       return; // common case, and saves invalidation
110     }
111     
112     _rawText = text;
113     _dirtyText = true;
114   }
115   
116   void setDataVisible(bool vis) {
117     if (vis == _dataVisible) {
118       return;
119     }
120     
121     if (_rawText.empty()) {
122       vis = false;
123     }
124     
125     _dataVisible = vis;
126     _dirtyText = true;
127   }
128   
129   static void setFont(puFont f)
130   {
131     _font = f;
132     _fontHeight = f.getStringHeight();
133     _fontDescender = f.getStringDescender();
134   }
135   
136   static void setPalette(puColor* pal)
137   {
138     _palette = pal;
139   }
140   
141   void setPriority(int pri)
142   {
143     _priority = pri;
144   }
145   
146   int priority() const
147   { return _priority; }
148   
149   void setAnchor(const SGVec2d& anchor)
150   {
151     _anchor = anchor;
152   }
153   
154   void setOffset(int direction, int px)
155   {
156     if ((_offsetPx == px) && (_offsetDir == direction)) {
157       return;
158     }
159     
160     _dirtyOffset = true;
161     _offsetDir = direction;
162     _offsetPx = px;
163   }
164   
165   bool isClipped(const puBox& vis) const
166   {
167     validate();
168     if ((_width < 1) || (_height < 1)) {
169       return true;
170     }
171     
172     return !puBoxIntersect(vis, box());
173   }
174   
175   bool overlaps(const MapDataVec& l) const
176   {
177     validate();
178     puBox b(box());
179         
180     MapDataVec::const_iterator it;
181     for (it = l.begin(); it != l.end(); ++it) {
182       if (puBoxIntersect(b, (*it)->box())) {
183         return true;
184       }
185     } // of list iteration
186     
187     return false;
188   }
189   
190   puBox box() const
191   {
192     validate();
193     return makePuBox(
194       _anchor.x() + _offset.x(), 
195       _anchor.y() + _offset.y(),
196       _width, _height);
197   }
198   
199   void draw()
200   {
201     validate();
202     
203     int xx = _anchor.x() + _offset.x();
204     int yy = _anchor.y() + _offset.y();
205     
206     if (_dataVisible) {
207       puBox box(makePuBox(0,0,_width, _height));
208       int border = 1;
209       box.draw(xx, yy, PUSTYLE_DROPSHADOW, _palette, FALSE, border);
210       
211       // draw lines
212       int lineHeight = _fontHeight;
213       int xPos = xx + MARGIN;
214       int yPos = yy + _height - (lineHeight + MARGIN);
215       glColor3f(0.8, 0.8, 0.8);
216       
217       for (unsigned int ln=0; ln<_lines.size(); ++ln) {
218         _font.drawString(_lines[ln].c_str(), xPos, yPos);
219         yPos -= lineHeight + LINE_LEADING;
220       }
221     } else {      
222       glColor3f(0.8, 0.8, 0.8);
223       _font.drawString(_label.c_str(), xx, yy + _fontDescender);
224     }
225   }
226   
227   void age()
228   {
229     ++_age;
230   }
231   
232   void resetAge()
233   {
234     _age = 0;
235   }
236     
237   bool isExpired() const
238   { return (_age > 100); }
239   
240   static bool order(MapData* a, MapData* b)
241   {
242     return a->_priority > b->_priority;
243   }
244 private:
245   void validate() const
246   {
247     if (!_dirtyText) {
248       if (_dirtyOffset) {
249         computeOffset();
250       }
251       
252       return;
253     }
254     
255     if (_dataVisible) {
256       measureData();
257     } else {
258       measureLabel();
259     }
260   
261     computeOffset();
262     _dirtyText = false;
263   }
264   
265   void measureData() const
266   {
267     _lines = simgear::strutils::split(_rawText, "\n");
268   // measure text to find width and height
269     _width = -1;
270     _height = 0;
271         
272     for (unsigned int ln=0; ln<_lines.size(); ++ln) {
273       _height += _fontHeight;
274       if (ln > 0) {
275         _height += LINE_LEADING;
276       }
277       
278       int lw = _font.getStringWidth(_lines[ln].c_str());
279       _width = std::max(_width, lw);
280     } // of line measurement
281         
282     if ((_width < 1) || (_height < 1)) {
283       // will be clipped
284       return;
285     }
286   
287     _height += MARGIN * 2;
288     _width += MARGIN * 2;
289   }
290   
291   void measureLabel() const
292   {
293     if (_label.empty()) {
294       _width = _height = -1;
295       return;
296     }
297     
298     _height = _fontHeight;
299     _width = _font.getStringWidth(_label.c_str());
300   }
301   
302   void computeOffset() const
303   {
304     _dirtyOffset = false;
305     if ((_width <= 0) || (_height <= 0)) {
306       return;
307     }
308     
309     int hOffset = 0;
310     int vOffset = 0;
311         
312     switch (_offsetDir & 0x0f) {
313     default:
314     case HALIGN_LEFT:
315       hOffset = _offsetPx;
316       break;
317       
318     case HALIGN_CENTER:
319       hOffset = -(_width>>1);
320       break;
321       
322     case HALIGN_RIGHT:
323       hOffset = -(_offsetPx + _width);
324       break;
325     }
326     
327     switch (_offsetDir & 0xf0) {
328     default:
329     case VALIGN_TOP:
330       vOffset = -(_offsetPx + _height);
331       break;
332       
333     case VALIGN_CENTER:
334       vOffset = -(_height>>1);
335       break;
336       
337     case VALIGN_BOTTOM:
338       vOffset = _offsetPx;
339       break;
340     }
341
342     _offset = SGVec2d(hOffset, vOffset);
343   }
344   
345   static const int LINE_LEADING = 3;
346         static const int MARGIN = 3;
347   
348   mutable bool _dirtyText;
349   mutable bool _dirtyOffset;
350   int _age;
351   std::string _rawText;
352   std::string _label;
353   mutable std::vector<std::string> _lines;
354   int _priority;
355   mutable int _width, _height;
356   SGVec2d _anchor;
357   int _offsetDir;
358   int _offsetPx;
359   mutable SGVec2d _offset;
360   bool _dataVisible;
361   
362   static puFont _font;
363   static puColor* _palette;
364   static int _fontHeight;
365   static int _fontDescender;
366 };
367
368 puFont MapData::_font;
369 puColor* MapData::_palette;
370 int MapData::_fontHeight = 0;
371 int MapData::_fontDescender = 0;
372
373 ///////////////////////////////////////////////////////////////////////////
374
375 const int MAX_ZOOM = 16;
376 const int SHOW_DETAIL_ZOOM = 8;
377 const int CURSOR_PAN_STEP = 32;
378
379 MapWidget::MapWidget(int x, int y, int maxX, int maxY) : 
380   puObject(x,y,maxX, maxY)
381 {
382   _route = static_cast<FGRouteMgr*>(globals->get_subsystem("route-manager"));
383   _gps = fgGetNode("/instrumentation/gps");
384   
385   _zoom = 6;
386   _width = maxX - x;
387   _height = maxY - y;
388   
389   MapData::setFont(legendFont);
390   MapData::setPalette(colour);
391   
392   _magVar = new SGMagVar();
393 }
394
395 MapWidget::~MapWidget()
396 {
397   delete _magVar;
398 }
399
400 void MapWidget::setProperty(SGPropertyNode_ptr prop)
401 {
402   _root = prop;
403   _root->setBoolValue("centre-on-aircraft", true);
404   _root->setBoolValue("draw-data", false);
405   _root->setBoolValue("magnetic-headings", true);
406 }
407
408 void MapWidget::setSize(int w, int h)
409 {
410   puObject::setSize(w, h);
411
412   _width = w;
413   _height = h;
414
415 }
416
417 void MapWidget::doHit( int button, int updown, int x, int y )
418 {
419   puObject::doHit(button, updown, x, y);  
420   if (updown == PU_DRAG) {    
421     handlePan(x, y);
422     return;
423   }
424   
425   if (button == 3) { // mouse-wheel up
426     zoomIn();
427   } else if (button == 4) { // mouse-wheel down
428     zoomOut();
429   }
430   
431   if (button != active_mouse_button) {
432     return;
433   }
434   
435   _hitLocation = SGVec2d(x - abox.min[0], y - abox.min[1]);
436   
437   if (updown == PU_UP) {
438     puDeactivateWidget();
439   } else if (updown == PU_DOWN) {
440     puSetActiveWidget(this, x, y);
441     
442     if (fgGetKeyModifiers() & KEYMOD_CTRL) {
443       _clickGeod = unproject(_hitLocation - SGVec2d(_width>>1, _height>>1));
444     }
445   }
446 }
447
448 void MapWidget::handlePan(int x, int y)
449 {
450   SGVec2d delta = SGVec2d(x, y) - _hitLocation;
451   pan(delta);
452   _hitLocation = SGVec2d(x,y);
453 }
454
455 int MapWidget::checkKey (int key, int updown )
456 {
457   if ((updown == PU_UP) || !isVisible () || !isActive () || (window != puGetWindow())) {
458     return FALSE ;
459   }
460   
461   switch (key)
462   {
463
464   case PU_KEY_UP:
465     pan(SGVec2d(0, -CURSOR_PAN_STEP));
466     break;
467
468   case PU_KEY_DOWN:
469     pan(SGVec2d(0, CURSOR_PAN_STEP));
470     break ;
471   
472   case PU_KEY_LEFT:
473     pan(SGVec2d(CURSOR_PAN_STEP, 0));
474     break;
475     
476   case PU_KEY_RIGHT:
477     pan(SGVec2d(-CURSOR_PAN_STEP, 0));
478     break;
479   
480   case '-':
481     zoomOut();
482     
483     break;
484     
485   case '=':
486     zoomIn();
487     break;
488   
489   default :
490     return FALSE;
491   }
492
493   return TRUE ;
494 }
495
496 void MapWidget::pan(const SGVec2d& delta)
497 {
498   _projectionCenter = unproject(-delta);
499 }
500
501 void MapWidget::zoomIn()
502 {
503   if (_zoom <= 0) {
504     return;
505   }
506   
507   --_zoom;
508   SG_LOG(SG_GENERAL, SG_INFO, "zoom is now:" << _zoom);
509 }
510
511 void MapWidget::zoomOut()
512 {
513   if (_zoom >= MAX_ZOOM) {
514     return;
515   }
516   
517   ++_zoom;
518   SG_LOG(SG_GENERAL, SG_INFO, "zoom is now:" << _zoom);
519 }
520
521 void MapWidget::draw(int dx, int dy)
522 {
523   _aircraft = SGGeod::fromDeg(fgGetDouble("/position/longitude-deg"), 
524     fgGetDouble("/position/latitude-deg"));
525   _magneticHeadings = _root->getBoolValue("magnetic-headings");
526   
527   if (_root->getBoolValue("centre-on-aircraft")) {
528     _projectionCenter = _aircraft;
529     _root->setBoolValue("centre-on-aircraft", false);
530   }
531   
532   double julianDate = globals->get_time_params()->getJD();
533   _magVar->update(_projectionCenter, julianDate);
534
535   bool aircraftUp = _root->getBoolValue("aircraft-heading-up");
536   if (aircraftUp) {
537     _upHeading = fgGetDouble("/orientation/heading-deg");
538   } else {
539     _upHeading = 0.0;
540   }
541
542   SGGeod topLeft = unproject(SGVec2d(_width/2, _height/2));
543   // compute draw range, including a fudge factor for ILSs and other 'long'
544   // symbols
545   _drawRangeNm = SGGeodesy::distanceNm(_projectionCenter, topLeft) + 10.0;
546
547 // drawing operations
548   GLint sx = (int) abox.min[0],
549     sy = (int) abox.min[1];
550   glScissor(dx + sx, dy + sy, _width, _height);
551   glEnable(GL_SCISSOR_TEST);
552   
553   glMatrixMode(GL_MODELVIEW);
554   glPushMatrix();
555   // cetere drawing about the widget center (which is also the
556   // projection centre)
557   glTranslated(dx + sx + (_width/2), dy + sy + (_height/2), 0.0);
558   
559   drawLatLonGrid();
560   
561   if (aircraftUp) {
562     int textHeight = legendFont.getStringHeight() + 5;
563     
564     // draw heading line
565     SGVec2d loc = project(_aircraft);
566     glColor3f(1.0, 1.0, 1.0);
567     drawLine(loc, SGVec2d(loc.x(), (_height / 2) - textHeight));
568     
569     int displayHdg;
570     if (_magneticHeadings) {
571       displayHdg = (int) fgGetDouble("/orientation/heading-magnetic-deg");
572     } else {
573       displayHdg = (int) _upHeading;
574     }
575     
576     double y = (_height / 2) - textHeight;
577     char buf[16];
578     ::snprintf(buf, 16, "%d", displayHdg);
579     int sw = legendFont.getStringWidth(buf);
580     legendFont.drawString(buf, loc.x() - sw/2, y);
581   }
582   
583   drawAirports();
584   drawNavaids();
585   drawTraffic();
586   drawGPSData();
587   drawNavRadio(fgGetNode("/instrumentation/nav[0]", false));
588   drawNavRadio(fgGetNode("/instrumentation/nav[1]", false));
589   paintAircraftLocation(_aircraft);
590   paintRoute();
591   paintRuler();
592   
593   drawData();
594   
595   glPopMatrix();
596   glDisable(GL_SCISSOR_TEST);
597 }
598
599 void MapWidget::paintRuler()
600 {
601   if (_clickGeod == SGGeod()) {
602     return;
603   }
604   
605   SGVec2d acftPos = project(_aircraft);
606   SGVec2d clickPos = project(_clickGeod);
607   
608   glColor4f(0.0, 1.0, 1.0, 0.6);
609   drawLine(acftPos, clickPos);
610   
611   circleAtAlt(clickPos, 8, 10, 5);
612   
613   double dist, az, az2;
614   SGGeodesy::inverse(_aircraft, _clickGeod, az, az2, dist);
615   if (_magneticHeadings) {
616     az -= _magVar->get_magvar();
617     SG_NORMALIZE_RANGE(az, 0.0, 360.0);
618   }
619   
620   char buffer[1024];
621         ::snprintf(buffer, 1024, "%03d/%.1fnm",
622                 SGMiscd::roundToInt(az), dist * SG_METER_TO_NM);
623   
624   MapData* d = getOrCreateDataForKey((void*) RULER_LEGEND_KEY);
625   d->setLabel(buffer);
626   d->setAnchor(clickPos);
627   d->setOffset(MapData::VALIGN_TOP | MapData::HALIGN_CENTER, 15);
628   d->setPriority(20000);
629
630   
631 }
632
633 void MapWidget::paintAircraftLocation(const SGGeod& aircraftPos)
634 {
635   SGVec2d loc = project(aircraftPos);
636   
637   double hdg = fgGetDouble("/orientation/heading-deg");
638   
639   glLineWidth(2.0);
640   glColor4f(1.0, 1.0, 0.0, 1.0);
641   glPushMatrix();
642   glTranslated(loc.x(), loc.y(), 0.0);
643   glRotatef(hdg - _upHeading, 0.0, 0.0, -1.0);
644   
645   const SGVec2d wingspan(12, 0);
646   const SGVec2d nose(0, 8);
647   const SGVec2d tail(0, -14);
648   const SGVec2d tailspan(4,0);
649   
650   drawLine(-wingspan, wingspan);
651   drawLine(nose, tail);
652   drawLine(tail - tailspan, tail + tailspan);
653   
654   glPopMatrix();
655   glLineWidth(1.0);
656 }
657
658 void MapWidget::paintRoute()
659 {
660   if (_route->size() < 2) {
661     return;
662   }
663   
664 // first pass, draw the actual line
665   glLineWidth(2.0);
666   glBegin(GL_LINE_STRIP);
667   
668   SGVec2d prev = project(_route->get_waypoint(0).get_target());
669   glVertex2d(prev.x(), prev.y());
670   
671   for (int w=1; w < _route->size(); ++w) {
672     
673     SGVec2d p = project(_route->get_waypoint(w).get_target());
674     
675     if (w < _route->currentWaypoint()) {
676       glColor4f(0.5, 0.5, 0.5, 0.7);
677     } else {
678       glColor4f(1.0, 0.0, 1.0, 1.0);
679     }
680     
681     glVertex2d(p.x(), p.y());
682     
683   }
684   glEnd();
685   
686   glLineWidth(1.0);
687 // second pass, draw waypoint symbols and data
688   for (int w=0; w < _route->size(); ++w) {
689     const SGWayPoint& wpt(_route->get_waypoint(w));
690     SGVec2d p = project(wpt.get_target());
691     glColor4f(1.0, 0.0, 1.0, 1.0);
692     circleAtAlt(p, 8, 12, 5);
693     
694     std::ostringstream legend;
695     legend << wpt.get_id();
696     if (wpt.get_target_alt() > -9990.0) {
697       legend << '\n' << SGMiscd::roundToInt(wpt.get_target_alt()) << '\'';
698     }
699     
700     if (wpt.get_speed() > 0.0) {
701       legend << '\n' << SGMiscd::roundToInt(wpt.get_speed()) << "Kts";
702     }
703         
704     MapData* d = getOrCreateDataForKey(reinterpret_cast<void*>(w * 2));
705     d->setText(legend.str());
706     d->setLabel(wpt.get_id());
707     d->setAnchor(p);
708     d->setOffset(MapData::VALIGN_TOP | MapData::HALIGN_CENTER, 15);
709     d->setPriority(w < _route->currentWaypoint() ? 9000 : 12000);
710         
711     if (w > 0) {
712       SGVec2d legMid = (prev + p) * 0.5;
713       std::ostringstream legLegend;
714       
715       double track = wpt.get_track();
716       if (_magneticHeadings) {
717         track -= _magVar->get_magvar(); // show magnetic track for leg
718       }
719       
720       legLegend << SGMiscd::roundToInt(track) << " " 
721         << SGMiscd::roundToInt(wpt.get_distance() * SG_METER_TO_NM) << "Nm";
722         
723       MapData* ld = getOrCreateDataForKey(reinterpret_cast<void*>(w * 2 + 1));
724       ld->setText(legLegend.str());
725       ld->setAnchor(legMid);
726       ld->setOffset(MapData::VALIGN_TOP | MapData::HALIGN_CENTER, 15);
727       ld->setPriority(w < _route->currentWaypoint() ? 8000 : 11000);
728     } // of draw leg data
729     
730     prev = p;
731   } // of second waypoint iteration
732 }
733
734 /**
735  * Round a SGGeod to an arbitrary precision. 
736  * For example, passing precision of 0.5 will round to the nearest 0.5 of
737  * a degree in both lat and lon - passing in 3.0 rounds to the nearest 3 degree
738  * multiple, and so on.
739  */
740 static SGGeod roundGeod(double precision, const SGGeod& g)
741 {
742   double lon = SGMiscd::round(g.getLongitudeDeg() / precision);
743   double lat = SGMiscd::round(g.getLatitudeDeg() / precision);
744   
745   return SGGeod::fromDeg(lon * precision, lat * precision);
746 }
747
748 bool MapWidget::drawLineClipped(const SGVec2d& a, const SGVec2d& b)
749 {
750   double minX = SGMiscd::min(a.x(), b.x()),
751     minY = SGMiscd::min(a.y(), b.y()),
752     maxX = SGMiscd::max(a.x(), b.x()),
753     maxY = SGMiscd::max(a.y(), b.y());
754   
755   int hh = _height >> 1, hw = _width >> 1;
756   
757   if ((maxX < -hw) || (minX > hw) || (minY > hh) || (maxY < -hh)) {
758     return false;
759   }
760   
761   glVertex2dv(a.data());
762   glVertex2dv(b.data());
763   return true;
764 }
765
766 SGVec2d MapWidget::gridPoint(int ix, int iy)
767 {
768         int key = (ix + 0x7fff) | ((iy + 0x7fff) << 16);
769         GridPointCache::iterator it = _gridCache.find(key);
770         if (it != _gridCache.end()) {
771                 return it->second;
772         }
773         
774         SGGeod gp = SGGeod::fromDeg(
775     _gridCenter.getLongitudeDeg() + ix * _gridSpacing,
776                 _gridCenter.getLatitudeDeg() + iy * _gridSpacing);
777                 
778         SGVec2d proj = project(gp);
779         _gridCache[key] = proj;
780         return proj;
781 }
782
783 void MapWidget::drawLatLonGrid()
784 {
785   _gridSpacing = 1.0;
786   _gridCenter = roundGeod(_gridSpacing, _projectionCenter);
787   _gridCache.clear();
788   
789   int ix = 0;
790   int iy = 0;
791
792   glColor4f(0.8, 0.8, 0.8, 0.4);
793   glBegin(GL_LINES);
794   bool didDraw;
795   do {
796     didDraw = false;
797     ++ix;
798     ++iy;
799     
800     for (int x = -ix; x < ix; ++x) {
801       didDraw |= drawLineClipped(gridPoint(x, -iy), gridPoint(x+1, -iy));
802       didDraw |= drawLineClipped(gridPoint(x, iy), gridPoint(x+1, iy));
803       didDraw |= drawLineClipped(gridPoint(x, -iy), gridPoint(x, -iy + 1));
804       didDraw |= drawLineClipped(gridPoint(x, iy), gridPoint(x, iy - 1));
805
806     }
807     
808     for (int y = -iy; y < iy; ++y) {
809       didDraw |= drawLineClipped(gridPoint(-ix, y), gridPoint(-ix, y+1));
810       didDraw |= drawLineClipped(gridPoint(-ix, y), gridPoint(-ix + 1, y));
811       didDraw |= drawLineClipped(gridPoint(ix, y), gridPoint(ix, y+1));
812       didDraw |= drawLineClipped(gridPoint(ix, y), gridPoint(ix - 1, y));
813     }
814     
815     if (ix > 30) {
816       break;
817     }
818   } while (didDraw);
819   
820   glEnd();
821 }
822
823 void MapWidget::drawGPSData()
824 {
825   std::string gpsMode = _gps->getStringValue("mode");
826   
827   SGGeod wp0Geod = SGGeod::fromDeg(
828         _gps->getDoubleValue("wp/wp[0]/longitude-deg"), 
829         _gps->getDoubleValue("wp/wp[0]/latitude-deg"));
830         
831   SGGeod wp1Geod = SGGeod::fromDeg(
832         _gps->getDoubleValue("wp/wp[1]/longitude-deg"), 
833         _gps->getDoubleValue("wp/wp[1]/latitude-deg"));
834   
835 // draw track line
836   double gpsTrackDeg = _gps->getDoubleValue("indicated-track-true-deg");
837   double gpsSpeed = _gps->getDoubleValue("indicated-ground-speed-kt");
838   double az2;
839   
840   if (gpsSpeed > 3.0) { // only draw track line if valid
841     SGGeod trackRadial;
842     SGGeodesy::direct(_aircraft, gpsTrackDeg, _drawRangeNm * SG_NM_TO_METER, trackRadial, az2);
843     
844     glColor4f(1.0, 1.0, 0.0, 1.0);
845     glEnable(GL_LINE_STIPPLE);
846     glLineStipple(1, 0x00FF);
847     drawLine(project(_aircraft), project(trackRadial));
848     glDisable(GL_LINE_STIPPLE);
849   }
850   
851   if (gpsMode == "dto") {
852     SGVec2d wp0Pos = project(wp0Geod);
853     SGVec2d wp1Pos = project(wp1Geod);
854     
855     glColor4f(1.0, 0.0, 1.0, 1.0);
856     drawLine(wp0Pos, wp1Pos);
857     
858   }
859     
860   if (_gps->getBoolValue("scratch/valid")) {
861     // draw scratch data
862     
863   }
864 }
865
866 class MapAirportFilter : public FGAirport::AirportFilter
867 {
868 public:
869   MapAirportFilter(SGPropertyNode_ptr nd)
870   {
871     _heliports = nd->getBoolValue("show-heliports", false);
872     _hardRunwaysOnly = nd->getBoolValue("hard-surfaced-airports", true);
873     _minLengthFt = nd->getDoubleValue("min-runway-length-ft", 2000.0);
874   }
875   
876   virtual FGPositioned::Type maxType() const {
877     return _heliports ? FGPositioned::HELIPORT : FGPositioned::AIRPORT;
878   }
879        
880   virtual bool passAirport(FGAirport* aApt) const {
881     if (_hardRunwaysOnly) {
882       return aApt->hasHardRunwayOfLengthFt(_minLengthFt);
883     }
884     
885     return true;
886   }
887
888 private:
889   bool _heliports;
890   bool _hardRunwaysOnly;
891   double _minLengthFt;
892 };
893
894 void MapWidget::drawAirports()
895 {
896   MapAirportFilter af(_root);
897   FGPositioned::List apts = FGPositioned::findWithinRange(_projectionCenter, _drawRangeNm, &af);
898   for (unsigned int i=0; i<apts.size(); ++i) {
899     drawAirport((FGAirport*) apts[i].get());
900   }
901 }
902
903 class NavaidFilter : public FGPositioned::Filter
904 {
905 public:
906   NavaidFilter(bool fixesEnabled, bool navaidsEnabled) :
907     _fixes(fixesEnabled),
908     _navaids(navaidsEnabled)
909   {}
910   
911   virtual bool pass(FGPositioned* aPos) const { 
912     if (_fixes && (aPos->type() == FGPositioned::FIX)) {
913       // ignore fixes which end in digits - expirmental
914       if (isdigit(aPos->ident()[3]) && isdigit(aPos->ident()[4])) {
915         return false;
916       }
917     }
918     
919     return true;
920   }
921    
922   virtual FGPositioned::Type minType() const {
923     return _fixes ? FGPositioned::FIX : FGPositioned::VOR;
924   }
925    
926   virtual FGPositioned::Type maxType() const {
927     return _navaids ? FGPositioned::NDB : FGPositioned::FIX;
928   }
929   
930 private:
931   bool _fixes, _navaids;
932 };
933
934 void MapWidget::drawNavaids()
935 {
936   bool fixes = _root->getBoolValue("draw-fixes");
937   NavaidFilter f(fixes, _root->getBoolValue("draw-navaids"));
938     
939   if (f.minType() <= f.maxType()) {
940     FGPositioned::List navs = FGPositioned::findWithinRange(_projectionCenter, _drawRangeNm, &f);
941     
942     glLineWidth(1.0);
943     for (unsigned int i=0; i<navs.size(); ++i) {
944       FGPositioned::Type ty = navs[i]->type();
945       if (ty == FGPositioned::NDB) {
946         drawNDB(false, (FGNavRecord*) navs[i].get());
947       } else if (ty == FGPositioned::VOR) {
948         drawVOR(false, (FGNavRecord*) navs[i].get());
949       } else if (ty == FGPositioned::FIX) {
950         drawFix((FGFix*) navs[i].get());
951       }
952     } // of navaid iteration
953   } // of navaids || fixes are drawn test
954 }
955
956 void MapWidget::drawNDB(bool tuned, FGNavRecord* ndb)
957 {
958   SGVec2d pos = project(ndb->geod());
959   
960   if (tuned) {
961     glColor3f(0.0, 1.0, 1.0);
962   } else {
963     glColor3f(0.0, 0.0, 0.0);
964   }
965   
966   glEnable(GL_LINE_STIPPLE);
967   glLineStipple(1, 0x00FF);
968   circleAt(pos, 20, 6);
969   circleAt(pos, 20, 10);
970   glDisable(GL_LINE_STIPPLE);
971   
972   if (validDataForKey(ndb)) {
973     setAnchorForKey(ndb, pos);
974     return;
975   }
976   
977   char buffer[1024];
978         ::snprintf(buffer, 1024, "%s\n%s %3.0fKhz",
979                 ndb->name().c_str(), ndb->ident().c_str(),ndb->get_freq()/100.0);
980         
981   MapData* d = createDataForKey(ndb);
982   d->setPriority(40);
983   d->setLabel(ndb->ident());
984   d->setText(buffer);
985   d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 10);
986   d->setAnchor(pos);
987
988 }
989
990 void MapWidget::drawVOR(bool tuned, FGNavRecord* vor)
991 {
992   SGVec2d pos = project(vor->geod());
993   if (tuned) {
994     glColor3f(0.0, 1.0, 1.0);
995   } else {
996     glColor3f(0.0, 0.0, 1.0);
997   }
998   
999   circleAt(pos, 6, 8);
1000   
1001   if (validDataForKey(vor)) {
1002     setAnchorForKey(vor, pos);
1003     return;
1004   }
1005   
1006   char buffer[1024];
1007         ::snprintf(buffer, 1024, "%s\n%s %6.3fMhz",
1008                 vor->name().c_str(), vor->ident().c_str(),
1009     vor->get_freq() / 100.0);
1010         
1011   MapData* d = createDataForKey(vor);
1012   d->setText(buffer);
1013   d->setLabel(vor->ident());
1014   d->setPriority(tuned ? 10000 : 100);
1015   d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 12);
1016   d->setAnchor(pos);
1017 }
1018
1019 void MapWidget::drawFix(FGFix* fix)
1020 {
1021   SGVec2d pos = project(fix->geod());
1022   glColor3f(0.0, 0.0, 0.0);
1023   circleAt(pos, 3, 6);
1024   
1025   if (_zoom > SHOW_DETAIL_ZOOM) {
1026     return; // hide fix labels beyond a certain zoom level
1027   }
1028
1029   if (validDataForKey(fix)) {
1030     setAnchorForKey(fix, pos);
1031     return;
1032   }
1033   
1034   MapData* d = createDataForKey(fix);
1035   d->setLabel(fix->ident());
1036   d->setPriority(20);
1037   d->setOffset(MapData::VALIGN_CENTER | MapData::HALIGN_LEFT, 10);
1038   d->setAnchor(pos);
1039 }
1040
1041 void MapWidget::drawNavRadio(SGPropertyNode_ptr radio)
1042 {
1043   if (!radio || radio->getBoolValue("slaved-to-gps", false) 
1044         || !radio->getBoolValue("in-range", false)) {
1045     return;
1046   }
1047   
1048   if (radio->getBoolValue("nav-loc", false)) {
1049     drawTunedLocalizer(radio);
1050   }
1051   
1052   // identify the tuned station - unfortunately we don't get lat/lon directly,
1053   // need to do the frequency search again
1054   double mhz = radio->getDoubleValue("frequencies/selected-mhz", 0.0);
1055   FGNavRecord* nav = globals->get_navlist()->findByFreq(mhz, _aircraft);
1056   if (!nav || (nav->ident() != radio->getStringValue("nav-id"))) {
1057     // mismatch between navradio selection logic and ours!
1058     return;
1059   }
1060   
1061   glLineWidth(1.0);
1062   drawVOR(true, nav);
1063   
1064   SGVec2d pos = project(nav->geod());
1065   SGGeod range;
1066   double az2;
1067   double trueRadial = radio->getDoubleValue("radials/target-radial-deg");
1068   SGGeodesy::direct(nav->geod(), trueRadial, nav->get_range() * SG_NM_TO_METER, range, az2);
1069   SGVec2d prange = project(range);
1070   
1071   SGVec2d norm = normalize(prange - pos);
1072   SGVec2d perp(norm.y(), -norm.x());
1073   
1074   circleAt(pos, 64, length(prange - pos));
1075   drawLine(pos, prange);
1076   
1077 // draw to/from arrows
1078   SGVec2d midPoint = (pos + prange) * 0.5;
1079   if (radio->getBoolValue("from-flag")) {
1080     norm = -norm;
1081     perp = -perp;
1082   }
1083   
1084   int sz = 10;
1085   SGVec2d arrowB = midPoint - (norm * sz) + (perp * sz);
1086   SGVec2d arrowC = midPoint - (norm * sz) - (perp * sz);
1087   drawLine(midPoint, arrowB);
1088   drawLine(arrowB, arrowC);
1089   drawLine(arrowC, midPoint);
1090   
1091   drawLine(pos, (2 * pos) - prange); // reciprocal radial
1092 }
1093
1094 void MapWidget::drawTunedLocalizer(SGPropertyNode_ptr radio)
1095 {
1096   double mhz = radio->getDoubleValue("frequencies/selected-mhz", 0.0);
1097   FGNavRecord* loc = globals->get_loclist()->findByFreq(mhz, _aircraft);
1098   if (!loc || (loc->ident() != radio->getStringValue("nav-id"))) {
1099     // mismatch between navradio selection logic and ours!
1100     return;
1101   }
1102   
1103   if (loc->runway()) {
1104     drawILS(true, loc->runway());
1105   }
1106 }
1107
1108 /*
1109 void MapWidget::drawObstacle(FGPositioned* obs)
1110 {
1111   SGVec2d pos = project(obs->geod());
1112   glColor3f(0.0, 0.0, 0.0);
1113   glLineWidth(2.0);
1114   drawLine(pos, pos + SGVec2d());
1115 }
1116 */
1117
1118 void MapWidget::drawAirport(FGAirport* apt)
1119 {
1120         // draw tower location
1121         SGVec2d towerPos = project(apt->getTowerLocation());
1122   
1123   if (_zoom <= SHOW_DETAIL_ZOOM) {
1124     glColor3f(1.0, 1.0, 1.0);
1125     glLineWidth(1.0);
1126     
1127     drawLine(towerPos + SGVec2d(3, 0), towerPos + SGVec2d(3, 10));
1128     drawLine(towerPos + SGVec2d(-3, 0), towerPos + SGVec2d(-3, 10));
1129     drawLine(towerPos + SGVec2d(-6, 20), towerPos + SGVec2d(-3, 10));
1130     drawLine(towerPos + SGVec2d(6, 20), towerPos + SGVec2d(3, 10));
1131     drawLine(towerPos + SGVec2d(-6, 20), towerPos + SGVec2d(6, 20));
1132   }
1133   
1134   if (validDataForKey(apt)) {
1135     setAnchorForKey(apt, towerPos);
1136   } else {
1137     char buffer[1024];
1138     ::snprintf(buffer, 1024, "%s\n%s",
1139       apt->ident().c_str(), apt->name().c_str());
1140
1141     MapData* d = createDataForKey(apt);
1142     d->setText(buffer);
1143     d->setLabel(apt->ident());
1144     d->setPriority(100 + scoreAirportRunways(apt));
1145     d->setOffset(MapData::VALIGN_TOP | MapData::HALIGN_CENTER, 6);
1146     d->setAnchor(towerPos);
1147   }
1148   
1149   if (_zoom > SHOW_DETAIL_ZOOM) {
1150     return;
1151   }
1152
1153   for (unsigned int r=0; r<apt->numRunways(); ++r) {
1154     FGRunway* rwy = apt->getRunwayByIndex(r);
1155                 if (!rwy->isReciprocal()) {
1156                         drawRunwayPre(rwy);
1157                 }
1158   }
1159   
1160         for (unsigned int r=0; r<apt->numRunways(); ++r) {
1161                 FGRunway* rwy = apt->getRunwayByIndex(r);
1162                 if (!rwy->isReciprocal()) {
1163                         drawRunway(rwy);
1164                 }
1165                 
1166                 if (rwy->ILS()) {
1167                         drawILS(false, rwy);
1168                 }
1169         } // of runway iteration
1170         
1171 }
1172
1173 int MapWidget::scoreAirportRunways(FGAirport* apt)
1174 {
1175   bool needHardSurface = _root->getBoolValue("hard-surfaced-airports", true);
1176   double minLength = _root->getDoubleValue("min-runway-length-ft", 2000.0);
1177   
1178   int score = 0;
1179   unsigned int numRunways(apt->numRunways());
1180   for (unsigned int r=0; r<numRunways; ++r) {
1181     FGRunway* rwy = apt->getRunwayByIndex(r);
1182     if (rwy->isReciprocal()) {
1183       continue;
1184     }
1185
1186     if (needHardSurface && !rwy->isHardSurface()) {
1187       continue;
1188     }
1189     
1190     if (rwy->lengthFt() < minLength) {
1191       continue;
1192     }
1193     
1194     int scoreLength = SGMiscd::roundToInt(rwy->lengthFt() / 200.0);
1195     score += scoreLength;
1196   } // of runways iteration
1197
1198   return score;
1199 }
1200
1201 void MapWidget::drawRunwayPre(FGRunway* rwy)
1202 {
1203   SGVec2d p1 = project(rwy->begin());
1204         SGVec2d p2 = project(rwy->end());
1205         
1206   glLineWidth(4.0);
1207   glColor3f(1.0, 0.0, 1.0);
1208         drawLine(p1, p2);
1209 }
1210
1211 void MapWidget::drawRunway(FGRunway* rwy)
1212 {
1213         // line for runway 
1214         // optionally show active, stopway, etc
1215         // in legend, show published heading and length
1216         // and threshold elevation
1217         
1218   SGVec2d p1 = project(rwy->begin());
1219         SGVec2d p2 = project(rwy->end());
1220   glLineWidth(2.0);
1221   glColor3f(1.0, 1.0, 1.0);
1222   SGVec2d inset = normalize(p2 - p1) * 2;
1223   
1224         drawLine(p1 + inset, p2 - inset);
1225         
1226   if (validDataForKey(rwy)) {
1227     setAnchorForKey(rwy, (p1 + p2) * 0.5);
1228     return;
1229   }
1230   
1231         char buffer[1024];
1232         ::snprintf(buffer, 1024, "%s/%s\n%3.0f/%3.0f\n%.0f'",
1233                 rwy->ident().c_str(),
1234                 rwy->reciprocalRunway()->ident().c_str(),
1235                 rwy->headingDeg(),
1236                 rwy->reciprocalRunway()->headingDeg(),
1237                 rwy->lengthFt());
1238         
1239   MapData* d = createDataForKey(rwy);
1240   d->setText(buffer);
1241   d->setLabel(rwy->ident() + "/" + rwy->reciprocalRunway()->ident());
1242   d->setPriority(50);
1243   d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 12);
1244   d->setAnchor((p1 + p2) * 0.5);
1245 }
1246
1247 void MapWidget::drawILS(bool tuned, FGRunway* rwy)
1248 {
1249         // arrow, tip centered on the landing threshold
1250   // using LOC transmitter position would be more accurate, but
1251   // is visually cluttered
1252         // arrow width is based upon the computed localizer width
1253         
1254         FGNavRecord* loc = rwy->ILS();
1255         double halfBeamWidth = loc->localizerWidth() * 0.5;
1256         SGVec2d t = project(rwy->threshold());
1257         SGGeod locEnd;
1258         double rangeM = loc->get_range() * SG_NM_TO_METER;
1259         double radial = loc->get_multiuse();
1260   SG_NORMALIZE_RANGE(radial, 0.0, 360.0);
1261         double az2;
1262         
1263 // compute the three end points at the widge end of the arrow
1264         SGGeodesy::direct(loc->geod(), radial, -rangeM, locEnd, az2);
1265         SGVec2d endCentre = project(locEnd);
1266         
1267         SGGeodesy::direct(loc->geod(), radial + halfBeamWidth, -rangeM * 1.1, locEnd, az2);
1268         SGVec2d endR = project(locEnd);
1269         
1270         SGGeodesy::direct(loc->geod(), radial - halfBeamWidth, -rangeM * 1.1, locEnd, az2);
1271         SGVec2d endL = project(locEnd);
1272         
1273 // outline two triangles
1274   glLineWidth(1.0);
1275   if (tuned) {
1276     glColor3f(0.0, 1.0, 1.0);
1277   } else {
1278     glColor3f(0.0, 0.0, 1.0);
1279         }
1280   
1281   glBegin(GL_LINE_LOOP);
1282                 glVertex2dv(t.data());
1283                 glVertex2dv(endCentre.data());
1284                 glVertex2dv(endL.data());
1285         glEnd();
1286         glBegin(GL_LINE_LOOP);
1287                 glVertex2dv(t.data());
1288                 glVertex2dv(endCentre.data());
1289                 glVertex2dv(endR.data());
1290         glEnd();
1291 }
1292
1293 void MapWidget::drawTraffic()
1294 {
1295   if (!_root->getBoolValue("draw-traffic")) {
1296     return;
1297   }
1298   
1299   if (_zoom > SHOW_DETAIL_ZOOM) {
1300     return;
1301   }
1302   
1303   const SGPropertyNode* ai = fgGetNode("/ai/models", true);
1304
1305   for (int i = 0; i < ai->nChildren(); ++i) {
1306     const SGPropertyNode *model = ai->getChild(i);
1307     // skip bad or dead entries
1308     if (!model || model->getIntValue("id", -1) < 0) {
1309       continue;
1310     }
1311
1312     const std::string& name(model->getName());
1313     SGGeod pos = SGGeod::fromDegFt(
1314       model->getDoubleValue("position/longitude-deg"),
1315       model->getDoubleValue("position/latitude-deg"),
1316       model->getDoubleValue("position/altitude-ft"));
1317       
1318     double dist = SGGeodesy::distanceNm(_projectionCenter, pos);
1319     if (dist > _drawRangeNm) {
1320       continue;
1321     }
1322     
1323     double heading = model->getDoubleValue("orientation/true-heading-deg");
1324     if ((name == "aircraft") || (name == "multiplayer") || 
1325         (name == "wingman") || (name == "tanker")) {
1326       drawAIAircraft(model, pos, heading);
1327     } else if ((name == "ship") || (name == "carrier") || (name == "escort")) {
1328       drawAIShip(model, pos, heading);
1329     }
1330   } // of ai/models iteration
1331 }
1332
1333 void MapWidget::drawAIAircraft(const SGPropertyNode* model, const SGGeod& pos, double hdg)
1334 {
1335
1336   SGVec2d p = project(pos);
1337
1338   glColor3f(0.0, 0.0, 0.0);
1339   glLineWidth(2.0);
1340   circleAt(p, 4, 6.0); // black diamond
1341   
1342 // draw heading vector
1343   int speedKts = static_cast<int>(model->getDoubleValue("velocities/true-airspeed-kt"));
1344   if (speedKts > 1) {
1345     glLineWidth(1.0);
1346
1347     const double dt = 15.0 / (3600.0); // 15 seconds look-ahead
1348     double distanceM = speedKts * SG_NM_TO_METER * dt;
1349     
1350     SGGeod advance;
1351     double az2;
1352     SGGeodesy::direct(pos, hdg, distanceM, advance, az2);
1353     
1354     drawLine(p, project(advance));
1355   }
1356     
1357   if (validDataForKey((void*) model)) {
1358     setAnchorForKey((void*) model, p);
1359     return;
1360   }
1361   
1362   // draw callsign / altitude / speed
1363
1364   
1365   char buffer[1024];
1366         ::snprintf(buffer, 1024, "%s\n%d'\n%dkts",
1367                 model->getStringValue("callsign", "<>"),
1368                 static_cast<int>(pos.getElevationFt() / 50.0) * 50,
1369     speedKts);
1370         
1371   MapData* d = createDataForKey((void*) model);
1372   d->setText(buffer);
1373   d->setLabel(model->getStringValue("callsign", "<>"));
1374   d->setPriority(speedKts > 5 ? 60 : 10); // low priority for parked aircraft
1375   d->setOffset(MapData::VALIGN_CENTER | MapData::HALIGN_LEFT, 10);
1376   d->setAnchor(p);
1377
1378 }
1379
1380 void MapWidget::drawAIShip(const SGPropertyNode* model, const SGGeod& pos, double hdg)
1381 {
1382
1383 }
1384
1385 SGVec2d MapWidget::project(const SGGeod& geod) const
1386 {
1387   // Sanson-Flamsteed projection, relative to the projection center
1388   double r = earth_radius_lat(geod.getLatitudeRad());
1389   double lonDiff = geod.getLongitudeRad() - _projectionCenter.getLongitudeRad(),
1390     latDiff = geod.getLatitudeRad() - _projectionCenter.getLatitudeRad();
1391   
1392   SGVec2d p = SGVec2d(cos(geod.getLatitudeRad()) * lonDiff, latDiff) * r * currentScale();
1393   
1394 // rotate as necessary
1395   double cost = cos(_upHeading * SG_DEGREES_TO_RADIANS), 
1396     sint = sin(_upHeading * SG_DEGREES_TO_RADIANS);
1397   double rx = cost * p.x() - sint * p.y();
1398   double ry = sint * p.x() + cost * p.y();
1399   return SGVec2d(rx, ry);
1400 }
1401
1402 SGGeod MapWidget::unproject(const SGVec2d& p) const
1403 {
1404   // unrotate, if necessary
1405   double cost = cos(-_upHeading * SG_DEGREES_TO_RADIANS), 
1406     sint = sin(-_upHeading * SG_DEGREES_TO_RADIANS);
1407   SGVec2d ur(cost * p.x() - sint * p.y(), 
1408              sint * p.x() + cost * p.y());
1409   
1410   double r = earth_radius_lat(_projectionCenter.getLatitudeRad());
1411   SGVec2d unscaled = ur * (1.0 / (currentScale() * r));
1412   
1413   double lat = unscaled.y() + _projectionCenter.getLatitudeRad();
1414   double lon = (unscaled.x() / cos(lat)) + _projectionCenter.getLongitudeRad();
1415   
1416   return SGGeod::fromRad(lon, lat);
1417 }
1418
1419 double MapWidget::currentScale() const
1420 {
1421   return 1.0 / pow(2.0, _zoom);
1422 }
1423
1424 void MapWidget::circleAt(const SGVec2d& center, int nSides, double r)
1425 {
1426   glBegin(GL_LINE_LOOP);
1427   double advance = (SGD_PI * 2) / nSides;
1428   glVertex2d(center.x(), center.y() + r);
1429   double t=advance;
1430   for (int i=1; i<nSides; ++i) {
1431     glVertex2d(center.x() + (sin(t) * r), center.y() + (cos(t) * r));
1432     t += advance;
1433   }
1434   glEnd();
1435 }
1436
1437 void MapWidget::circleAtAlt(const SGVec2d& center, int nSides, double r, double r2)
1438 {
1439   glBegin(GL_LINE_LOOP);
1440   double advance = (SGD_PI * 2) / nSides;
1441   glVertex2d(center.x(), center.y() + r);
1442   double t=advance;
1443   for (int i=1; i<nSides; ++i) {
1444     double rr = (i%2 == 0) ? r : r2;
1445     glVertex2d(center.x() + (sin(t) * rr), center.y() + (cos(t) * rr));
1446     t += advance;
1447   }
1448   glEnd();
1449 }
1450
1451 void MapWidget::drawLine(const SGVec2d& p1, const SGVec2d& p2)
1452 {
1453   glBegin(GL_LINES);
1454     glVertex2dv(p1.data());
1455     glVertex2dv(p2.data());
1456   glEnd();
1457 }
1458
1459 void MapWidget::drawLegendBox(const SGVec2d& pos, const std::string& t)
1460 {
1461         std::vector<std::string> lines(simgear::strutils::split(t, "\n"));
1462         const int LINE_LEADING = 4;
1463         const int MARGIN = 4;
1464         
1465 // measure
1466         int maxWidth = -1, totalHeight = 0;
1467         int lineHeight = legendFont.getStringHeight();
1468         
1469         for (unsigned int ln=0; ln<lines.size(); ++ln) {
1470                 totalHeight += lineHeight;
1471                 if (ln > 0) {
1472                         totalHeight += LINE_LEADING;
1473                 }
1474                 
1475                 int lw = legendFont.getStringWidth(lines[ln].c_str());
1476                 maxWidth = std::max(maxWidth, lw);
1477         } // of line measurement
1478         
1479         if (maxWidth < 0) {
1480                 return; // all lines are empty, don't draw
1481         }
1482         
1483         totalHeight += MARGIN * 2;
1484
1485 // draw box
1486         puBox box;
1487         box.min[0] = 0;
1488         box.min[1] = -totalHeight;
1489         box.max[0] = maxWidth + (MARGIN * 2);
1490         box.max[1] = 0;
1491         int border = 1;
1492         box.draw (pos.x(), pos.y(), PUSTYLE_DROPSHADOW, colour, FALSE, border);
1493         
1494 // draw lines
1495         int xPos = pos.x() + MARGIN;
1496         int yPos = pos.y() - (lineHeight + MARGIN);
1497         glColor3f(0.8, 0.8, 0.8);
1498   
1499         for (unsigned int ln=0; ln<lines.size(); ++ln) {
1500                 legendFont.drawString(lines[ln].c_str(), xPos, yPos);
1501                 yPos -= lineHeight + LINE_LEADING;
1502         }
1503 }
1504
1505 void MapWidget::drawData()
1506 {
1507   std::sort(_dataQueue.begin(), _dataQueue.end(), MapData::order);
1508   
1509   int hw = _width >> 1, 
1510     hh = _height >> 1;
1511   puBox visBox(makePuBox(-hw, -hh, _width, _height));
1512   
1513   unsigned int d = 0;
1514   int drawn = 0;
1515   std::vector<MapData*> drawQueue;
1516   
1517   bool drawData = _root->getBoolValue("draw-data");
1518   const int MAX_DRAW_DATA = 25;
1519   const int MAX_DRAW = 50;
1520   
1521   for (; (d < _dataQueue.size()) && (drawn < MAX_DRAW); ++d) {
1522     MapData* md = _dataQueue[d];
1523     md->setDataVisible(drawData);
1524     
1525     if (md->isClipped(visBox)) {
1526       continue;
1527     }
1528     
1529     if (md->overlaps(drawQueue)) {
1530       if (drawData) { // overlapped with data, let's try just the label
1531         md->setDataVisible(false);
1532         if (md->overlaps(drawQueue)) {
1533           continue;
1534         }
1535       } else {
1536         continue;
1537       }
1538     } // of overlaps case
1539     
1540     drawQueue.push_back(md);
1541     ++drawn;
1542     if (drawData && (drawn >= MAX_DRAW_DATA)) {
1543       drawData = false;
1544     }
1545   }
1546     
1547   // draw lowest-priority first, so higher-priorty items appear on top
1548   std::vector<MapData*>::reverse_iterator r;
1549   for (r = drawQueue.rbegin(); r!= drawQueue.rend(); ++r) {
1550     (*r)->draw();
1551   }
1552   
1553   _dataQueue.clear();
1554   KeyDataMap::iterator it = _mapData.begin();
1555   for (; it != _mapData.end(); ) {
1556     it->second->age();
1557     if (it->second->isExpired()) {
1558       delete it->second;
1559       KeyDataMap::iterator cur = it++;
1560       _mapData.erase(cur);
1561     } else {
1562       ++it;
1563     }
1564   } // of expiry iteration
1565 }
1566
1567 bool MapWidget::validDataForKey(void* key)
1568 {
1569   KeyDataMap::iterator it = _mapData.find(key);
1570   if (it == _mapData.end()) {
1571     return false; // no valid data for the key!
1572   }
1573   
1574   it->second->resetAge(); // mark data as valid this frame
1575   _dataQueue.push_back(it->second);
1576   return true;
1577 }
1578
1579 void MapWidget::setAnchorForKey(void* key, const SGVec2d& anchor)
1580 {
1581   KeyDataMap::iterator it = _mapData.find(key);
1582   if (it == _mapData.end()) {
1583     throw sg_exception("no valid data for key!");
1584   }
1585   
1586   it->second->setAnchor(anchor);
1587 }
1588
1589 MapData* MapWidget::getOrCreateDataForKey(void* key)
1590 {
1591   KeyDataMap::iterator it = _mapData.find(key);
1592   if (it == _mapData.end()) {
1593     return createDataForKey(key);
1594   }
1595   
1596   it->second->resetAge(); // mark data as valid this frame
1597   _dataQueue.push_back(it->second);
1598   return it->second;
1599 }
1600
1601 MapData* MapWidget::createDataForKey(void* key)
1602 {
1603   KeyDataMap::iterator it = _mapData.find(key);
1604   if (it != _mapData.end()) {
1605     throw sg_exception("duplicate data requested for key!");
1606   }
1607   
1608   MapData* d =  new MapData(0);
1609   _mapData[key] = d;
1610   _dataQueue.push_back(d);
1611   d->resetAge();
1612   return d;
1613 }