27#include <QPaintDevice>
45const double rec = 6378137;
46const double rpol = 6356752.314;
54 double a = cos(lat)/
rec;
55 double b = sin(lat)/
rpol;
56 return 1.0 / sqrt( a * a + b * b );
60 QQuickPaintedItem(pr),
64 setAcceptedMouseButtons(Qt::LeftButton);
65 setFlag(ItemHasContents);
66 setOpaquePainting(
true);
67 setAntialiasing(
true);
73 t.translate(width() / 2, height() / 2);
90 if (isNavaidIgnored(pos))
92 m_ignored.push_back(pos);
97 if (
p.x() < r.left()) {
99 }
else if (
p.x() > r.right()) {
103 if (
p.y() < r.top()) {
105 }
else if (
p.y() > r.bottom()) {
113 static_cast<int>(width()),
114 static_cast<int>(height()));
120 p->fillRect(
rect(), QColor(0x3f, 0x3f, 0x3f));
124 const int MARGIN = 32;
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);
131 m_baseDeviceTransform =
p->deviceTransform();
133 p->setWorldTransform(m_viewportTransform * m_baseDeviceTransform);
134 m_projectedPositions.clear();
144 QPixmap pix(
":/airplane-icon");
145 pos = m_viewportTransform.map(pos);
147 painter->setWorldTransform(m_baseDeviceTransform);
149 painter->translate(pos.x(), pos.y());
150 painter->rotate(headingDeg);
152 painter->setRenderHint(QPainter::SmoothPixmapTransform,
true);
153 QRect airplaneIconRect = pix.rect();
154 airplaneIconRect.moveCenter(QPoint(0,0));
155 painter->drawPixmap(airplaneIconRect, pix);
162 if (m_carrierPixmap.isNull()) {
165 m_carrierPixmap = iconProvider->requestPixmap(
"aircraft-carrier?#ff00ff", &sz, {});
169 pos = m_viewportTransform.map(pos);
171 painter->setWorldTransform(m_baseDeviceTransform);
173 painter->translate(pos.x(), pos.y());
174 painter->rotate(headingDeg);
176 painter->setRenderHint(QPainter::SmoothPixmapTransform,
true);
177 QRect carrierIconRect = m_carrierPixmap.rect();
178 carrierIconRect.moveCenter(QPoint(0,0));
179 painter->drawPixmap(carrierIconRect, m_carrierPixmap);
184void BaseDiagram::paintPolygonData(QPainter* painter)
186 QTransform invT = m_viewportTransform.inverted();
187 const auto geom =
rect();
192 double drawRangeNm = std::max(SGGeodesy::distanceNm(viewCenter, topLeft),
193 SGGeodesy::distanceNm(viewCenter, bottomRight));
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());
207 for (
auto line : lines) {
208 fillClosedGeodVec(painter, QColor(192, 192, 96), line->points());
214 painter->setPen(waterPen);
215 for (
auto line : lines) {
216 paintGeodVec(painter, line->points());
223 for (
auto line : lines) {
224 fillClosedGeodVec(painter, QColor(128, 128, 255), line->points());
230 QVector<QPointF> projected;
231 projected.reserve(vec.size());
236 painter->drawPolyline(projected.data(), projected.size());
239void BaseDiagram::fillClosedGeodVec(QPainter* painter,
const QColor& color,
const flightgear::SGGeodVec& vec)
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));
248 painter->setPen(Qt::NoPen);
249 painter->setBrush(color);
250 painter->drawPolygon(projected.data(), projected.size());
283 bool ok = TypeFilter::pass(aPos);
285 const auto ty = aPos->
type();
288 if (aPos->
ident().length() > 4 && isdigit(aPos->
ident()[3]) && isdigit(aPos->
ident()[4])) {
294 if (!simgear::strutils::ends_with(aPos->
name(),
"TACAN")) {
310 navaids.push_back(
p);
324void BaseDiagram::paintNavaids(QPainter* painter)
327 QTransform invT = m_viewportTransform.inverted();
333 double drawRangeNm = std::max(SGGeodesy::distanceNm(viewCenter, topLeft),
334 SGGeodesy::distanceNm(viewCenter, bottomRight));
340 splitItems(items, navaids, ports);
342 if (ports.size() >= 40) {
343 FGPositionedList::iterator middle = ports.begin() + 40;
344 std::partial_sort(ports.begin(), middle, ports.end(),
352 m_labelRects.clear();
353 m_labelRects.reserve(items.size());
354 painter->setTransform(m_baseDeviceTransform);
356 for (
auto port : ports) {
357 paintNavaid(painter, port);
360 for (
auto nav : navaids) {
361 paintNavaid(painter, nav);
374 to(t), from(f), airway(a)
381 std::vector<AirwaySegment> segmentsToDraw;
383 for (
auto n : navs) {
384 const auto navGuid = n->guid();
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)
392 return ((segment.to == edgeEndGuid) && (segment.from == navGuid)) ||
393 ((segment.from == edgeEndGuid) && (segment.to == navGuid));
395 if (it == segmentsToDraw.end()) {
396 segmentsToDraw.emplace_back(navGuid, edgeEndGuid, e.first);
401 QVector<QLineF> lines;
402 lines.reserve(segmentsToDraw.size());
404 for (
auto seg : segmentsToDraw) {
408 const auto d = QVector2D(
p2 - p1).normalized();
409 p1 += (d.toPointF() * 100);
410 p2 += (d.toPointF() * -100);
412 lines.append(QLineF(p1,
p2));
415 QPen linePen(Qt::cyan, 1);
416 linePen.setCosmetic(
true);
417 painter->setPen(linePen);
418 painter->drawLines(lines);
429 Q_FOREACH(
const QLineF& l, lines) {
430 r = r.united(QRectF(l.p1(), l.p2()).toRect());
436void BaseDiagram::paintNavaid(QPainter* painter,
const FGPositionedRef &pos)
438 if (isNavaidIgnored(pos))
441 bool drawAsIcon =
true;
442 const double minRunwayLengthFt = (16 /
m_scale) * SG_METER_TO_FEET;
448 FGAirport* apt =
static_cast<FGAirport*
>(pos.ptr());
455 painter->setTransform(m_viewportTransform * m_baseDeviceTransform);
457 QPen pen(QColor(0x03, 0x83, 0xbf), 8);
458 pen.setCosmetic(
true);
459 painter->setPen(pen);
460 painter->drawLines(lines);
462 QPen linePen(Qt::white, 2);
463 linePen.setCosmetic(
true);
464 painter->setPen(linePen);
465 painter->drawLines(lines);
467 iconRect = m_viewportTransform.mapRect(
boundsOfLines(lines));
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);
485 label = QString::fromStdString(pos->name());
488 label = QString::fromStdString(pos->ident());
492 FGNavRecord* nav =
static_cast<FGNavRecord*
>(pos.ptr());
493 label.append(
"\n").append(QString::number(nav->
get_freq() / 100));
495 FGNavRecord* nav =
static_cast<FGNavRecord*
>(pos.ptr());
496 label.append(
"\n").append(QString::number(nav->
get_freq() / 100.0,
'f', 1));
499 QRect textBounds = painter->boundingRect(QRect(0, 0, 100, 100),
500 Qt::TextWordWrap, label);
502 textBounds = rectAndFlagsForLabel(pos->guid(), iconRect,
506 painter->setPen(isNDB ? QColor(0x9b, 0x5d, 0xa2) : QColor(0x03, 0x83, 0xbf));
507 painter->drawText(textBounds, textFlags, label);
512 return m_ignored.contains(pos);
515bool BaseDiagram::isLabelRectAvailable(
const QRect &r)
const
517 Q_FOREACH(
const QRect& lr, m_labelRects) {
518 if (lr.intersects(r))
525int BaseDiagram::textFlagsForLabelPosition(LabelPosition pos)
529 case LABEL_RIGHT:
return Qt::AlignLeft | Qt::AlignVCenter;
530 case LABEL_ABOVE:
return Qt::AlignHCenter | Qt::A
536QRect BaseDiagram::rectAndFlagsForLabel(
PositionedID guid,
const QRect& item,
540 m_labelRects.append(item);
541 int pos = m_labelPositions.value(guid, LABEL_RIGHT);
542 bool firstAttempt =
true;
543 flags = Qt::TextWordWrap;
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));
552 }
else if (firstAttempt && (pos != LABEL_RIGHT)) {
558 firstAttempt =
false;
561 return QRect(item.x(), item.y(), bounds.width(), bounds.height());
564QRect BaseDiagram::labelPositioned(
const QRect& itemRect,
566 LabelPosition lp)
const
568 const int SHORT_MARGIN = 4;
569 const int DIAGONAL_MARGIN = 12;
571 QPoint topLeft = itemRect.topLeft();
576 topLeft = QPoint(itemRect.right() + SHORT_MARGIN,
577 itemRect.center().y() - bounds.height() / 2);
580 topLeft = QPoint(itemRect.center().x() - (bounds.width() / 2),
581 itemRect.top() - (SHORT_MARGIN + bounds.height()));
584 topLeft = QPoint(itemRect.center().x() - (bounds.width() / 2),
585 itemRect.bottom() + SHORT_MARGIN);
588 topLeft = QPoint(itemRect.left() - (SHORT_MARGIN + bounds.width()),
589 itemRect.center().y() - bounds.height() / 2);
596 topLeft = QPoint(itemRect.right() + DIAGONAL_MARGIN,
597 itemRect.top() - (DIAGONAL_MARGIN + bounds.height()));
601 topLeft = QPoint(itemRect.left() - (DIAGONAL_MARGIN + bounds.width()),
602 itemRect.top() - (DIAGONAL_MARGIN + bounds.height()));
606 topLeft = QPoint(itemRect.right() + DIAGONAL_MARGIN,
607 itemRect.bottom() + DIAGONAL_MARGIN);
611 topLeft = QPoint(itemRect.left() - (DIAGONAL_MARGIN + bounds.width()),
612 itemRect.bottom() + DIAGONAL_MARGIN);
615 qWarning() << Q_FUNC_INFO <<
"Implement me";
619 return QRect(topLeft, bounds);
624 if (!hasActiveFocus()) {
625 forceActiveFocus(Qt::MouseFocusReason);
650 return (v == 0) ? 0 : (v < 0) ? -1 : 1;
657 int delta = we->angleDelta().y();
719 if (v.length() > 100000.0f) {
720 qWarning() <<
"Excessively distant point" <<
p << v.length();
735 double ref_lat = center.getLatitudeRad(),
736 ref_lon = center.getLongitudeRad(),
737 lat = geod.getLatitudeRad(),
738 lon = geod.getLongitudeRad(),
739 lonDiff = lon - ref_lon;
741 double c = acos( sin(ref_lat) * sin(lat) + cos(ref_lat) * cos(lat) * cos(lonDiff) );
744 return QPointF(0.0, 0.0);
747 double k = c / sin(c);
749 if (ref_lat == (90 * SG_DEGREES_TO_RADIANS))
751 x = (SGD_PI / 2 - lat) * sin(lonDiff);
752 y = -(SGD_PI / 2 - lat) * cos(lonDiff);
754 else if (ref_lat == -(90 * SG_DEGREES_TO_RADIANS))
756 x = (SGD_PI / 2 + lat) * sin(lonDiff);
757 y = (SGD_PI / 2 + lat) * cos(lonDiff);
761 x = k * cos(lat) * sin(lonDiff);
762 y = k * ( cos(ref_lat) * sin(lat) - sin(ref_lat) * cos(lat) * cos(lonDiff) );
766 return QPointF(x, -y) * r;
774 ref_lat = center.getLatitudeRad(),
775 ref_lon = center.getLongitudeRad(),
776 rho = QVector2D(xy).length(),
786 lat = asin( cos(c) * sin(ref_lat) + (y * sin(c) * cos(ref_lat)) / rho);
788 if (ref_lat == (90 * SG_DEGREES_TO_RADIANS))
790 lon = ref_lon + atan(-x/y);
792 else if (ref_lat == -(90 * SG_DEGREES_TO_RADIANS))
794 lon = ref_lon + atan(x/y);
798 lon = ref_lon + atan(x* sin(c) / (rho * cos(ref_lat) * cos(c) - y * sin(ref_lat) * sin(c)));
801 return SGGeod::fromRad(lon, lat);
815 bool isTowered =
false;
821 switch (pos->type()) {
824 return QPixmap(
":/vortac-icon");
827 return QPixmap(
":/vor-dme-icon");
829 return QPixmap(
":/vor-icon");
834 return QPixmap(
":/tacan-icon");
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");
858 return QPixmap(
":/airport-closed-icon");
862 return QPixmap(apt->
hasTower() ?
":/airport-tower-icon" :
":/airport-icon");
866 QPixmap result(96, 96);
867 result.setDevicePixelRatio(2.0);
868 QRect logicalRect = QRect(0, 0, 48, 48);
870 result.fill(Qt::transparent);
873 p.setRenderHint(QPainter::Antialiasing,
true);
874 QRectF b = logicalRect.adjusted(4, 4, -4, -4);
877 p.setPen(QPen(QColor(0x03, 0x83, 0xbf), 8));
880 p.setPen(QPen(Qt::white, 2));
886 QPixmap result(50, 50);
887 result.setDevicePixelRatio(2.0);
888 result.fill(Qt::transparent);
892 p.setRenderHint(QPainter::Antialiasing,
true);
895 p.setBrush(apt->
hasTower() ? QColor(0x03, 0x83, 0xbf) :
896 QColor(0x9b, 0x5d, 0xa2));
897 p.drawEllipse(QPointF(13, 13), 10, 10);
901 p.setPen(QPen(Qt::white, 2));
903 p.rotate(r->headingDeg());
904 p.drawLine(0, -8, 0, 8);
913 for (
auto rwy : apt->getRunwaysWithoutReciprocals()) {
914 QPointF p1 =
project(rwy->geod(), c);
916 r.append(QLineF(p1,
p2));
933 Q_FOREACH(
const QLineF& l, r) {
939 double ratioInX = bounds.width() / extent.width();
940 double ratioInY = bounds.height() / extent.height();
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());
948 for (
int i=0;
i<r.size(); ++
i) {
957 if (m_projectedPositions.contains(pid))
958 return m_projectedPositions.value(pid);
964 QPointF pt =
project(pos->geod());
965 m_projectedPositions[pid] = pt;
971 if (m_projectedPositions.contains(pos->guid()))
972 return m_projectedPositions.value(pos->guid());
974 QPointF pt =
project(pos->geod());
975 m_projectedPositions[pos->guid()] = pt;
QRect boundsOfLines(const QVector< QLineF > &lines)
bool orderAirportsByRunwayLength(const FGPositionedRef &a, const FGPositionedRef &b)
static double earth_radius_lat(double lat)
const double MAXIMUM_SCALE
const double MINIMUM_SCALE
bool options(int, char **)
QString fixNavaidName(QString s)
SGSharedPtr< FGAirport > FGAirportRef
SGSharedPtr< FGRunway > FGRunwayRef
int m_wheelAngleDeltaAccumulator
BaseDiagram(QQuickItem *pr=nullptr)
void clearIgnoredNavaids()
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)
static SGGeod unproject(const QPointF &xy, const SGGeod ¢er)
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
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)?
bool hasHardRunwayOfLengthFt(double aLengthFt) const
Useful predicate for FMS/GPS/NAV displays and similar - check if this airport has a hard-surfaced run...
FGRunwayRef longestRunway() 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...
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.
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.
@ RIVER
state / province / country / department
FlightPlan.hxx - defines a full flight-plan object, including departure, cruise, arrival information ...
std::vector< AirwayEdge > AirwayEdgeVec
std::vector< PolyLineRef > PolyLineList
std::vector< SGGeod > SGGeodVec
SGSharedPtr< FGPositioned > FGPositionedRef
std::vector< FGPositionedRef > FGPositionedList