X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FGUI%2FBaseDiagram.cxx;h=fa593f8b7cdcc5353bdfea0d233173b5914ece1a;hb=4befe0e6ea6b5f21119364f1175a0b6c8e97395c;hp=1c938a0a0b51d8199e3354b4d7b20a9f9e9e9af6;hpb=a39df4877264fc25365f609c41ed86eec66cc944;p=flightgear.git diff --git a/src/GUI/BaseDiagram.cxx b/src/GUI/BaseDiagram.cxx index 1c938a0a0..fa593f8b7 100644 --- a/src/GUI/BaseDiagram.cxx +++ b/src/GUI/BaseDiagram.cxx @@ -30,11 +30,16 @@ #include #include #include +#include + +#include "QtLauncher_fwd.hxx" /* equatorial and polar earth radius */ const float rec = 6378137; // earth radius, equator (?) const float rpol = 6356752.314f; // earth radius, polar (?) +const double MINIMUM_SCALE = 0.002; + //Returns Earth radius at a given latitude (Ellipsoide equation with two equal axis) static float earth_radius_lat( float lat ) { @@ -66,6 +71,18 @@ QTransform BaseDiagram::transform() const return t; } +void BaseDiagram::clearIgnoredNavaids() +{ + m_ignored.clear(); +} + +void BaseDiagram::addIgnoredNavaid(FGPositionedRef pos) +{ + if (isNavaidIgnored(pos)) + return; + m_ignored.push_back(pos); +} + void BaseDiagram::extendRect(QRectF &r, const QPointF &p) { if (p.x() < r.left()) { @@ -98,90 +115,397 @@ void BaseDiagram::paintEvent(QPaintEvent* pe) QTransform t(transform()); p.setTransform(t); + paintPolygonData(&p); + paintNavaids(&p); paintContents(&p); } +void BaseDiagram::paintAirplaneIcon(QPainter* painter, const SGGeod& geod, int headingDeg) +{ + QPointF pos = project(geod); + QPixmap pix(":/airplane-icon"); + pos = painter->transform().map(pos); + painter->resetTransform(); + painter->translate(pos.x(), pos.y()); + painter->rotate(headingDeg); + + painter->setRenderHint(QPainter::SmoothPixmapTransform, true); + QRect airplaneIconRect = pix.rect(); + airplaneIconRect.moveCenter(QPoint(0,0)); + painter->drawPixmap(airplaneIconRect, pix); +} + +void BaseDiagram::paintPolygonData(QPainter* painter) +{ + QTransform xf = painter->transform(); + QTransform invT = xf.inverted(); + + SGGeod topLeft = unproject(invT.map(rect().topLeft()), m_projectionCenter); + SGGeod viewCenter = unproject(invT.map(rect().center()), m_projectionCenter); + SGGeod bottomRight = unproject(invT.map(rect().bottomRight()), m_projectionCenter); + + double drawRangeNm = std::max(SGGeodesy::distanceNm(viewCenter, topLeft), + SGGeodesy::distanceNm(viewCenter, bottomRight)); + + flightgear::PolyLineList lines(flightgear::PolyLine::linesNearPos(viewCenter, drawRangeNm, + flightgear::PolyLine::COASTLINE)); + + QPen waterPen(QColor(64, 64, 255), 1); + waterPen.setCosmetic(true); + painter->setPen(waterPen); + flightgear::PolyLineList::const_iterator it; + for (it=lines.begin(); it != lines.end(); ++it) { + paintGeodVec(painter, (*it)->points()); + } + + lines = flightgear::PolyLine::linesNearPos(viewCenter, drawRangeNm, + flightgear::PolyLine::URBAN); + for (it=lines.begin(); it != lines.end(); ++it) { + fillClosedGeodVec(painter, QColor(192, 192, 96), (*it)->points()); + } + + lines = flightgear::PolyLine::linesNearPos(viewCenter, drawRangeNm, + flightgear::PolyLine::RIVER); + + painter->setPen(waterPen); + for (it=lines.begin(); it != lines.end(); ++it) { + paintGeodVec(painter, (*it)->points()); + } + + + lines = flightgear::PolyLine::linesNearPos(viewCenter, drawRangeNm, + flightgear::PolyLine::LAKE); + + for (it=lines.begin(); it != lines.end(); ++it) { + fillClosedGeodVec(painter, QColor(128, 128, 255), + (*it)->points()); + } + + +} + +void BaseDiagram::paintGeodVec(QPainter* painter, const flightgear::SGGeodVec& vec) +{ + QVector projected; + projected.reserve(vec.size()); + flightgear::SGGeodVec::const_iterator it; + for (it=vec.begin(); it != vec.end(); ++it) { + projected.append(project(*it)); + } + + painter->drawPolyline(projected.data(), projected.size()); +} + +void BaseDiagram::fillClosedGeodVec(QPainter* painter, const QColor& color, const flightgear::SGGeodVec& vec) +{ + QVector projected; + projected.reserve(vec.size()); + flightgear::SGGeodVec::const_iterator it; + for (it=vec.begin(); it != vec.end(); ++it) { + projected.append(project(*it)); + } + + painter->setPen(Qt::NoPen); + painter->setBrush(color); + painter->drawPolygon(projected.data(), projected.size()); +} + class MapFilter : public FGPositioned::TypeFilter { public: - MapFilter() + + MapFilter(LauncherAircraftType aircraft) { // addType(FGPositioned::FIX); - addType(FGPositioned::AIRPORT); addType(FGPositioned::NDB); addType(FGPositioned::VOR); + + if (aircraft == Helicopter) { + addType(FGPositioned::HELIPAD); + } + + if (aircraft == Seaplane) { + addType(FGPositioned::SEAPORT); + } else { + addType(FGPositioned::AIRPORT); + } } virtual bool pass(FGPositioned* aPos) const { bool ok = TypeFilter::pass(aPos); + // fix-filtering code disabled since fixed are entirely disabled +#if 0 if (ok && (aPos->type() == FGPositioned::FIX)) { // ignore fixes which end in digits if (aPos->ident().length() > 4 && isdigit(aPos->ident()[3]) && isdigit(aPos->ident()[4])) { return false; } } - +#endif return ok; } }; +void BaseDiagram::splitItems(const FGPositionedList& in, FGPositionedList& navaids, + FGPositionedList& ports) +{ + FGPositionedList::const_iterator it = in.begin(); + for (; it != in.end(); ++it) { + if (FGAirport::isAirportType(it->ptr())) { + ports.push_back(*it); + } else { + navaids.push_back(*it); + } + } +} + +bool orderAirportsByRunwayLength(const FGPositionedRef& a, + const FGPositionedRef& b) +{ + FGAirport* aptA = static_cast(a.ptr()); + FGAirport* aptB = static_cast(b.ptr()); + + return aptA->longestRunway()->lengthFt() > aptB->longestRunway()->lengthFt(); +} void BaseDiagram::paintNavaids(QPainter* painter) { QTransform xf = painter->transform(); painter->setTransform(QTransform()); // reset to identity QTransform invT = xf.inverted(); - SGGeod topLeft = unproject(invT.map(QPointF(0,0)), m_projectionCenter); - double minRunwayLengthFt = (16 / m_scale) * SG_METER_TO_FEET; - // add 10nm fudge factor - double drawRangeNm = SGGeodesy::distanceNm(m_projectionCenter, topLeft) + 10.0; - //qDebug() << "draw range computed as:" << drawRangeNm; - MapFilter f; - FGPositionedList items = FGPositioned::findWithinRange(m_projectionCenter, drawRangeNm, &f); + SGGeod topLeft = unproject(invT.map(rect().topLeft()), m_projectionCenter); + SGGeod viewCenter = unproject(invT.map(rect().center()), m_projectionCenter); + SGGeod bottomRight = unproject(invT.map(rect().bottomRight()), m_projectionCenter); + + double drawRangeNm = std::max(SGGeodesy::distanceNm(viewCenter, topLeft), + SGGeodesy::distanceNm(viewCenter, bottomRight)); + + MapFilter f(m_aircraftType); + FGPositionedList items = FGPositioned::findWithinRange(viewCenter, drawRangeNm, &f); + + FGPositionedList navaids, ports; + splitItems(items, navaids, ports); + + if (ports.size() >= 40) { + FGPositionedList::iterator middle = ports.begin() + 40; + std::partial_sort(ports.begin(), middle, ports.end(), + orderAirportsByRunwayLength); + ports.resize(40); + } - // pass 0 - icons + m_labelRects.clear(); + m_labelRects.reserve(items.size()); FGPositionedList::const_iterator it; - for (it = items.begin(); it != items.end(); ++it) { - bool drawAsIcon = true; + for (it = ports.begin(); it != ports.end(); ++it) { + paintNavaid(painter, xf, *it); + } - if ((*it)->type() == FGPositioned::AIRPORT) { - FGAirport* apt = static_cast(it->ptr()); - if (apt->hasHardRunwayOfLengthFt(minRunwayLengthFt)) { + for (it = navaids.begin(); it != navaids.end(); ++it) { + paintNavaid(painter, xf, *it); + } - drawAsIcon = false; - painter->setTransform(xf); - QVector lines = projectAirportRuwaysWithCenter(apt, m_projectionCenter); - QPen pen(QColor(0x03, 0x83, 0xbf), 8); - pen.setCosmetic(true); - painter->setPen(pen); - painter->drawLines(lines); + // restore transform + painter->setTransform(xf); +} - QPen linePen(Qt::white, 2); - linePen.setCosmetic(true); - painter->setPen(linePen); - painter->drawLines(lines); +QRect boundsOfLines(const QVector& lines) +{ + QRect r; + Q_FOREACH(const QLineF& l, lines) { + r = r.united(QRectF(l.p1(), l.p2()).toRect()); + } - painter->resetTransform(); - } + return r; +} + +void BaseDiagram::paintNavaid(QPainter* painter, const QTransform& t, const FGPositionedRef &pos) +{ + if (isNavaidIgnored(pos)) + return; + + bool drawAsIcon = true; + const double minRunwayLengthFt = (16 / m_scale) * SG_METER_TO_FEET; + const FGPositioned::Type ty(pos->type()); + const bool isNDB = (ty == FGPositioned::NDB); + QRect iconRect; + + if (ty == FGPositioned::AIRPORT) { + FGAirport* apt = static_cast(pos.ptr()); + if (apt->hasHardRunwayOfLengthFt(minRunwayLengthFt)) { + + drawAsIcon = false; + painter->setTransform(t); + QVector lines = projectAirportRuwaysWithCenter(apt, m_projectionCenter); + + QPen pen(QColor(0x03, 0x83, 0xbf), 8); + pen.setCosmetic(true); + painter->setPen(pen); + painter->drawLines(lines); + + QPen linePen(Qt::white, 2); + linePen.setCosmetic(true); + painter->setPen(linePen); + painter->drawLines(lines); + + painter->resetTransform(); + + iconRect = t.mapRect(boundsOfLines(lines)); } + } + + if (drawAsIcon) { + QPixmap pm = iconForPositioned(pos); + QPointF loc = t.map(project(pos->geod())); + iconRect = pm.rect(); + iconRect.moveCenter(loc.toPoint()); + painter->drawPixmap(iconRect, pm); + } + + // compute label text so we can measure it + QString label; + if (FGAirport::isAirportType(pos.ptr())) { + label = QString::fromStdString(pos->name()); + label = fixNavaidName(label); + } else { + label = QString::fromStdString(pos->ident()); + } + + if (ty == FGPositioned::NDB) { + FGNavRecord* nav = static_cast(pos.ptr()); + label.append("\n").append(QString::number(nav->get_freq() / 100)); + } else if (ty == FGPositioned::VOR) { + FGNavRecord* nav = static_cast(pos.ptr()); + label.append("\n").append(QString::number(nav->get_freq() / 100.0, 'f', 1)); + } + + QRect textBounds = painter->boundingRect(QRect(0, 0, 100, 100), + Qt::TextWordWrap, label); + int textFlags; + textBounds = rectAndFlagsForLabel(pos->guid(), iconRect, + textBounds.size(), + textFlags); + + painter->setPen(isNDB ? QColor(0x9b, 0x5d, 0xa2) : QColor(0x03, 0x83, 0xbf)); + painter->drawText(textBounds, textFlags, label); +} + +bool BaseDiagram::isNavaidIgnored(const FGPositionedRef &pos) const +{ + return m_ignored.contains(pos); +} + +bool BaseDiagram::isLabelRectAvailable(const QRect &r) const +{ + Q_FOREACH(const QRect& lr, m_labelRects) { + if (lr.intersects(r)) + return false; + } + + return true; +} + +int BaseDiagram::textFlagsForLabelPosition(LabelPosition pos) +{ +#if 0 + switch (pos) { + case LABEL_RIGHT: return Qt::AlignLeft | Qt::AlignVCenter; + case LABEL_ABOVE: return Qt::AlignHCenter | Qt::A + } +#endif + return 0; +} - if (drawAsIcon) { - QPixmap pm = iconForPositioned(*it); - QPointF loc = xf.map(project((*it)->geod())); - loc -= QPointF(pm.width() >> 1, pm.height() >> 1); - painter->drawPixmap(loc, pm); +QRect BaseDiagram::rectAndFlagsForLabel(PositionedID guid, const QRect& item, + const QSize &bounds, + int& flags) const +{ + m_labelRects.append(item); + int pos = m_labelPositions.value(guid, LABEL_RIGHT); + bool firstAttempt = true; + flags = Qt::TextWordWrap; + + while (pos < LAST_POSITION) { + QRect r = labelPositioned(item, bounds, static_cast(pos)); + if (isLabelRectAvailable(r)) { + m_labelRects.append(r); + m_labelPositions[guid] = static_cast(pos); + flags |= textFlagsForLabelPosition(static_cast(pos)); + return r; + } else if (firstAttempt && (pos != LABEL_RIGHT)) { + pos = LABEL_RIGHT; + } else { + ++pos; } + + firstAttempt = false; } - // restore transform - painter->setTransform(xf); + return QRect(item.x(), item.y(), bounds.width(), bounds.height()); +} + +QRect BaseDiagram::labelPositioned(const QRect& itemRect, + const QSize& bounds, + LabelPosition lp) const +{ + const int SHORT_MARGIN = 4; + const int DIAGONAL_MARGIN = 12; + + QPoint topLeft = itemRect.topLeft(); + + switch (lp) { + // cardinal compass points are short (close in) + case LABEL_RIGHT: + topLeft = QPoint(itemRect.right() + SHORT_MARGIN, + itemRect.center().y() - bounds.height() / 2); + break; + case LABEL_ABOVE: + topLeft = QPoint(itemRect.center().x() - (bounds.width() / 2), + itemRect.top() - (SHORT_MARGIN + bounds.height())); + break; + case LABEL_BELOW: + topLeft = QPoint(itemRect.center().x() - (bounds.width() / 2), + itemRect.bottom() + SHORT_MARGIN); + break; + case LABEL_LEFT: + topLeft = QPoint(itemRect.left() - (SHORT_MARGIN + bounds.width()), + itemRect.center().y() - bounds.height() / 2); + break; + + // first diagonals are further out (to hopefully have a better chance + // of finding clear space + + case LABEL_NE: + topLeft = QPoint(itemRect.right() + DIAGONAL_MARGIN, + itemRect.top() - (DIAGONAL_MARGIN + bounds.height())); + break; + + case LABEL_NW: + topLeft = QPoint(itemRect.left() - (DIAGONAL_MARGIN + bounds.width()), + itemRect.top() - (DIAGONAL_MARGIN + bounds.height())); + break; + + case LABEL_SE: + topLeft = QPoint(itemRect.right() + DIAGONAL_MARGIN, + itemRect.bottom() + DIAGONAL_MARGIN); + break; + + case LABEL_SW: + topLeft = QPoint(itemRect.left() - (DIAGONAL_MARGIN + bounds.width()), + itemRect.bottom() + DIAGONAL_MARGIN); + break; + default: + qWarning() << Q_FUNC_INFO << "Implement me"; + + } + + return QRect(topLeft, bounds); } void BaseDiagram::mousePressEvent(QMouseEvent *me) @@ -225,12 +549,16 @@ void BaseDiagram::wheelEvent(QWheelEvent *we) m_wheelAngleDeltaAccumulator += delta; if (m_wheelAngleDeltaAccumulator > 120) { m_wheelAngleDeltaAccumulator = 0; - m_scale *= 2.0; + + m_scale *= 1.5; + } else if (m_wheelAngleDeltaAccumulator < -120) { m_wheelAngleDeltaAccumulator = 0; - m_scale *= 0.5; + + m_scale *= 0.75; } + SG_CLAMP_RANGE(m_scale, MINIMUM_SCALE, 1.0); update(); } @@ -295,6 +623,7 @@ QPointF BaseDiagram::project(const SGGeod& geod, const SGGeod& center) y = k * ( cos(ref_lat) * sin(lat) - sin(ref_lat) * cos(lat) * cos(lonDiff) ); } + // flip for top-left origin return QPointF(x, -y) * r; } @@ -312,7 +641,9 @@ SGGeod BaseDiagram::unproject(const QPointF& xy, const SGGeod& center) return center; } - double x = xy.x(), y = xy.y(); + // invert y to balance the equivalent in project() + double x = xy.x(), + y = -xy.y(); lat = asin( cos(c) * sin(ref_lat) + (y * sin(c) * cos(ref_lat)) / rho); if (ref_lat == (90 * SG_DEGREES_TO_RADIANS)) // north pole @@ -336,9 +667,11 @@ QPointF BaseDiagram::project(const SGGeod& geod) const return project(geod, m_projectionCenter); } -QPixmap BaseDiagram::iconForPositioned(const FGPositionedRef& pos) +QPixmap BaseDiagram::iconForPositioned(const FGPositionedRef& pos, + const IconOptions& options) { // if airport type, check towered or untowered + bool small = options.testFlag(SmallIcons); bool isTowered = false; if (FGAirport::isAirportType(pos)) { @@ -348,7 +681,8 @@ QPixmap BaseDiagram::iconForPositioned(const FGPositionedRef& pos) switch (pos->type()) { case FGPositioned::VOR: - // check for VORTAC + if (static_cast(pos.ptr())->isVORTAC()) + return QPixmap(":/vortac-icon"); if (static_cast(pos.ptr())->hasDME()) return QPixmap(":/vor-dme-icon"); @@ -356,14 +690,14 @@ QPixmap BaseDiagram::iconForPositioned(const FGPositionedRef& pos) return QPixmap(":/vor-icon"); case FGPositioned::AIRPORT: - return iconForAirport(static_cast(pos.ptr())); + return iconForAirport(static_cast(pos.ptr()), options); case FGPositioned::HELIPORT: return QPixmap(":/heliport-icon"); case FGPositioned::SEAPORT: return QPixmap(isTowered ? ":/seaport-tower-icon" : ":/seaport-icon"); case FGPositioned::NDB: - return QPixmap(":/ndb-icon"); + return QPixmap(small ? ":/ndb-small-icon" : ":/ndb-icon"); case FGPositioned::FIX: return QPixmap(":/waypoint-icon"); @@ -374,13 +708,17 @@ QPixmap BaseDiagram::iconForPositioned(const FGPositionedRef& pos) return QPixmap(); } -QPixmap BaseDiagram::iconForAirport(FGAirport* apt) +QPixmap BaseDiagram::iconForAirport(FGAirport* apt, const IconOptions& options) { + if (apt->isClosed()) { + return QPixmap(":/airport-closed-icon"); + } + if (!apt->hasHardRunwayOfLengthFt(1500)) { return QPixmap(apt->hasTower() ? ":/airport-tower-icon" : ":/airport-icon"); } - if (apt->hasHardRunwayOfLengthFt(8500)) { + if (options.testFlag(LargeAirportPlans) && apt->hasHardRunwayOfLengthFt(8500)) { QPixmap result(32, 32); result.fill(Qt::transparent); { @@ -438,6 +776,12 @@ QVector BaseDiagram::projectAirportRuwaysWithCenter(FGAirportRef apt, co return r; } +void BaseDiagram::setAircraftType(LauncherAircraftType type) +{ + m_aircraftType = type; + update(); +} + QVector BaseDiagram::projectAirportRuwaysIntoRect(FGAirportRef apt, const QRectF &bounds) { QVector r = projectAirportRuwaysWithCenter(apt, apt->geod());