#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();
}
}
}
- 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 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()
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);
}