X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FGUI%2FAirportDiagram.cxx;h=d268bc2f9d9f3a37d6a8dac39ab0677f94aaa1e4;hb=3cee5eea735545dddd0c1e5b9551d97cf8f79c2c;hp=68acba52fda23d31b99ca375782f8cb84aa483e6;hpb=78e8f533124ad38c414d10470abcd2149b6d01e8;p=flightgear.git diff --git a/src/GUI/AirportDiagram.cxx b/src/GUI/AirportDiagram.cxx index 68acba52f..d268bc2f9 100644 --- a/src/GUI/AirportDiagram.cxx +++ b/src/GUI/AirportDiagram.cxx @@ -1,47 +1,148 @@ +// AirportDiagram.cxx - part of GUI launcher using Qt5 +// +// Written by James Turner, started December 2014. +// +// Copyright (C) 2014 James Turner +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + #include "AirportDiagram.hxx" +#include + +#include + #include #include +#include +#include #include #include #include #include -/* equatorial and polar earth radius */ -const float rec = 6378137; // earth radius, equator (?) -const float rpol = 6356752.314f; // earth radius, polar (?) +#include -//Returns Earth radius at a given latitude (Ellipsoide equation with two equal axis) -static float earth_radius_lat( float lat ) +static double distanceToLineSegment(const QVector2D& p, const QVector2D& a, + const QVector2D& b, double* outT = NULL) { - double a = cos(lat)/rec; - double b = sin(lat)/rpol; - return 1.0f / sqrt( a * a + b * b ); + QVector2D ab(b - a); + QVector2D ac(p - a); + + // Squared length, to avoid a sqrt + const qreal len2 = ab.lengthSquared(); + + // Line null, the projection can't exist, we return the first point + if (qIsNull(len2)) { + if (outT) { + *outT = 0.0; + } + return (p - a).length(); + } + + // Parametric value of the projection on the line + const qreal t = (ac.x() * ab.x() + ac.y() * ab.y()) / len2; + + if (t < 0.0) { + // Point is before the first point + if (outT) { + *outT = 0.0; + } + return (p - a).length(); + } else if (t > 1.0) { + // Point is after the second point + if (outT) { + *outT = 1.0; + } + return (p - b).length(); + } else { + if (outT) { + *outT = t; + } + + const QVector2D proj = a + t * ab; + return (proj - p).length(); + } + + return 0.0; } - AirportDiagram::AirportDiagram(QWidget* pr) : -QWidget(pr) + BaseDiagram(pr), + m_approachDistanceNm(-1.0) { - setSizePolicy(QSizePolicy::MinimumExpanding, - QSizePolicy::MinimumExpanding); - setMinimumSize(100, 100); + m_parkingIconPath.moveTo(0,0); + m_parkingIconPath.lineTo(-16, -16); + m_parkingIconPath.lineTo(-64, -16); + m_parkingIconPath.lineTo(-64, 16); + m_parkingIconPath.lineTo(-16, 16); + m_parkingIconPath.lineTo(0, 0); + + m_parkingIconLeftPath.moveTo(0,0); + m_parkingIconLeftPath.lineTo(16, -16); + m_parkingIconLeftPath.lineTo(64, -16); + m_parkingIconLeftPath.lineTo(64, 16); + m_parkingIconLeftPath.lineTo(16, 16); + m_parkingIconLeftPath.lineTo(0, 0); +} + +AirportDiagram::~AirportDiagram() +{ + } void AirportDiagram::setAirport(FGAirportRef apt) { m_airport = apt; m_projectionCenter = apt ? apt->geod() : SGGeod(); - m_scale = 1.0; - m_bounds = QRectF(); // clear m_runways.clear(); + m_approachDistanceNm = -1.0; + m_parking.clear(); if (apt) { buildTaxiways(); buildPavements(); } + + clearIgnoredNavaids(); + addIgnoredNavaid(apt); + + recomputeBounds(true); + update(); +} + +FGRunwayRef AirportDiagram::selectedRunway() const +{ + return m_selectedRunway; +} + +void AirportDiagram::setSelectedRunway(FGRunwayRef r) +{ + if (r == m_selectedRunway) { + return; + } + m_selectedRunway = r; + update(); +} + +void AirportDiagram::setApproachExtensionDistance(double distanceNm) +{ + m_approachDistanceNm = distanceNm; + recomputeBounds(true); update(); } @@ -53,144 +154,253 @@ void AirportDiagram::addRunway(FGRunwayRef rwy) } } - QPointF p1 = project(rwy->geod()), - p2 = project(rwy->end()); - extendBounds(p1); - extendBounds(p2); - RunwayData r; - r.p1 = p1; - r.p2 = p2; + r.p1 = project(rwy->geod()); + r.p2 = project(rwy->end()); r.widthM = qRound(rwy->widthM()); r.runway = rwy; m_runways.append(r); + + recomputeBounds(false); update(); } -void AirportDiagram::addParking(FGParking* park) +void AirportDiagram::doComputeBounds() +{ + Q_FOREACH(const RunwayData& r, m_runways) { + extendBounds(r.p1); + extendBounds(r.p2); + } + + Q_FOREACH(const TaxiwayData& t, m_taxiways) { + extendBounds(t.p1); + extendBounds(t.p2); + } + + Q_FOREACH(const ParkingData& p, m_parking) { + extendBounds(p.pt); + } + + if (m_selectedRunway && (m_approachDistanceNm > 0.0)) { + double d = SG_NM_TO_METER * m_approachDistanceNm; + QPointF pt = project(m_selectedRunway->pointOnCenterline(-d)); + extendBounds(pt); + } +} + +void AirportDiagram::addParking(FGParkingRef park) { - QPointF p = project(park->geod()); - extendBounds(p); + ParkingData pd = { project(park->geod()), park }; + m_parking.push_back(pd); + recomputeBounds(false); update(); } -void AirportDiagram::paintEvent(QPaintEvent* pe) + +void AirportDiagram::paintContents(QPainter* p) { - QPainter p(this); - p.fillRect(rect(), QColor(0x3f, 0x3f, 0x3f)); - - // fit bounds within our available space, allowing for a margin - const int MARGIN = 32; // pixels - double ratioInX = (width() - MARGIN * 2) / m_bounds.width(); - double ratioInY = (height() - MARGIN * 2) / m_bounds.height(); - double scale = std::min(ratioInX, ratioInY); - - QTransform t; - t.translate(width() / 2, height() / 2); // center projection origin in the widget - t.scale(scale, scale); - // center the bounding box (may not be at the origin) - t.translate(-m_bounds.center().x(), -m_bounds.center().y()); - p.setTransform(t); + QTransform t = p->transform(); // pavements QBrush brush(QColor(0x9f, 0x9f, 0x9f)); Q_FOREACH(const QPainterPath& path, m_pavements) { - p.drawPath(path); + p->drawPath(path); } // taxiways Q_FOREACH(const TaxiwayData& t, m_taxiways) { QPen pen(QColor(0x9f, 0x9f, 0x9f)); pen.setWidth(t.widthM); - p.setPen(pen); - p.drawLine(t.p1, t.p2); + p->setPen(pen); + p->drawLine(t.p1, t.p2); } + + drawParkings(p); + // runways - QPen pen(Qt::magenta); QFont f; f.setPixelSize(14); - p.setFont(f); + p->setFont(f); + + // draw ILS first so underneath all runways + QPen pen(QColor(0x5f, 0x5f, 0x5f)); + pen.setWidth(1); + pen.setCosmetic(true); + p->setPen(pen); + + Q_FOREACH(const RunwayData& r, m_runways) { + drawILS(p, r.runway); + drawILS(p, r.runway->reciprocalRunway()); + } + + bool drawAircraft = false; + SGGeod aircraftPos; + int headingDeg; + // now draw the runways for real Q_FOREACH(const RunwayData& r, m_runways) { - p.setTransform(t); + QColor color(Qt::magenta); + if ((r.runway == m_selectedRunway) || (r.runway->reciprocalRunway() == m_selectedRunway)) { + color = Qt::yellow; + } + + p->setTransform(t); + + QPen pen(color); pen.setWidth(r.widthM); - p.setPen(pen); - p.drawLine(r.p1, r.p2); + p->setPen(pen); + + p->drawLine(r.p1, r.p2); // draw idents QString ident = QString::fromStdString(r.runway->ident()); - p.translate(r.p1); - p.rotate(r.runway->headingDeg()); + p->translate(r.p1); + p->rotate(r.runway->headingDeg()); // invert scaling factor so we can use screen pixel sizes here - p.scale(1.0 / scale, 1.0/ scale); - - p.drawText(QRect(-100, 5, 200, 200), ident, Qt::AlignHCenter | Qt::AlignTop); + p->scale(1.0 / m_scale, 1.0/ m_scale); + + p->setPen((r.runway == m_selectedRunway) ? Qt::yellow : Qt::magenta); + p->drawText(QRect(-100, 5, 200, 200), ident, Qt::AlignHCenter | Qt::AlignTop); FGRunway* recip = r.runway->reciprocalRunway(); QString recipIdent = QString::fromStdString(recip->ident()); - p.setTransform(t); - p.translate(r.p2); - p.rotate(recip->headingDeg()); - p.scale(1.0 / scale, 1.0/ scale); + p->setTransform(t); + p->translate(r.p2); + p->rotate(recip->headingDeg()); + p->scale(1.0 / m_scale, 1.0/ m_scale); - p.drawText(QRect(-100, 5, 200, 200), recipIdent, Qt::AlignHCenter | Qt::AlignTop); + p->setPen((r.runway->reciprocalRunway() == m_selectedRunway) ? Qt::yellow : Qt::magenta); + p->drawText(QRect(-100, 5, 200, 200), recipIdent, Qt::AlignHCenter | Qt::AlignTop); } -} + if (m_selectedRunway) { + drawAircraft = true; + aircraftPos = m_selectedRunway->geod(); + headingDeg = m_selectedRunway->headingDeg(); + } -void AirportDiagram::extendBounds(const QPointF& p) -{ - if (p.x() < m_bounds.left()) { - m_bounds.setLeft(p.x()); - } else if (p.x() > m_bounds.right()) { - m_bounds.setRight(p.x()); + if (m_selectedRunway && (m_approachDistanceNm > 0.0)) { + p->setTransform(t); + // draw approach extension point + double d = SG_NM_TO_METER * m_approachDistanceNm; + QPointF pt = project(m_selectedRunway->pointOnCenterline(-d)); + QPointF pt2 = project(m_selectedRunway->geod()); + QPen pen(Qt::yellow); + pen.setWidth(2.0 / m_scale); + p->setPen(pen); + p->drawLine(pt, pt2); + + aircraftPos = m_selectedRunway->pointOnCenterline(-d); } - if (p.y() < m_bounds.top()) { - m_bounds.setTop(p.y()); - } else if (p.y() > m_bounds.bottom()) { - m_bounds.setBottom(p.y()); + if (drawAircraft) { + p->setTransform(t); + paintAirplaneIcon(p, aircraftPos, headingDeg); } } -QPointF AirportDiagram::project(const SGGeod& geod) const + +void AirportDiagram::drawParkings(QPainter* painter) { - double r = earth_radius_lat(geod.getLatitudeRad()); - double ref_lat = m_projectionCenter.getLatitudeRad(), - ref_lon = m_projectionCenter.getLongitudeRad(), - lat = geod.getLatitudeRad(), - lon = geod.getLongitudeRad(), - lonDiff = lon - ref_lon; - - double c = acos( sin(ref_lat) * sin(lat) + cos(ref_lat) * cos(lat) * cos(lonDiff) ); - if (c == 0.0) { - // angular distance from center is 0 - return QPointF(0.0, 0.0); - } + QTransform t = painter->transform(); + + + QFont f = painter->font(); + f.setPixelSize(16); + painter->setFont(f); + + Q_FOREACH(const ParkingData& p, m_parking) { + painter->setTransform(t); + painter->translate(p.pt); - double k = c / sin(c); - double x, y; - if (ref_lat == (90 * SG_DEGREES_TO_RADIANS)) - { - x = (SGD_PI / 2 - lat) * sin(lonDiff); - y = -(SGD_PI / 2 - lat) * cos(lonDiff); + double hdg = p.parking->getHeading(); + bool useLeftIcon = false; + QRect labelRect(-62, -14, 40, 28); + + if (hdg > 180.0) { + hdg += 90; + useLeftIcon = true; + labelRect = QRect(22, -14, 40, 28); + } else { + hdg -= 90; + } + + painter->rotate(hdg); + + painter->setBrush(QColor(255, 196, 196)); // kind of pink + painter->drawPath(useLeftIcon ? m_parkingIconLeftPath : m_parkingIconPath); + + painter->fillRect(labelRect, Qt::white); + + // draw text + painter->setPen(Qt::black); + painter->drawText(labelRect, + Qt::AlignVCenter | Qt::AlignHCenter, + QString::fromStdString(p.parking->name())); } - else if (ref_lat == -(90 * SG_DEGREES_TO_RADIANS)) - { - x = (SGD_PI / 2 + lat) * sin(lonDiff); - y = (SGD_PI / 2 + lat) * cos(lonDiff); + + painter->setTransform(t); +} + +void AirportDiagram::drawILS(QPainter* painter, FGRunwayRef runway) const +{ + if (!runway) + return; + + FGNavRecord* loc = runway->ILS(); + if (!loc) + return; + + double halfBeamWidth = loc->localizerWidth() * 0.5; + QPointF threshold = project(runway->threshold()); + double rangeM = loc->get_range() * SG_NM_TO_METER; + double radial = loc->get_multiuse(); + SG_NORMALIZE_RANGE(radial, 0.0, 360.0); + +// compute the three end points at the wide end of the arrow + QPointF endCentre = project(SGGeodesy::direct(loc->geod(), radial, -rangeM)); + QPointF endR = project(SGGeodesy::direct(loc->geod(), radial + halfBeamWidth, -rangeM * 1.1)); + QPointF endL = project(SGGeodesy::direct(loc->geod(), radial - halfBeamWidth, -rangeM * 1.1)); + + painter->drawLine(threshold, endCentre); + painter->drawLine(threshold, endL); + painter->drawLine(threshold, endR); + painter->drawLine(endL, endCentre); + painter->drawLine(endR, endCentre); +} + +void AirportDiagram::mouseReleaseEvent(QMouseEvent* me) +{ + if (m_didPan) + return; // ignore panning drag+release ops here + + QTransform t(transform()); + double minDist = std::numeric_limits::max(); + FGRunwayRef bestRunway; + + Q_FOREACH(const RunwayData& r, m_runways) { + QPointF p1(t.map(r.p1)), p2(t.map(r.p2)); + double t; + double d = distanceToLineSegment(QVector2D(me->pos()), + QVector2D(p1), + QVector2D(p2), &t); + if (d < minDist) { + if (t > 0.5) { + bestRunway = r.runway->reciprocalRunway(); + } else { + bestRunway = r.runway; + } + minDist = d; + } } - else - { - x = k * cos(lat) * sin(lonDiff); - y = k * ( cos(ref_lat) * sin(lat) - sin(ref_lat) * cos(lat) * cos(lonDiff) ); + + if (minDist < 16.0) { + emit clickedRunway(bestRunway); } - - return QPointF(x, -y) * r * m_scale; } void AirportDiagram::buildTaxiways() @@ -202,8 +412,7 @@ void AirportDiagram::buildTaxiways() TaxiwayData td; td.p1 = project(tx->geod()); td.p2 = project(tx->pointOnCenterline(tx->lengthM())); - extendBounds(td.p1); - extendBounds(td.p2); + td.widthM = tx->widthM(); m_taxiways.append(td); }