]> git.mxchange.org Git - flightgear.git/blob - src/GUI/BaseDiagram.cxx
Clamp diagram zoom.
[flightgear.git] / src / GUI / BaseDiagram.cxx
1 // BaseDiagram.cxx - part of GUI launcher using Qt5
2 //
3 // Written by James Turner, started December 2014.
4 //
5 // Copyright (C) 2014 James Turner <zakalawe@mac.com>
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20
21 #include "BaseDiagram.hxx"
22
23 #include <limits>
24
25 #include <QPainter>
26 #include <QDebug>
27 #include <QVector2D>
28 #include <QMouseEvent>
29
30 #include <Navaids/navrecord.hxx>
31 #include <Navaids/positioned.hxx>
32 #include <Airports/airport.hxx>
33 #include <Navaids/PolyLine.hxx>
34
35 #include "QtLauncher_fwd.hxx"
36
37 /* equatorial and polar earth radius */
38 const float rec  = 6378137;          // earth radius, equator (?)
39 const float rpol = 6356752.314f;      // earth radius, polar   (?)
40
41 const double MINIMUM_SCALE = 0.002;
42
43 //Returns Earth radius at a given latitude (Ellipsoide equation with two equal axis)
44 static float earth_radius_lat( float lat )
45 {
46     double a = cos(lat)/rec;
47     double b = sin(lat)/rpol;
48     return 1.0f / sqrt( a * a + b * b );
49 }
50
51 BaseDiagram::BaseDiagram(QWidget* pr) :
52     QWidget(pr),
53     m_autoScalePan(true),
54     m_wheelAngleDeltaAccumulator(0)
55 {
56     setSizePolicy(QSizePolicy::MinimumExpanding,
57                   QSizePolicy::MinimumExpanding);
58     setMinimumSize(100, 100);
59 }
60
61 QTransform BaseDiagram::transform() const
62 {
63     QTransform t;
64     t.translate(width() / 2, height() / 2); // center projection origin in the widget
65     t.scale(m_scale, m_scale);
66
67     // apply any pan offset that exists
68     t.translate(m_panOffset.x(), m_panOffset.y());
69     // center the bounding box (may not be at the origin)
70     t.translate(-m_bounds.center().x(), -m_bounds.center().y());
71     return t;
72 }
73
74 void BaseDiagram::clearIgnoredNavaids()
75 {
76     m_ignored.clear();
77 }
78
79 void BaseDiagram::addIgnoredNavaid(FGPositionedRef pos)
80 {
81     if (isNavaidIgnored(pos))
82         return;
83     m_ignored.push_back(pos);
84 }
85
86 void BaseDiagram::extendRect(QRectF &r, const QPointF &p)
87 {
88     if (p.x() < r.left()) {
89         r.setLeft(p.x());
90     } else if (p.x() > r.right()) {
91         r.setRight(p.x());
92     }
93
94     if (p.y() < r.top()) {
95         r.setTop(p.y());
96     } else if (p.y() > r.bottom()) {
97         r.setBottom(p.y());
98     }
99 }
100
101 void BaseDiagram::paintEvent(QPaintEvent* pe)
102 {
103     QPainter p(this);
104     p.setRenderHints(QPainter::Antialiasing);
105     p.fillRect(rect(), QColor(0x3f, 0x3f, 0x3f));
106
107     if (m_autoScalePan) {
108         // fit bounds within our available space, allowing for a margin
109         const int MARGIN = 32; // pixels
110         double ratioInX = (width() - MARGIN * 2) / m_bounds.width();
111         double ratioInY = (height() - MARGIN * 2) / m_bounds.height();
112         m_scale = std::min(ratioInX, ratioInY);
113     }
114
115     QTransform t(transform());
116     p.setTransform(t);
117
118     paintPolygonData(&p);
119
120     paintNavaids(&p);
121
122     paintContents(&p);
123 }
124
125 void BaseDiagram::paintAirplaneIcon(QPainter* painter, const SGGeod& geod, int headingDeg)
126 {
127     QPointF pos = project(geod);
128     QPixmap pix(":/airplane-icon");
129     pos = painter->transform().map(pos);
130     painter->resetTransform();
131     painter->translate(pos.x(), pos.y());
132     painter->rotate(headingDeg);
133
134     painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
135     QRect airplaneIconRect = pix.rect();
136     airplaneIconRect.moveCenter(QPoint(0,0));
137     painter->drawPixmap(airplaneIconRect, pix);
138 }
139
140 void BaseDiagram::paintPolygonData(QPainter* painter)
141 {
142     QTransform xf = painter->transform();
143     QTransform invT = xf.inverted();
144
145     SGGeod topLeft = unproject(invT.map(rect().topLeft()), m_projectionCenter);
146     SGGeod viewCenter = unproject(invT.map(rect().center()), m_projectionCenter);
147     SGGeod bottomRight = unproject(invT.map(rect().bottomRight()), m_projectionCenter);
148
149     double drawRangeNm = std::max(SGGeodesy::distanceNm(viewCenter, topLeft),
150                                   SGGeodesy::distanceNm(viewCenter, bottomRight));
151
152     flightgear::PolyLineList lines(flightgear::PolyLine::linesNearPos(viewCenter, drawRangeNm,
153                                                                       flightgear::PolyLine::COASTLINE));
154
155     QPen waterPen(QColor(64, 64, 255), 1);
156     waterPen.setCosmetic(true);
157     painter->setPen(waterPen);
158     flightgear::PolyLineList::const_iterator it;
159     for (it=lines.begin(); it != lines.end(); ++it) {
160         paintGeodVec(painter, (*it)->points());
161     }
162
163     lines = flightgear::PolyLine::linesNearPos(viewCenter, drawRangeNm,
164                                               flightgear::PolyLine::URBAN);
165     for (it=lines.begin(); it != lines.end(); ++it) {
166         fillClosedGeodVec(painter, QColor(192, 192, 96), (*it)->points());
167     }
168
169     lines = flightgear::PolyLine::linesNearPos(viewCenter, drawRangeNm,
170                                               flightgear::PolyLine::RIVER);
171
172     painter->setPen(waterPen);
173     for (it=lines.begin(); it != lines.end(); ++it) {
174         paintGeodVec(painter, (*it)->points());
175     }
176
177
178     lines = flightgear::PolyLine::linesNearPos(viewCenter, drawRangeNm,
179                                               flightgear::PolyLine::LAKE);
180
181     for (it=lines.begin(); it != lines.end(); ++it) {
182         fillClosedGeodVec(painter, QColor(128, 128, 255),
183                           (*it)->points());
184     }
185
186
187 }
188
189 void BaseDiagram::paintGeodVec(QPainter* painter, const flightgear::SGGeodVec& vec)
190 {
191     QVector<QPointF> projected;
192     projected.reserve(vec.size());
193     flightgear::SGGeodVec::const_iterator it;
194     for (it=vec.begin(); it != vec.end(); ++it) {
195         projected.append(project(*it));
196     }
197
198     painter->drawPolyline(projected.data(), projected.size());
199 }
200
201 void BaseDiagram::fillClosedGeodVec(QPainter* painter, const QColor& color, const flightgear::SGGeodVec& vec)
202 {
203     QVector<QPointF> projected;
204     projected.reserve(vec.size());
205     flightgear::SGGeodVec::const_iterator it;
206     for (it=vec.begin(); it != vec.end(); ++it) {
207         projected.append(project(*it));
208     }
209
210     painter->setPen(Qt::NoPen);
211     painter->setBrush(color);
212     painter->drawPolygon(projected.data(), projected.size());
213 }
214
215 class MapFilter : public FGPositioned::TypeFilter
216 {
217 public:
218
219     MapFilter(LauncherAircraftType aircraft)
220     {
221       //  addType(FGPositioned::FIX);
222         addType(FGPositioned::NDB);
223         addType(FGPositioned::VOR);
224
225         if (aircraft == Helicopter) {
226             addType(FGPositioned::HELIPAD);
227         }
228
229         if (aircraft == Seaplane) {
230             addType(FGPositioned::SEAPORT);
231         } else {
232             addType(FGPositioned::AIRPORT);
233         }
234     }
235
236     virtual bool pass(FGPositioned* aPos) const
237     {
238         bool ok = TypeFilter::pass(aPos);
239         // fix-filtering code disabled since fixed are entirely disabled
240 #if 0
241         if (ok && (aPos->type() == FGPositioned::FIX)) {
242             // ignore fixes which end in digits
243             if (aPos->ident().length() > 4 && isdigit(aPos->ident()[3]) && isdigit(aPos->ident()[4])) {
244                 return false;
245             }
246         }
247 #endif
248         return ok;
249     }
250 };
251
252 void BaseDiagram::splitItems(const FGPositionedList& in, FGPositionedList& navaids,
253                              FGPositionedList& ports)
254 {
255     FGPositionedList::const_iterator it = in.begin();
256     for (; it != in.end(); ++it) {
257         if (FGAirport::isAirportType(it->ptr())) {
258             ports.push_back(*it);
259         } else {
260             navaids.push_back(*it);
261         }
262     }
263 }
264
265 bool orderAirportsByRunwayLength(const FGPositionedRef& a,
266                                  const FGPositionedRef& b)
267 {
268     FGAirport* aptA = static_cast<FGAirport*>(a.ptr());
269     FGAirport* aptB = static_cast<FGAirport*>(b.ptr());
270
271     return aptA->longestRunway()->lengthFt() > aptB->longestRunway()->lengthFt();
272 }
273
274 void BaseDiagram::paintNavaids(QPainter* painter)
275 {
276     QTransform xf = painter->transform();
277     painter->setTransform(QTransform()); // reset to identity
278     QTransform invT = xf.inverted();
279
280
281     SGGeod topLeft = unproject(invT.map(rect().topLeft()), m_projectionCenter);
282     SGGeod viewCenter = unproject(invT.map(rect().center()), m_projectionCenter);
283     SGGeod bottomRight = unproject(invT.map(rect().bottomRight()), m_projectionCenter);
284
285     double drawRangeNm = std::max(SGGeodesy::distanceNm(viewCenter, topLeft),
286                                   SGGeodesy::distanceNm(viewCenter, bottomRight));
287
288     MapFilter f(m_aircraftType);
289     FGPositionedList items = FGPositioned::findWithinRange(viewCenter, drawRangeNm, &f);
290
291     FGPositionedList navaids, ports;
292     splitItems(items, navaids, ports);
293
294     if (ports.size() >= 40) {
295         FGPositionedList::iterator middle = ports.begin() + 40;
296         std::partial_sort(ports.begin(), middle, ports.end(),
297                           orderAirportsByRunwayLength);
298         ports.resize(40);
299     }
300
301     m_labelRects.clear();
302     m_labelRects.reserve(items.size());
303
304     FGPositionedList::const_iterator it;
305     for (it = ports.begin(); it != ports.end(); ++it) {
306         paintNavaid(painter, xf, *it);
307     }
308
309     for (it = navaids.begin(); it != navaids.end(); ++it) {
310         paintNavaid(painter, xf, *it);
311     }
312
313
314     // restore transform
315     painter->setTransform(xf);
316 }
317
318 QRect boundsOfLines(const QVector<QLineF>& lines)
319 {
320     QRect r;
321     Q_FOREACH(const QLineF& l, lines) {
322         r = r.united(QRectF(l.p1(), l.p2()).toRect());
323     }
324
325     return r;
326 }
327
328 void BaseDiagram::paintNavaid(QPainter* painter, const QTransform& t, const FGPositionedRef &pos)
329 {
330     if (isNavaidIgnored(pos))
331         return;
332
333     bool drawAsIcon = true;
334     const double minRunwayLengthFt = (16 / m_scale) * SG_METER_TO_FEET;
335     const FGPositioned::Type ty(pos->type());
336     const bool isNDB = (ty == FGPositioned::NDB);
337     QRect iconRect;
338
339     if (ty == FGPositioned::AIRPORT) {
340         FGAirport* apt = static_cast<FGAirport*>(pos.ptr());
341         if (apt->hasHardRunwayOfLengthFt(minRunwayLengthFt)) {
342
343             drawAsIcon = false;
344             painter->setTransform(t);
345             QVector<QLineF> lines = projectAirportRuwaysWithCenter(apt, m_projectionCenter);
346
347             QPen pen(QColor(0x03, 0x83, 0xbf), 8);
348             pen.setCosmetic(true);
349             painter->setPen(pen);
350             painter->drawLines(lines);
351
352             QPen linePen(Qt::white, 2);
353             linePen.setCosmetic(true);
354             painter->setPen(linePen);
355             painter->drawLines(lines);
356
357             painter->resetTransform();
358
359             iconRect = t.mapRect(boundsOfLines(lines));
360         }
361     }
362
363     if (drawAsIcon) {
364         QPixmap pm = iconForPositioned(pos);
365         QPointF loc = t.map(project(pos->geod()));
366         iconRect = pm.rect();
367         iconRect.moveCenter(loc.toPoint());
368         painter->drawPixmap(iconRect, pm);
369     }
370
371    // compute label text so we can measure it
372     QString label;
373     if (FGAirport::isAirportType(pos.ptr())) {
374         label = QString::fromStdString(pos->name());
375         label = fixNavaidName(label);
376     } else {
377         label = QString::fromStdString(pos->ident());
378     }
379
380     if (ty == FGPositioned::NDB) {
381         FGNavRecord* nav = static_cast<FGNavRecord*>(pos.ptr());
382         label.append("\n").append(QString::number(nav->get_freq() / 100));
383     } else if (ty == FGPositioned::VOR) {
384         FGNavRecord* nav = static_cast<FGNavRecord*>(pos.ptr());
385         label.append("\n").append(QString::number(nav->get_freq() / 100.0, 'f', 1));
386     }
387
388     QRect textBounds = painter->boundingRect(QRect(0, 0, 100, 100),
389                                              Qt::TextWordWrap, label);
390     int textFlags;
391     textBounds = rectAndFlagsForLabel(pos->guid(), iconRect,
392                                       textBounds.size(),
393                                       textFlags);
394
395     painter->setPen(isNDB ? QColor(0x9b, 0x5d, 0xa2) : QColor(0x03, 0x83, 0xbf));
396     painter->drawText(textBounds, textFlags, label);
397 }
398
399 bool BaseDiagram::isNavaidIgnored(const FGPositionedRef &pos) const
400 {
401     return m_ignored.contains(pos);
402 }
403
404 bool BaseDiagram::isLabelRectAvailable(const QRect &r) const
405 {
406     Q_FOREACH(const QRect& lr, m_labelRects) {
407         if (lr.intersects(r))
408             return false;
409     }
410
411     return true;
412 }
413
414 int BaseDiagram::textFlagsForLabelPosition(LabelPosition pos)
415 {
416 #if 0
417     switch (pos) {
418     case LABEL_RIGHT:       return Qt::AlignLeft | Qt::AlignVCenter;
419     case LABEL_ABOVE:       return Qt::AlignHCenter | Qt::A
420     }
421 #endif
422     return 0;
423 }
424
425 QRect BaseDiagram::rectAndFlagsForLabel(PositionedID guid, const QRect& item,
426                                         const QSize &bounds,
427                                         int& flags) const
428 {
429     m_labelRects.append(item);
430     int pos = m_labelPositions.value(guid, LABEL_RIGHT);
431     bool firstAttempt = true;
432     flags = Qt::TextWordWrap;
433
434     while (pos < LAST_POSITION) {
435         QRect r = labelPositioned(item, bounds, static_cast<LabelPosition>(pos));
436         if (isLabelRectAvailable(r)) {
437             m_labelRects.append(r);
438             m_labelPositions[guid] = static_cast<LabelPosition>(pos);
439             flags |= textFlagsForLabelPosition(static_cast<LabelPosition>(pos));
440             return r;
441         } else if (firstAttempt && (pos != LABEL_RIGHT)) {
442             pos = LABEL_RIGHT;
443         } else {
444             ++pos;
445         }
446
447         firstAttempt = false;
448     }
449
450     return QRect(item.x(), item.y(), bounds.width(), bounds.height());
451 }
452
453 QRect BaseDiagram::labelPositioned(const QRect& itemRect,
454                                    const QSize& bounds,
455                                    LabelPosition lp) const
456 {
457     const int SHORT_MARGIN = 4;
458     const int DIAGONAL_MARGIN = 12;
459
460     QPoint topLeft = itemRect.topLeft();
461
462     switch (lp) {
463     // cardinal compass points are short (close in)
464     case LABEL_RIGHT:
465         topLeft = QPoint(itemRect.right() + SHORT_MARGIN,
466                      itemRect.center().y() - bounds.height() / 2);
467         break;
468     case LABEL_ABOVE:
469         topLeft = QPoint(itemRect.center().x() - (bounds.width() / 2),
470                      itemRect.top() - (SHORT_MARGIN + bounds.height()));
471         break;
472     case LABEL_BELOW:
473         topLeft = QPoint(itemRect.center().x() - (bounds.width() / 2),
474                      itemRect.bottom() + SHORT_MARGIN);
475         break;
476     case LABEL_LEFT:
477         topLeft = QPoint(itemRect.left() - (SHORT_MARGIN + bounds.width()),
478                      itemRect.center().y() - bounds.height() / 2);
479         break;
480
481     // first diagonals are further out (to hopefully have a better chance
482     // of finding clear space
483
484     case LABEL_NE:
485         topLeft = QPoint(itemRect.right() + DIAGONAL_MARGIN,
486                      itemRect.top() - (DIAGONAL_MARGIN + bounds.height()));
487         break;
488
489     case LABEL_NW:
490         topLeft = QPoint(itemRect.left() - (DIAGONAL_MARGIN + bounds.width()),
491                      itemRect.top() - (DIAGONAL_MARGIN + bounds.height()));
492         break;
493
494     case LABEL_SE:
495         topLeft = QPoint(itemRect.right() + DIAGONAL_MARGIN,
496                      itemRect.bottom() + DIAGONAL_MARGIN);
497         break;
498
499     case LABEL_SW:
500         topLeft = QPoint(itemRect.left() - (DIAGONAL_MARGIN + bounds.width()),
501                      itemRect.bottom() + DIAGONAL_MARGIN);
502         break;
503     default:
504         qWarning() << Q_FUNC_INFO << "Implement me";
505
506     }
507
508     return QRect(topLeft, bounds);
509 }
510
511 void BaseDiagram::mousePressEvent(QMouseEvent *me)
512 {
513     m_lastMousePos = me->pos();
514     m_didPan = false;
515 }
516
517 void BaseDiagram::mouseMoveEvent(QMouseEvent *me)
518 {
519     m_autoScalePan = false;
520
521     QPointF delta = me->pos() - m_lastMousePos;
522     m_lastMousePos = me->pos();
523
524     // offset is stored in metres so we don't have to modify it when
525     // zooming
526     m_panOffset += (delta / m_scale);
527     m_didPan = true;
528
529     update();
530 }
531
532 int intSign(int v)
533 {
534     return (v == 0) ? 0 : (v < 0) ? -1 : 1;
535 }
536
537 void BaseDiagram::wheelEvent(QWheelEvent *we)
538 {
539     m_autoScalePan = false;
540
541     int delta = we->angleDelta().y();
542     if (delta == 0)
543         return;
544
545     if (intSign(m_wheelAngleDeltaAccumulator) != intSign(delta)) {
546         m_wheelAngleDeltaAccumulator = 0;
547     }
548
549     m_wheelAngleDeltaAccumulator += delta;
550     if (m_wheelAngleDeltaAccumulator > 120) {
551         m_wheelAngleDeltaAccumulator = 0;
552
553         m_scale *= 1.5;
554
555     } else if (m_wheelAngleDeltaAccumulator < -120) {
556         m_wheelAngleDeltaAccumulator = 0;
557
558         m_scale *= 0.75;
559     }
560
561     SG_CLAMP_RANGE(m_scale, MINIMUM_SCALE, 1.0);
562     update();
563 }
564
565 void BaseDiagram::paintContents(QPainter* painter)
566 {
567 }
568
569 void BaseDiagram::recomputeBounds(bool resetZoom)
570 {
571     m_bounds = QRectF();
572     doComputeBounds();
573
574     if (resetZoom) {
575         m_autoScalePan = true;
576         m_scale = 1.0;
577         m_panOffset = QPointF();
578     }
579
580     update();
581 }
582
583 void BaseDiagram::doComputeBounds()
584 {
585     // no-op in the base class
586 }
587
588 void BaseDiagram::extendBounds(const QPointF& p)
589 {
590     extendRect(m_bounds, p);
591 }
592
593 QPointF BaseDiagram::project(const SGGeod& geod, const SGGeod& center)
594 {
595     double r = earth_radius_lat(geod.getLatitudeRad());
596     double ref_lat = center.getLatitudeRad(),
597     ref_lon = center.getLongitudeRad(),
598     lat = geod.getLatitudeRad(),
599     lon = geod.getLongitudeRad(),
600     lonDiff = lon - ref_lon;
601
602     double c = acos( sin(ref_lat) * sin(lat) + cos(ref_lat) * cos(lat) * cos(lonDiff) );
603     if (c == 0.0) {
604         // angular distance from center is 0
605         return QPointF(0.0, 0.0);
606     }
607
608     double k = c / sin(c);
609     double x, y;
610     if (ref_lat == (90 * SG_DEGREES_TO_RADIANS))
611     {
612         x = (SGD_PI / 2 - lat) * sin(lonDiff);
613         y = -(SGD_PI / 2 - lat) * cos(lonDiff);
614     }
615     else if (ref_lat == -(90 * SG_DEGREES_TO_RADIANS))
616     {
617         x = (SGD_PI / 2 + lat) * sin(lonDiff);
618         y = (SGD_PI / 2 + lat) * cos(lonDiff);
619     }
620     else
621     {
622         x = k * cos(lat) * sin(lonDiff);
623         y = k * ( cos(ref_lat) * sin(lat) - sin(ref_lat) * cos(lat) * cos(lonDiff) );
624     }
625
626     // flip for top-left origin
627     return QPointF(x, -y) * r;
628 }
629
630 SGGeod BaseDiagram::unproject(const QPointF& xy, const SGGeod& center)
631 {
632     double r = earth_radius_lat(center.getLatitudeRad());
633     double lat = 0,
634            lon = 0,
635            ref_lat = center.getLatitudeRad(),
636            ref_lon = center.getLongitudeRad(),
637            rho = QVector2D(xy).length(),
638            c = rho/r;
639
640     if (rho == 0) {
641         return center;
642     }
643
644     // invert y to balance the equivalent in project()
645     double x = xy.x(),
646             y = -xy.y();
647     lat = asin( cos(c) * sin(ref_lat) + (y * sin(c) * cos(ref_lat)) / rho);
648
649     if (ref_lat == (90 * SG_DEGREES_TO_RADIANS)) // north pole
650     {
651         lon = ref_lon + atan(-x/y);
652     }
653     else if (ref_lat == -(90 * SG_DEGREES_TO_RADIANS)) // south pole
654     {
655         lon = ref_lon + atan(x/y);
656     }
657     else
658     {
659         lon = ref_lon + atan(x* sin(c) / (rho * cos(ref_lat) * cos(c) - y * sin(ref_lat) * sin(c)));
660     }
661
662     return SGGeod::fromRad(lon, lat);
663 }
664
665 QPointF BaseDiagram::project(const SGGeod& geod) const
666 {
667     return project(geod, m_projectionCenter);
668 }
669
670 QPixmap BaseDiagram::iconForPositioned(const FGPositionedRef& pos,
671                                        const IconOptions& options)
672 {
673     // if airport type, check towered or untowered
674     bool small = options.testFlag(SmallIcons);
675
676     bool isTowered = false;
677     if (FGAirport::isAirportType(pos)) {
678         FGAirport* apt = static_cast<FGAirport*>(pos.ptr());
679         isTowered = apt->hasTower();
680     }
681
682     switch (pos->type()) {
683     case FGPositioned::VOR:
684         if (static_cast<FGNavRecord*>(pos.ptr())->isVORTAC())
685             return QPixmap(":/vortac-icon");
686
687         if (static_cast<FGNavRecord*>(pos.ptr())->hasDME())
688             return QPixmap(":/vor-dme-icon");
689
690         return QPixmap(":/vor-icon");
691
692     case FGPositioned::AIRPORT:
693         return iconForAirport(static_cast<FGAirport*>(pos.ptr()), options);
694
695     case FGPositioned::HELIPORT:
696         return QPixmap(":/heliport-icon");
697     case FGPositioned::SEAPORT:
698         return QPixmap(isTowered ? ":/seaport-tower-icon" : ":/seaport-icon");
699     case FGPositioned::NDB:
700         return QPixmap(small ? ":/ndb-small-icon" : ":/ndb-icon");
701     case FGPositioned::FIX:
702         return QPixmap(":/waypoint-icon");
703
704     default:
705         break;
706     }
707
708     return QPixmap();
709 }
710
711 QPixmap BaseDiagram::iconForAirport(FGAirport* apt, const IconOptions& options)
712 {
713     if (apt->isClosed()) {
714         return QPixmap(":/airport-closed-icon");
715     }
716
717     if (!apt->hasHardRunwayOfLengthFt(1500)) {
718         return QPixmap(apt->hasTower() ? ":/airport-tower-icon" : ":/airport-icon");
719     }
720
721     if (options.testFlag(LargeAirportPlans) && apt->hasHardRunwayOfLengthFt(8500)) {
722         QPixmap result(32, 32);
723         result.fill(Qt::transparent);
724         {
725             QPainter p(&result);
726             p.setRenderHint(QPainter::Antialiasing, true);
727             QRectF b = result.rect().adjusted(4, 4, -4, -4);
728             QVector<QLineF> lines = projectAirportRuwaysIntoRect(apt, b);
729
730             p.setPen(QPen(QColor(0x03, 0x83, 0xbf), 8));
731             p.drawLines(lines);
732
733             p.setPen(QPen(Qt::white, 2));
734             p.drawLines(lines);
735         }
736         return result;
737     }
738
739     QPixmap result(25, 25);
740     result.fill(Qt::transparent);
741
742     {
743         QPainter p(&result);
744         p.setRenderHint(QPainter::Antialiasing, true);
745         p.setPen(Qt::NoPen);
746
747         p.setBrush(apt->hasTower() ? QColor(0x03, 0x83, 0xbf) :
748                                      QColor(0x9b, 0x5d, 0xa2));
749         p.drawEllipse(QPointF(13, 13), 10, 10);
750
751         FGRunwayRef r = apt->longestRunway();
752
753         p.setPen(QPen(Qt::white, 2));
754         p.translate(13, 13);
755         p.rotate(r->headingDeg());
756         p.drawLine(0, -8, 0, 8);
757     }
758
759     return result;
760 }
761
762 QVector<QLineF> BaseDiagram::projectAirportRuwaysWithCenter(FGAirportRef apt, const SGGeod& c)
763 {
764     QVector<QLineF> r;
765
766     const FGRunwayList& runways(apt->getRunwaysWithoutReciprocals());
767     FGRunwayList::const_iterator it;
768
769     for (it = runways.begin(); it != runways.end(); ++it) {
770         FGRunwayRef rwy = *it;
771         QPointF p1 = project(rwy->geod(), c);
772         QPointF p2 = project(rwy->end(), c);
773         r.append(QLineF(p1, p2));
774     }
775
776     return r;
777 }
778
779 void BaseDiagram::setAircraftType(LauncherAircraftType type)
780 {
781     m_aircraftType = type;
782     update();
783 }
784
785 QVector<QLineF> BaseDiagram::projectAirportRuwaysIntoRect(FGAirportRef apt, const QRectF &bounds)
786 {
787     QVector<QLineF> r = projectAirportRuwaysWithCenter(apt, apt->geod());
788
789     QRectF extent;
790     Q_FOREACH(const QLineF& l, r) {
791         extendRect(extent, l.p1());
792         extendRect(extent, l.p2());
793     }
794
795  // find constraining scale factor
796     double ratioInX = bounds.width() / extent.width();
797     double ratioInY = bounds.height() / extent.height();
798
799     QTransform t;
800     t.translate(bounds.left(), bounds.top());
801     t.scale(std::min(ratioInX, ratioInY),
802             std::min(ratioInX, ratioInY));
803     t.translate(-extent.left(), -extent.top()); // move unscaled to 0,0
804
805     for (int i=0; i<r.size(); ++i) {
806         r[i] = t.map(r[i]);
807     }
808
809     return r;
810 }