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