]> git.mxchange.org Git - flightgear.git/blobdiff - src/GUI/AirportDiagram.cxx
Initial work on rendering parking locations.
[flightgear.git] / src / GUI / AirportDiagram.cxx
index c460e2878cee9b50ee62db14a37a9486f1f86059..d268bc2f9d9f3a37d6a8dac39ab0677f94aaa1e4 100644 (file)
 
 #include "AirportDiagram.hxx"
 
+#include <limits>
+
+#include <simgear/sg_inlines.h>
+
 #include <QPainter>
 #include <QDebug>
+#include <QVector2D>
+#include <QMouseEvent>
 
 #include <Airports/airport.hxx>
 #include <Airports/runways.hxx>
 #include <Airports/parking.hxx>
 #include <Airports/pavement.hxx>
 
-/* equatorial and polar earth radius */
-const float rec  = 6378137;          // earth radius, equator (?)
-const float rpol = 6356752.314f;      // earth radius, polar   (?)
+#include <Navaids/navrecord.hxx>
 
-//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)
+{
+    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()
 {
-    setSizePolicy(QSizePolicy::MinimumExpanding,
-                  QSizePolicy::MinimumExpanding);
-    setMinimumSize(100, 100);
+
 }
 
 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();
 }
 
@@ -73,145 +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.setRenderHints(QPainter::Antialiasing);
-    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 hdg = p.parking->getHeading();
+        bool useLeftIcon = false;
+        QRect labelRect(-62, -14, 40, 28);
 
-    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);
+        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<double>::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()
@@ -223,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);
     }