1 // AirportDiagram.cxx - part of GUI launcher using Qt5
3 // Written by James Turner, started December 2014.
5 // Copyright (C) 2014 James Turner <zakalawe@mac.com>
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 // General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include "AirportDiagram.hxx"
25 #include <simgear/sg_inlines.h>
30 #include <QMouseEvent>
32 #include <Airports/airport.hxx>
33 #include <Airports/runways.hxx>
34 #include <Airports/parking.hxx>
35 #include <Airports/pavement.hxx>
37 #include <Navaids/navrecord.hxx>
39 static double distanceToLineSegment(const QVector2D& p, const QVector2D& a,
40 const QVector2D& b, double* outT = NULL)
45 // Squared length, to avoid a sqrt
46 const qreal len2 = ab.lengthSquared();
48 // Line null, the projection can't exist, we return the first point
53 return (p - a).length();
56 // Parametric value of the projection on the line
57 const qreal t = (ac.x() * ab.x() + ac.y() * ab.y()) / len2;
60 // Point is before the first point
64 return (p - a).length();
66 // Point is after the second point
70 return (p - b).length();
76 const QVector2D proj = a + t * ab;
77 return (proj - p).length();
83 AirportDiagram::AirportDiagram(QWidget* pr) :
85 m_approachDistanceNm(-1.0)
87 m_parkingIconPath.moveTo(0,0);
88 m_parkingIconPath.lineTo(-16, -16);
89 m_parkingIconPath.lineTo(-64, -16);
90 m_parkingIconPath.lineTo(-64, 16);
91 m_parkingIconPath.lineTo(-16, 16);
92 m_parkingIconPath.lineTo(0, 0);
94 m_parkingIconLeftPath.moveTo(0,0);
95 m_parkingIconLeftPath.lineTo(16, -16);
96 m_parkingIconLeftPath.lineTo(64, -16);
97 m_parkingIconLeftPath.lineTo(64, 16);
98 m_parkingIconLeftPath.lineTo(16, 16);
99 m_parkingIconLeftPath.lineTo(0, 0);
102 AirportDiagram::~AirportDiagram()
107 void AirportDiagram::setAirport(FGAirportRef apt)
110 m_projectionCenter = apt ? apt->geod() : SGGeod();
112 m_approachDistanceNm = -1.0;
121 clearIgnoredNavaids();
122 addIgnoredNavaid(apt);
124 recomputeBounds(true);
128 FGRunwayRef AirportDiagram::selectedRunway() const
130 return m_selectedRunway;
133 void AirportDiagram::setSelectedRunway(FGRunwayRef r)
135 if (r == m_selectedRunway) {
139 m_selectedRunway = r;
143 void AirportDiagram::setSelectedHelipad(FGHelipadRef pad)
148 void AirportDiagram::setApproachExtensionDistance(double distanceNm)
150 m_approachDistanceNm = distanceNm;
151 recomputeBounds(true);
155 void AirportDiagram::addRunway(FGRunwayRef rwy)
157 Q_FOREACH(RunwayData rd, m_runways) {
158 if (rd.runway == rwy->reciprocalRunway()) {
159 return; // only add one end of reciprocal runways
164 r.p1 = project(rwy->geod());
165 r.p2 = project(rwy->end());
166 r.widthM = qRound(rwy->widthM());
170 recomputeBounds(false);
174 void AirportDiagram::doComputeBounds()
176 Q_FOREACH(const RunwayData& r, m_runways) {
181 Q_FOREACH(const TaxiwayData& t, m_taxiways) {
186 Q_FOREACH(const ParkingData& p, m_parking) {
190 Q_FOREACH(const HelipadData& p, m_helipads) {
194 if (m_selectedRunway && (m_approachDistanceNm > 0.0)) {
195 double d = SG_NM_TO_METER * m_approachDistanceNm;
196 QPointF pt = project(m_selectedRunway->pointOnCenterline(-d));
201 void AirportDiagram::addParking(FGParkingRef park)
203 ParkingData pd = { project(park->geod()), park };
204 m_parking.push_back(pd);
205 recomputeBounds(false);
209 void AirportDiagram::addHelipad(FGHelipadRef pad)
211 HelipadData pd = { project(pad->geod()), pad };
212 m_helipads.push_back(pd);
213 recomputeBounds(false);
218 void AirportDiagram::paintContents(QPainter* p)
220 QTransform t = p->transform();
223 QBrush brush(QColor(0x9f, 0x9f, 0x9f));
224 Q_FOREACH(const QPainterPath& path, m_pavements) {
229 Q_FOREACH(const TaxiwayData& t, m_taxiways) {
230 QPen pen(QColor(0x9f, 0x9f, 0x9f));
231 pen.setWidth(t.widthM);
233 p->drawLine(t.p1, t.p2);
244 // draw ILS first so underneath all runways
245 QPen pen(QColor(0x5f, 0x5f, 0x5f));
247 pen.setCosmetic(true);
250 Q_FOREACH(const RunwayData& r, m_runways) {
251 drawILS(p, r.runway);
252 drawILS(p, r.runway->reciprocalRunway());
255 bool drawAircraft = false;
259 // now draw the runways for real
260 Q_FOREACH(const RunwayData& r, m_runways) {
262 QColor color(Qt::magenta);
263 if ((r.runway == m_selectedRunway) || (r.runway->reciprocalRunway() == m_selectedRunway)) {
270 pen.setWidth(r.widthM);
273 p->drawLine(r.p1, r.p2);
276 QString ident = QString::fromStdString(r.runway->ident());
279 p->rotate(r.runway->headingDeg());
280 // invert scaling factor so we can use screen pixel sizes here
281 p->scale(1.0 / m_scale, 1.0/ m_scale);
283 p->setPen((r.runway == m_selectedRunway) ? Qt::yellow : Qt::magenta);
284 p->drawText(QRect(-100, 5, 200, 200), ident, Qt::AlignHCenter | Qt::AlignTop);
286 FGRunway* recip = r.runway->reciprocalRunway();
287 QString recipIdent = QString::fromStdString(recip->ident());
291 p->rotate(recip->headingDeg());
292 p->scale(1.0 / m_scale, 1.0/ m_scale);
294 p->setPen((r.runway->reciprocalRunway() == m_selectedRunway) ? Qt::yellow : Qt::magenta);
295 p->drawText(QRect(-100, 5, 200, 200), recipIdent, Qt::AlignHCenter | Qt::AlignTop);
298 if (m_selectedRunway) {
300 aircraftPos = m_selectedRunway->geod();
301 headingDeg = m_selectedRunway->headingDeg();
304 if (m_selectedRunway && (m_approachDistanceNm > 0.0)) {
306 // draw approach extension point
307 double d = SG_NM_TO_METER * m_approachDistanceNm;
308 QPointF pt = project(m_selectedRunway->pointOnCenterline(-d));
309 QPointF pt2 = project(m_selectedRunway->geod());
310 QPen pen(Qt::yellow);
311 pen.setWidth(2.0 / m_scale);
313 p->drawLine(pt, pt2);
315 aircraftPos = m_selectedRunway->pointOnCenterline(-d);
320 paintAirplaneIcon(p, aircraftPos, headingDeg);
324 void AirportDiagram::drawHelipads(QPainter* painter)
326 QTransform t = painter->transform();
327 QPixmap icon(":/heliport-icon");
329 QRect r = icon.rect();
330 r.moveCenter(QPoint(0, 0));
332 Q_FOREACH(const HelipadData& p, m_helipads) {
333 painter->setTransform(t);
334 painter->translate(p.pt);
335 painter->drawPixmap(r, icon);
339 void AirportDiagram::drawParkings(QPainter* painter)
341 QTransform t = painter->transform();
344 QFont f = painter->font();
348 Q_FOREACH(const ParkingData& p, m_parking) {
349 painter->setTransform(t);
350 painter->translate(p.pt);
352 double hdg = p.parking->getHeading();
353 bool useLeftIcon = false;
354 QRect labelRect(-62, -14, 40, 28);
359 labelRect = QRect(22, -14, 40, 28);
364 painter->rotate(hdg);
366 painter->setBrush(QColor(255, 196, 196)); // kind of pink
367 painter->drawPath(useLeftIcon ? m_parkingIconLeftPath : m_parkingIconPath);
369 painter->fillRect(labelRect, Qt::white);
372 painter->setPen(Qt::black);
373 painter->drawText(labelRect,
374 Qt::AlignVCenter | Qt::AlignHCenter,
375 QString::fromStdString(p.parking->name()));
378 painter->setTransform(t);
381 void AirportDiagram::drawILS(QPainter* painter, FGRunwayRef runway) const
386 FGNavRecord* loc = runway->ILS();
390 double halfBeamWidth = loc->localizerWidth() * 0.5;
391 QPointF threshold = project(runway->threshold());
392 double rangeM = loc->get_range() * SG_NM_TO_METER;
393 double radial = loc->get_multiuse();
394 SG_NORMALIZE_RANGE(radial, 0.0, 360.0);
396 // compute the three end points at the wide end of the arrow
397 QPointF endCentre = project(SGGeodesy::direct(loc->geod(), radial, -rangeM));
398 QPointF endR = project(SGGeodesy::direct(loc->geod(), radial + halfBeamWidth, -rangeM * 1.1));
399 QPointF endL = project(SGGeodesy::direct(loc->geod(), radial - halfBeamWidth, -rangeM * 1.1));
401 painter->drawLine(threshold, endCentre);
402 painter->drawLine(threshold, endL);
403 painter->drawLine(threshold, endR);
404 painter->drawLine(endL, endCentre);
405 painter->drawLine(endR, endCentre);
408 static double pointDistance(const QPointF& p1, const QPointF& p2)
411 return ::sqrt((d.x() * d.x()) + (d.y() * d.y()));
414 void AirportDiagram::mouseReleaseEvent(QMouseEvent* me)
417 return; // ignore panning drag+release ops here
419 QTransform t(transform());
420 double minDist = std::numeric_limits<double>::max();
421 FGRunwayRef bestRunway;
422 FGHelipadRef bestHelipad;
423 FGParkingRef bestParking;
425 Q_FOREACH(const RunwayData& r, m_runways) {
426 QPointF p1(t.map(r.p1)), p2(t.map(r.p2));
428 double d = distanceToLineSegment(QVector2D(me->pos()),
433 bestRunway = r.runway->reciprocalRunway();
435 bestRunway = r.runway;
441 Q_FOREACH(const ParkingData& parking, m_parking) {
442 double d = pointDistance(me->pos(), t.map(parking.pt));
444 bestParking = parking.parking;
450 Q_FOREACH(const HelipadData& pad, m_helipads) {
451 double d = pointDistance(me->pos(), t.map(pad.pt));
453 bestHelipad = pad.helipad;
461 if (minDist < 16.0) {
463 emit clickedRunway(bestRunway);
464 else if (bestParking)
465 emit clickedParking(bestParking);
466 else if (bestHelipad)
467 emit clickedHelipad(bestHelipad);
471 void AirportDiagram::buildTaxiways()
474 for (unsigned int tIndex=0; tIndex < m_airport->numTaxiways(); ++tIndex) {
475 FGTaxiwayRef tx = m_airport->getTaxiwayByIndex(tIndex);
478 td.p1 = project(tx->geod());
479 td.p2 = project(tx->pointOnCenterline(tx->lengthM()));
481 td.widthM = tx->widthM();
482 m_taxiways.append(td);
486 void AirportDiagram::buildPavements()
489 for (unsigned int pIndex=0; pIndex < m_airport->numPavements(); ++pIndex) {
490 FGPavementRef pave = m_airport->getPavementByIndex(pIndex);
491 if (pave->getNodeList().empty()) {
498 QPointF p0 = project(pave->getNodeList().front()->mPos);
500 FGPavement::NodeList::const_iterator it;
501 for (it = pave->getNodeList().begin(); it != pave->getNodeList().end(); ) {
502 const FGPavement::BezierNode *bn = dynamic_cast<const FGPavement::BezierNode *>(it->get());
503 bool close = (*it)->mClose;
505 // increment iterator so we can look at the next point
507 QPointF nextPoint = (it == pave->getNodeList().end()) ? startPoint : project((*it)->mPos);
510 QPointF control = project(bn->mControl);
511 QPointF endPoint = close ? startPoint : nextPoint;
512 pp.quadTo(control, endPoint);
514 // straight line segment
526 startPoint = QPointF();
530 } // of nodes iteration
536 m_pavements.append(pp);
537 } // of pavements iteration