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