FlightGear next
BaseDiagram.cxx
Go to the documentation of this file.
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 <QDebug>
26#include <QMouseEvent>
27#include <QPaintDevice>
28#include <QPainter>
29#include <QQmlEngine>
30#include <QVector2D>
31
32#include <Navaids/navrecord.hxx>
34#include <Airports/airport.hxx>
35#include <Navaids/PolyLine.hxx>
37#include <Navaids/airways.hxx>
38
40#include "QtLauncher_fwd.hxx"
41
42using namespace flightgear;
43
44/* equatorial and polar earth radius */
45const double rec = 6378137; // earth radius, equator (?)
46const double rpol = 6356752.314; // earth radius, polar (?)
47
48const double MINIMUM_SCALE = 0.002;
49const double MAXIMUM_SCALE = 2.0;
50
51//Returns Earth radius at a given latitude (Ellipsoide equation with two equal axis)
52static double earth_radius_lat( double lat )
53{
54 double a = cos(lat)/rec;
55 double b = sin(lat)/rpol;
56 return 1.0 / sqrt( a * a + b * b );
57}
58
59BaseDiagram::BaseDiagram(QQuickItem* pr) :
60 QQuickPaintedItem(pr),
61 m_autoScalePan(true),
63{
64 setAcceptedMouseButtons(Qt::LeftButton);
65 setFlag(ItemHasContents);
66 setOpaquePainting(true);
67 setAntialiasing(true);
68}
69
70QTransform BaseDiagram::transform() const
71{
72 QTransform t;
73 t.translate(width() / 2, height() / 2); // center projection origin in the widget
74 t.scale(m_scale, m_scale);
75
76 // apply any pan offset that exists
77 t.translate(m_panOffset.x(), m_panOffset.y());
78 // center the bounding box (may not be at the origin)
79 t.translate(-m_bounds.center().x(), -m_bounds.center().y());
80 return t;
81}
82
84{
85 m_ignored.clear();
86}
87
89{
90 if (isNavaidIgnored(pos))
91 return;
92 m_ignored.push_back(pos);
93}
94
95void BaseDiagram::extendRect(QRectF &r, const QPointF &p)
96{
97 if (p.x() < r.left()) {
98 r.setLeft(p.x());
99 } else if (p.x() > r.right()) {
100 r.setRight(p.x());
101 }
102
103 if (p.y() < r.top()) {
104 r.setTop(p.y());
105 } else if (p.y() > r.bottom()) {
106 r.setBottom(p.y());
107 }
108}
109
110QRect BaseDiagram::rect() const
111{
112 return QRect(0, 0,
113 static_cast<int>(width()),
114 static_cast<int>(height()));
115}
116
117void BaseDiagram::paint(QPainter* p)
118{
119 //p->setRenderHints(QPainter::Antialiasing);
120 p->fillRect(rect(), QColor(0x3f, 0x3f, 0x3f));
121
122 if (m_autoScalePan) {
123 // fit bounds within our available space, allowing for a margin
124 const int MARGIN = 32; // pixels
125 double ratioInX = (width() - MARGIN * 2) / m_bounds.width();
126 double ratioInY = (height() - MARGIN * 2) / m_bounds.height();
127 m_scale = std::min(ratioInX, ratioInY);
128 SG_CLAMP_RANGE(m_scale, MINIMUM_SCALE, MAXIMUM_SCALE);
129 }
130
131 m_baseDeviceTransform = p->deviceTransform();
132 m_viewportTransform = transform();
133 p->setWorldTransform(m_viewportTransform * m_baseDeviceTransform);
134 m_projectedPositions.clear();
135
136 paintPolygonData(p);
137 paintNavaids(p);
139}
140
141void BaseDiagram::paintAirplaneIcon(QPainter* painter, const SGGeod& geod, int headingDeg)
142{
143 QPointF pos = project(geod);
144 QPixmap pix(":/airplane-icon");
145 pos = m_viewportTransform.map(pos);
146 painter->save();
147 painter->setWorldTransform(m_baseDeviceTransform);
148
149 painter->translate(pos.x(), pos.y());
150 painter->rotate(headingDeg);
151
152 painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
153 QRect airplaneIconRect = pix.rect();
154 airplaneIconRect.moveCenter(QPoint(0,0));
155 painter->drawPixmap(airplaneIconRect, pix);
156
157 painter->restore();
158}
159
160void BaseDiagram::paintCarrierIcon(QPainter* painter, const SGGeod& geod, int headingDeg)
161{
162 if (m_carrierPixmap.isNull()) {
163 auto iconProvider = QmlColoredImageProvider::instance();
164 QSize sz;
165 m_carrierPixmap = iconProvider->requestPixmap("aircraft-carrier?#ff00ff", &sz, {});
166 }
167
168 QPointF pos = project(geod);
169 pos = m_viewportTransform.map(pos);
170 painter->save();
171 painter->setWorldTransform(m_baseDeviceTransform);
172
173 painter->translate(pos.x(), pos.y());
174 painter->rotate(headingDeg);
175
176 painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
177 QRect carrierIconRect = m_carrierPixmap.rect();
178 carrierIconRect.moveCenter(QPoint(0,0));
179 painter->drawPixmap(carrierIconRect, m_carrierPixmap);
180
181 painter->restore();
182}
183
184void BaseDiagram::paintPolygonData(QPainter* painter)
185{
186 QTransform invT = m_viewportTransform.inverted();
187 const auto geom = rect();
188 SGGeod topLeft = unproject(invT.map(geom.topLeft()), m_projectionCenter);
189 SGGeod viewCenter = unproject(invT.map(geom.center()), m_projectionCenter);
190 SGGeod bottomRight = unproject(invT.map(geom.bottomRight()), m_projectionCenter);
191
192 double drawRangeNm = std::max(SGGeodesy::distanceNm(viewCenter, topLeft),
193 SGGeodesy::distanceNm(viewCenter, bottomRight));
194
197
198 QPen waterPen(QColor(64, 64, 255), 1);
199 waterPen.setCosmetic(true);
200 painter->setPen(waterPen);
201 for (auto line : lines) {
202 paintGeodVec(painter, line->points());
203 }
204
205 lines = flightgear::PolyLine::linesNearPos(viewCenter, drawRangeNm,
207 for (auto line : lines) {
208 fillClosedGeodVec(painter, QColor(192, 192, 96), line->points());
209 }
210
211 lines = flightgear::PolyLine::linesNearPos(viewCenter, drawRangeNm,
213
214 painter->setPen(waterPen);
215 for (auto line : lines) {
216 paintGeodVec(painter, line->points());
217 }
218
219
220 lines = flightgear::PolyLine::linesNearPos(viewCenter, drawRangeNm,
222
223 for (auto line : lines) {
224 fillClosedGeodVec(painter, QColor(128, 128, 255), line->points());
225 }
226}
227
228void BaseDiagram::paintGeodVec(QPainter* painter, const flightgear::SGGeodVec& vec)
229{
230 QVector<QPointF> projected;
231 projected.reserve(vec.size());
232 for (auto v: vec) {
233 projected.append(project(v));
234 }
235
236 painter->drawPolyline(projected.data(), projected.size());
237}
238
239void BaseDiagram::fillClosedGeodVec(QPainter* painter, const QColor& color, const flightgear::SGGeodVec& vec)
240{
241 QVector<QPointF> projected;
242 projected.reserve(vec.size());
243 flightgear::SGGeodVec::const_iterator it;
244 for (it=vec.begin(); it != vec.end(); ++it) {
245 projected.append(project(*it));
246 }
247
248 painter->setPen(Qt::NoPen);
249 painter->setBrush(color);
250 painter->drawPolygon(projected.data(), projected.size());
251}
252
254{
255public:
256
258 {
262
263 // this doesn't work, since the cache does not actually contain TACAN entries
264 // addType(FGPositioned::TACAN);
265 // instead we look for DMEs and filter on the name
267
268 if (aircraft == LauncherController::Helicopter) {
270 }
271
272 if (aircraft == LauncherController::Seaplane) {
274 }
275
276 // always add regular aiports, since they may contain helipads,
277 // and seaplanes may have wheels on their floats
279 }
280
281 bool pass(FGPositioned* aPos) const override
282 {
283 bool ok = TypeFilter::pass(aPos);
284
285 const auto ty = aPos->type();
286 if (ok && (ty == FGPositioned::FIX)) {
287 // ignore fixes which end in digits
288 if (aPos->ident().length() > 4 && isdigit(aPos->ident()[3]) && isdigit(aPos->ident()[4])) {
289 return false;
290 }
291 }
292
293 if (ok && (ty == FGPositioned::DME)) {
294 if (!simgear::strutils::ends_with(aPos->name(), "TACAN")) {
295 return false;
296 }
297 }
298
299 return ok;
300 }
301};
302
303void BaseDiagram::splitItems(const FGPositionedList& in, FGPositionedList& navaids,
304 FGPositionedList& ports)
305{
306 for (auto p : in) {
308 ports.push_back(p);
309 } else {
310 navaids.push_back(p);
311 }
312 }
313}
314
316 const FGPositionedRef& b)
317{
318 FGAirport* aptA = static_cast<FGAirport*>(a.ptr());
319 FGAirport* aptB = static_cast<FGAirport*>(b.ptr());
320
321 return aptA->longestRunway()->lengthFt() > aptB->longestRunway()->lengthFt();
322}
323
324void BaseDiagram::paintNavaids(QPainter* painter)
325{
326 painter->save();
327 QTransform invT = m_viewportTransform.inverted();
328
329 SGGeod topLeft = unproject(invT.map(rect().topLeft()), m_projectionCenter);
330 SGGeod viewCenter = unproject(invT.map(rect().center()), m_projectionCenter);
331 SGGeod bottomRight = unproject(invT.map(rect().bottomRight()), m_projectionCenter);
332
333 double drawRangeNm = std::max(SGGeodesy::distanceNm(viewCenter, topLeft),
334 SGGeodesy::distanceNm(viewCenter, bottomRight));
335
336 MapFilter f(m_aircraftType);
337 FGPositionedList items = FGPositioned::findWithinRange(viewCenter, drawRangeNm, &f);
338
339 FGPositionedList navaids, ports;
340 splitItems(items, navaids, ports);
341
342 if (ports.size() >= 40) {
343 FGPositionedList::iterator middle = ports.begin() + 40;
344 std::partial_sort(ports.begin(), middle, ports.end(),
346 ports.resize(40);
347 }
348
349 // paint airways underneath
350 paintAirways(painter, navaids);
351
352 m_labelRects.clear();
353 m_labelRects.reserve(items.size());
354 painter->setTransform(m_baseDeviceTransform);
355
356 for (auto port : ports) {
357 paintNavaid(painter, port);
358 }
359
360 for (auto nav : navaids) {
361 paintNavaid(painter, nav);
362 }
363
364 painter->restore();
365}
366
367void BaseDiagram::paintAirways(QPainter* painter, const FGPositionedList& navs)
368{
369 // FIXME: specify the network
370
371 struct AirwaySegment
372 {
373 AirwaySegment(PositionedID t, PositionedID f, int a) :
374 to(t), from(f), airway(a)
375 {}
376
377 PositionedID to, from;
378 int airway;
379 };
380
381 std::vector<AirwaySegment> segmentsToDraw;
382
383 for (auto n : navs) {
384 const auto navGuid = n->guid();
386
387 for (auto e : edges) {
388 const auto edgeEndGuid = e.second;
389 auto it = std::find_if(segmentsToDraw.begin(), segmentsToDraw.end(),
390 [edgeEndGuid, navGuid](const AirwaySegment& segment)
391 {
392 return ((segment.to == edgeEndGuid) && (segment.from == navGuid)) ||
393 ((segment.from == edgeEndGuid) && (segment.to == navGuid));
394 });
395 if (it == segmentsToDraw.end()) {
396 segmentsToDraw.emplace_back(navGuid, edgeEndGuid, e.first);
397 }
398 }
399 }
400
401 QVector<QLineF> lines;
402 lines.reserve(segmentsToDraw.size());
403
404 for (auto seg : segmentsToDraw) {
405 QPointF p1 = projectedPosition(seg.from);
406 QPointF p2 = projectedPosition(seg.to);
407
408 const auto d = QVector2D(p2 - p1).normalized();
409 p1 += (d.toPointF() * 100);
410 p2 += (d.toPointF() * -100);
411
412 lines.append(QLineF(p1, p2));
413 }
414
415 QPen linePen(Qt::cyan, 1);
416 linePen.setCosmetic(true);
417 painter->setPen(linePen);
418 painter->drawLines(lines);
419
420 // find airway names
421
422 // draw airway name on top
423
424}
425
426QRect boundsOfLines(const QVector<QLineF>& lines)
427{
428 QRect r;
429 Q_FOREACH(const QLineF& l, lines) {
430 r = r.united(QRectF(l.p1(), l.p2()).toRect());
431 }
432
433 return r;
434}
435
436void BaseDiagram::paintNavaid(QPainter* painter, const FGPositionedRef &pos)
437{
438 if (isNavaidIgnored(pos))
439 return;
440
441 bool drawAsIcon = true;
442 const double minRunwayLengthFt = (16 / m_scale) * SG_METER_TO_FEET;
443 const FGPositioned::Type ty(pos->type());
444 const bool isNDB = (ty == FGPositioned::NDB);
445 QRect iconRect;
446
447 if (ty == FGPositioned::AIRPORT) {
448 FGAirport* apt = static_cast<FGAirport*>(pos.ptr());
449 if (apt->hasHardRunwayOfLengthFt(minRunwayLengthFt)) {
450
451 drawAsIcon = false;
452 QVector<QLineF> lines = projectAirportRuwaysWithCenter(apt, m_projectionCenter);
453
454 painter->save();
455 painter->setTransform(m_viewportTransform * m_baseDeviceTransform);
456
457 QPen pen(QColor(0x03, 0x83, 0xbf), 8);
458 pen.setCosmetic(true);
459 painter->setPen(pen);
460 painter->drawLines(lines);
461
462 QPen linePen(Qt::white, 2);
463 linePen.setCosmetic(true);
464 painter->setPen(linePen);
465 painter->drawLines(lines);
466
467 iconRect = m_viewportTransform.mapRect(boundsOfLines(lines));
468
469 painter->restore();
470 }
471 }
472
473 if (drawAsIcon) {
474 QPixmap pm = iconForPositioned(pos);
475 QPointF loc = m_viewportTransform.map(projectedPosition(pos));
476 const auto sz = pm.size() / pm.devicePixelRatio();
477 iconRect = QRect(QPoint(0,0), sz);
478 iconRect.moveCenter(loc.toPoint());
479 painter->drawPixmap(iconRect, pm);
480 }
481
482 // compute label text so we can measure it
483 QString label;
484 if (FGPositioned::isAirportType(pos.ptr())) {
485 label = QString::fromStdString(pos->name());
486 label = fixNavaidName(label);
487 } else {
488 label = QString::fromStdString(pos->ident());
489 }
490
491 if (ty == FGPositioned::NDB) {
492 FGNavRecord* nav = static_cast<FGNavRecord*>(pos.ptr());
493 label.append("\n").append(QString::number(nav->get_freq() / 100));
494 } else if (ty == FGPositioned::VOR) {
495 FGNavRecord* nav = static_cast<FGNavRecord*>(pos.ptr());
496 label.append("\n").append(QString::number(nav->get_freq() / 100.0, 'f', 1));
497 }
498
499 QRect textBounds = painter->boundingRect(QRect(0, 0, 100, 100),
500 Qt::TextWordWrap, label);
501 int textFlags;
502 textBounds = rectAndFlagsForLabel(pos->guid(), iconRect,
503 textBounds.size(),
504 textFlags);
505
506 painter->setPen(isNDB ? QColor(0x9b, 0x5d, 0xa2) : QColor(0x03, 0x83, 0xbf));
507 painter->drawText(textBounds, textFlags, label);
508}
509
510bool BaseDiagram::isNavaidIgnored(const FGPositionedRef &pos) const
511{
512 return m_ignored.contains(pos);
513}
514
515bool BaseDiagram::isLabelRectAvailable(const QRect &r) const
516{
517 Q_FOREACH(const QRect& lr, m_labelRects) {
518 if (lr.intersects(r))
519 return false;
520 }
521
522 return true;
523}
524
525int BaseDiagram::textFlagsForLabelPosition(LabelPosition pos)
526{
527#if 0
528 switch (pos) {
529 case LABEL_RIGHT: return Qt::AlignLeft | Qt::AlignVCenter;
530 case LABEL_ABOVE: return Qt::AlignHCenter | Qt::A
531 }
532#endif
533 return 0;
534}
535
536QRect BaseDiagram::rectAndFlagsForLabel(PositionedID guid, const QRect& item,
537 const QSize &bounds,
538 int& flags) const
539{
540 m_labelRects.append(item);
541 int pos = m_labelPositions.value(guid, LABEL_RIGHT);
542 bool firstAttempt = true;
543 flags = Qt::TextWordWrap;
544
545 while (pos < LAST_POSITION) {
546 QRect r = labelPositioned(item, bounds, static_cast<LabelPosition>(pos));
547 if (isLabelRectAvailable(r)) {
548 m_labelRects.append(r);
549 m_labelPositions[guid] = static_cast<LabelPosition>(pos);
550 flags |= textFlagsForLabelPosition(static_cast<LabelPosition>(pos));
551 return r;
552 } else if (firstAttempt && (pos != LABEL_RIGHT)) {
553 pos = LABEL_RIGHT;
554 } else {
555 ++pos;
556 }
557
558 firstAttempt = false;
559 }
560
561 return QRect(item.x(), item.y(), bounds.width(), bounds.height());
562}
563
564QRect BaseDiagram::labelPositioned(const QRect& itemRect,
565 const QSize& bounds,
566 LabelPosition lp) const
567{
568 const int SHORT_MARGIN = 4;
569 const int DIAGONAL_MARGIN = 12;
570
571 QPoint topLeft = itemRect.topLeft();
572
573 switch (lp) {
574 // cardinal compass points are short (close in)
575 case LABEL_RIGHT:
576 topLeft = QPoint(itemRect.right() + SHORT_MARGIN,
577 itemRect.center().y() - bounds.height() / 2);
578 break;
579 case LABEL_ABOVE:
580 topLeft = QPoint(itemRect.center().x() - (bounds.width() / 2),
581 itemRect.top() - (SHORT_MARGIN + bounds.height()));
582 break;
583 case LABEL_BELOW:
584 topLeft = QPoint(itemRect.center().x() - (bounds.width() / 2),
585 itemRect.bottom() + SHORT_MARGIN);
586 break;
587 case LABEL_LEFT:
588 topLeft = QPoint(itemRect.left() - (SHORT_MARGIN + bounds.width()),
589 itemRect.center().y() - bounds.height() / 2);
590 break;
591
592 // first diagonals are further out (to hopefully have a better chance
593 // of finding clear space
594
595 case LABEL_NE:
596 topLeft = QPoint(itemRect.right() + DIAGONAL_MARGIN,
597 itemRect.top() - (DIAGONAL_MARGIN + bounds.height()));
598 break;
599
600 case LABEL_NW:
601 topLeft = QPoint(itemRect.left() - (DIAGONAL_MARGIN + bounds.width()),
602 itemRect.top() - (DIAGONAL_MARGIN + bounds.height()));
603 break;
604
605 case LABEL_SE:
606 topLeft = QPoint(itemRect.right() + DIAGONAL_MARGIN,
607 itemRect.bottom() + DIAGONAL_MARGIN);
608 break;
609
610 case LABEL_SW:
611 topLeft = QPoint(itemRect.left() - (DIAGONAL_MARGIN + bounds.width()),
612 itemRect.bottom() + DIAGONAL_MARGIN);
613 break;
614 default:
615 qWarning() << Q_FUNC_INFO << "Implement me";
616
617 }
618
619 return QRect(topLeft, bounds);
620}
621
622void BaseDiagram::mousePressEvent(QMouseEvent *me)
623{
624 if (!hasActiveFocus()) {
625 forceActiveFocus(Qt::MouseFocusReason);
626 }
627
628 m_lastMousePos = me->pos();
629 m_didPan = false;
630 me->accept();
631}
632
633void BaseDiagram::mouseMoveEvent(QMouseEvent *me)
634{
635 m_autoScalePan = false;
636
637 QPointF delta = me->pos() - m_lastMousePos;
638 m_lastMousePos = me->pos();
639
640 // offset is stored in metres so we don't have to modify it when
641 // zooming
642 m_panOffset += (delta / m_scale);
643 m_didPan = true;
644
645 update();
646}
647
648int intSign(int v)
649{
650 return (v == 0) ? 0 : (v < 0) ? -1 : 1;
651}
652
653void BaseDiagram::wheelEvent(QWheelEvent *we)
654{
655 m_autoScalePan = false;
656
657 int delta = we->angleDelta().y();
658 if (delta == 0)
659 return;
660
663 }
664
668
669 m_scale *= 1.5;
670
671 } else if (m_wheelAngleDeltaAccumulator < -120) {
673
674 m_scale *= 0.75;
675 }
676
677 SG_CLAMP_RANGE(m_scale, MINIMUM_SCALE, MAXIMUM_SCALE);
678 update();
679}
680
681void BaseDiagram::paintContents(QPainter* painter)
682{
683 Q_UNUSED(painter);
684}
685
686void BaseDiagram::recomputeBounds(bool doResetZoom)
687{
688 m_bounds = QRectF();
690
691 if (doResetZoom) {
692 resetZoom();
693 }
694
695 update();
696}
697
699{
700 m_autoScalePan = true;
701 m_scale = 1.0;
702 m_panOffset = QPointF();
703 update();
704}
705
707{
708 // no-op in the base class
709}
710
711void BaseDiagram::extendBounds(const QPointF& p, double radiusM)
712{
713 // this check added after a bug where apt.dat reports SCSL as
714 // https://airportguide.com/airport/info/AG0003 (British Columbia)
715 // but the groundnet is for
716 // https://en.wikipedia.org/wiki/Salar_de_Atacama_Airport
717 // causing a rather large airport boundary.
718 QVector2D v(p - m_bounds.center());
719 if (v.length() > 100000.0f) {
720 qWarning() << "Excessively distant point" << p << v.length();
721 return;
722 }
723
724 if (radiusM > 0.0) {
725 extendRect(m_bounds, p - QPointF(radiusM, radiusM));
726 extendRect(m_bounds, p + QPointF(radiusM, radiusM));
727 } else {
729 }
730}
731
732QPointF BaseDiagram::project(const SGGeod& geod, const SGGeod& center)
733{
734 double r = earth_radius_lat(geod.getLatitudeRad());
735 double ref_lat = center.getLatitudeRad(),
736 ref_lon = center.getLongitudeRad(),
737 lat = geod.getLatitudeRad(),
738 lon = geod.getLongitudeRad(),
739 lonDiff = lon - ref_lon;
740
741 double c = acos( sin(ref_lat) * sin(lat) + cos(ref_lat) * cos(lat) * cos(lonDiff) );
742 if (c == 0.0) {
743 // angular distance from center is 0
744 return QPointF(0.0, 0.0);
745 }
746
747 double k = c / sin(c);
748 double x, y;
749 if (ref_lat == (90 * SG_DEGREES_TO_RADIANS))
750 {
751 x = (SGD_PI / 2 - lat) * sin(lonDiff);
752 y = -(SGD_PI / 2 - lat) * cos(lonDiff);
753 }
754 else if (ref_lat == -(90 * SG_DEGREES_TO_RADIANS))
755 {
756 x = (SGD_PI / 2 + lat) * sin(lonDiff);
757 y = (SGD_PI / 2 + lat) * cos(lonDiff);
758 }
759 else
760 {
761 x = k * cos(lat) * sin(lonDiff);
762 y = k * ( cos(ref_lat) * sin(lat) - sin(ref_lat) * cos(lat) * cos(lonDiff) );
763 }
764
765 // flip for top-left origin
766 return QPointF(x, -y) * r;
767}
768
769SGGeod BaseDiagram::unproject(const QPointF& xy, const SGGeod& center)
770{
771 double r = earth_radius_lat(center.getLatitudeRad());
772 double lat = 0,
773 lon = 0,
774 ref_lat = center.getLatitudeRad(),
775 ref_lon = center.getLongitudeRad(),
776 rho = QVector2D(xy).length(),
777 c = rho/r;
778
779 if (rho == 0) {
780 return center;
781 }
782
783 // invert y to balance the equivalent in project()
784 double x = xy.x(),
785 y = -xy.y();
786 lat = asin( cos(c) * sin(ref_lat) + (y * sin(c) * cos(ref_lat)) / rho);
787
788 if (ref_lat == (90 * SG_DEGREES_TO_RADIANS)) // north pole
789 {
790 lon = ref_lon + atan(-x/y);
791 }
792 else if (ref_lat == -(90 * SG_DEGREES_TO_RADIANS)) // south pole
793 {
794 lon = ref_lon + atan(x/y);
795 }
796 else
797 {
798 lon = ref_lon + atan(x* sin(c) / (rho * cos(ref_lat) * cos(c) - y * sin(ref_lat) * sin(c)));
799 }
800
801 return SGGeod::fromRad(lon, lat);
802}
803
804QPointF BaseDiagram::project(const SGGeod& geod) const
805{
806 return project(geod, m_projectionCenter);
807}
808
810 const IconOptions& options)
811{
812 // if airport type, check towered or untowered
813 bool small = options.testFlag(SmallIcons);
814
815 bool isTowered = false;
816 if (FGPositioned::isAirportType(pos.ptr())) {
817 FGAirport* apt = static_cast<FGAirport*>(pos.ptr());
818 isTowered = apt->hasTower();
819 }
820
821 switch (pos->type()) {
823 if (static_cast<FGNavRecord*>(pos.ptr())->isVORTAC())
824 return QPixmap(":/vortac-icon");
825
826 if (static_cast<FGNavRecord*>(pos.ptr())->hasDME())
827 return QPixmap(":/vor-dme-icon");
828
829 return QPixmap(":/vor-icon");
830
831 // our filter only passes DMEs which are TACANs, so this is correct,
832 // until we actually record TACANs in the NavCache
834 return QPixmap(":/tacan-icon");
835
837 return iconForAirport(static_cast<FGAirport*>(pos.ptr()), options);
838
840 return QPixmap(":/heliport-icon");
842 return QPixmap(isTowered ? ":/seaport-tower-icon" : ":/seaport-icon");
844 return QPixmap(small ? ":/ndb-small-icon" : ":/ndb-icon");
846 return QPixmap(":/waypoint-icon");
847
848 default:
849 break;
850 }
851
852 return QPixmap();
853}
854
855QPixmap BaseDiagram::iconForAirport(FGAirport* apt, const IconOptions& options)
856{
857 if (apt->isClosed()) {
858 return QPixmap(":/airport-closed-icon");
859 }
860
861 if (!apt->hasHardRunwayOfLengthFt(1500)) {
862 return QPixmap(apt->hasTower() ? ":/airport-tower-icon" : ":/airport-icon");
863 }
864
865 if (options.testFlag(LargeAirportPlans) && apt->hasHardRunwayOfLengthFt(8500)) {
866 QPixmap result(96, 96);
867 result.setDevicePixelRatio(2.0);
868 QRect logicalRect = QRect(0, 0, 48, 48);
869
870 result.fill(Qt::transparent);
871 {
872 QPainter p(&result);
873 p.setRenderHint(QPainter::Antialiasing, true);
874 QRectF b = logicalRect.adjusted(4, 4, -4, -4);
875 QVector<QLineF> lines = projectAirportRuwaysIntoRect(apt, b);
876
877 p.setPen(QPen(QColor(0x03, 0x83, 0xbf), 8));
878 p.drawLines(lines);
879
880 p.setPen(QPen(Qt::white, 2));
881 p.drawLines(lines);
882 }
883 return result;
884 }
885
886 QPixmap result(50, 50);
887 result.setDevicePixelRatio(2.0);
888 result.fill(Qt::transparent);
889
890 {
891 QPainter p(&result);
892 p.setRenderHint(QPainter::Antialiasing, true);
893 p.setPen(Qt::NoPen);
894
895 p.setBrush(apt->hasTower() ? QColor(0x03, 0x83, 0xbf) :
896 QColor(0x9b, 0x5d, 0xa2));
897 p.drawEllipse(QPointF(13, 13), 10, 10);
898
899 FGRunwayRef r = apt->longestRunway();
900
901 p.setPen(QPen(Qt::white, 2));
902 p.translate(13, 13);
903 p.rotate(r->headingDeg());
904 p.drawLine(0, -8, 0, 8);
905 }
906
907 return result;
908}
909
911{
912 QVector<QLineF> r;
913 for (auto rwy : apt->getRunwaysWithoutReciprocals()) {
914 QPointF p1 = project(rwy->geod(), c);
915 QPointF p2 = project(rwy->end(), c);
916 r.append(QLineF(p1, p2));
917 }
918
919 return r;
920}
921
927
928QVector<QLineF> BaseDiagram::projectAirportRuwaysIntoRect(FGAirportRef apt, const QRectF &bounds)
929{
930 QVector<QLineF> r = projectAirportRuwaysWithCenter(apt, apt->geod());
931
932 QRectF extent;
933 Q_FOREACH(const QLineF& l, r) {
934 extendRect(extent, l.p1());
935 extendRect(extent, l.p2());
936 }
937
938 // find constraining scale factor
939 double ratioInX = bounds.width() / extent.width();
940 double ratioInY = bounds.height() / extent.height();
941
942 QTransform t;
943 t.translate(bounds.left(), bounds.top());
944 t.scale(std::min(ratioInX, ratioInY),
945 std::min(ratioInX, ratioInY));
946 t.translate(-extent.left(), -extent.top()); // move unscaled to 0,0
947
948 for (int i=0; i<r.size(); ++i) {
949 r[i] = t.map(r[i]);
950 }
951
952 return r;
953}
954
956{
957 if (m_projectedPositions.contains(pid))
958 return m_projectedPositions.value(pid);
959
961 if (!pos)
962 return {};
963
964 QPointF pt = project(pos->geod());
965 m_projectedPositions[pid] = pt;
966 return pt;
967}
968
970{
971 if (m_projectedPositions.contains(pos->guid()))
972 return m_projectedPositions.value(pos->guid());
973
974 QPointF pt = project(pos->geod());
975 m_projectedPositions[pos->guid()] = pt;
976 return pt;
977}
QRect boundsOfLines(const QVector< QLineF > &lines)
bool orderAirportsByRunwayLength(const FGPositionedRef &a, const FGPositionedRef &b)
int intSign(int v)
static double earth_radius_lat(double lat)
const double MAXIMUM_SCALE
const double rpol
const double MINIMUM_SCALE
const double rec
#define p2(x, y)
#define p(x)
bool options(int, char **)
Definition JSBSim.cpp:568
QString fixNavaidName(QString s)
#define i(x)
SGSharedPtr< FGAirport > FGAirportRef
SGSharedPtr< FGRunway > FGRunwayRef
int m_wheelAngleDeltaAccumulator
QRectF m_bounds
BaseDiagram(QQuickItem *pr=nullptr)
void clearIgnoredNavaids()
QRect rect() const
static void extendRect(QRectF &r, const QPointF &p)
QTransform transform() const
static QVector< QLineF > projectAirportRuwaysIntoRect(FGAirportRef apt, const QRectF &bounds)
void paintAirplaneIcon(QPainter *painter, const SGGeod &geod, int headingDeg)
bool m_autoScalePan
static SGGeod unproject(const QPointF &xy, const SGGeod &center)
QPointF m_lastMousePos
void setAircraftType(LauncherController::AircraftType type)
static QPixmap iconForAirport(FGAirport *apt, const IconOptions &options=NoOptions)
QPointF projectedPosition(PositionedID pid) const
void wheelEvent(QWheelEvent *we) override
SGGeod m_projectionCenter
QPointF m_panOffset
Q_INVOKABLE void resetZoom()
static QVector< QLineF > projectAirportRuwaysWithCenter(FGAirportRef apt, const SGGeod &c)
void extendBounds(const QPointF &p, double radiusM=1.0)
virtual void paintContents(QPainter *)
void addIgnoredNavaid(FGPositionedRef pos)
void paintAirways(QPainter *painter, const FGPositionedList &navs)
void paint(QPainter *p) override
QPointF project(const SGGeod &geod) const
virtual void doComputeBounds()
void paintCarrierIcon(QPainter *painter, const SGGeod &geod, int headingDeg)
void mousePressEvent(QMouseEvent *me) override
static QPixmap iconForPositioned(const FGPositionedRef &pos, const IconOptions &options=NoOptions)
void recomputeBounds(bool resetZoom)
LauncherController::AircraftType m_aircraftType
void mouseMoveEvent(QMouseEvent *me) override
bool isClosed() const
is the airport closed (disused)?
Definition airport.hxx:70
bool hasHardRunwayOfLengthFt(double aLengthFt) const
Useful predicate for FMS/GPS/NAV displays and similar - check if this airport has a hard-surfaced run...
Definition airport.cxx:272
FGRunwayRef longestRunway() const
Definition airport.cxx:285
bool hasTower() const
Definition airport.cxx:819
int get_freq() const
Definition navrecord.hxx:76
bool hasDME() const
Definition navrecord.cxx:96
bool isVORTAC() const
virtual const std::string & name() const
Return the name of this positioned.
static FGPositionedList findWithinRange(const SGGeod &aPos, double aRangeNm, Filter *aFilter)
static bool isAirportType(FGPositioned *pos)
@ DME
important that DME & TACAN are adjacent to keep the TacanFilter efficient - DMEs are proxies for TACA...
Type type() const
const std::string & ident() const
bool pass(FGPositioned *aPos) const override
Over-rideable filter method.
MapFilter(LauncherController::AircraftType aircraft)
static QmlColoredImageProvider * instance()
@ HighLevel
Victor airways.
Definition airways.hxx:48
FGPositionedRef loadById(PositionedID guid)
retrieve an FGPositioned from the cache.
static NavDataCache * instance()
AirwayEdgeVec airwayEdgesFrom(int network, PositionedID pos)
retrive all the destination points reachable from a positioned in an airway
static PolyLineList linesNearPos(const SGGeod &aPos, double aRangeNm, Type aTy)
retrieve all the lines within a range of a search point.
Definition PolyLine.cxx:143
@ RIVER
state / province / country / department
Definition PolyLine.hxx:63
FlightPlan.hxx - defines a full flight-plan object, including departure, cruise, arrival information ...
Definition Addon.cxx:53
std::vector< AirwayEdge > AirwayEdgeVec
std::vector< PolyLineRef > PolyLineList
Definition PolyLine.hxx:41
std::vector< SGGeod > SGGeodVec
Definition PolyLine.hxx:36
SGSharedPtr< FGPositioned > FGPositionedRef
Definition airways.cxx:49
std::vector< FGPositionedRef > FGPositionedList
int64_t PositionedID