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