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"
28 #include <QMouseEvent>
30 #include <Airports/airport.hxx>
31 #include <Airports/runways.hxx>
32 #include <Airports/parking.hxx>
33 #include <Airports/pavement.hxx>
35 static double distanceToLineSegment(const QVector2D& p, const QVector2D& a,
36 const QVector2D& b, double* outT = NULL)
41 // Squared length, to avoid a sqrt
42 const qreal len2 = ab.lengthSquared();
44 // Line null, the projection can't exist, we return the first point
49 return (p - a).length();
52 // Parametric value of the projection on the line
53 const qreal t = (ac.x() * ab.x() + ac.y() * ab.y()) / len2;
56 // Point is before the first point
60 return (p - a).length();
62 // Point is after the second point
66 return (p - b).length();
72 const QVector2D proj = a + t * ab;
73 return (proj - p).length();
79 AirportDiagram::AirportDiagram(QWidget* pr) :
81 m_approachDistanceNm(-1.0)
85 AirportDiagram::~AirportDiagram()
90 void AirportDiagram::setAirport(FGAirportRef apt)
93 m_projectionCenter = apt ? apt->geod() : SGGeod();
95 m_approachDistanceNm = -1.0;
102 recomputeBounds(true);
106 FGRunwayRef AirportDiagram::selectedRunway() const
108 return m_selectedRunway;
111 void AirportDiagram::setSelectedRunway(FGRunwayRef r)
113 if (r == m_selectedRunway) {
117 m_selectedRunway = r;
121 void AirportDiagram::setApproachExtensionDistance(double distanceNm)
123 m_approachDistanceNm = distanceNm;
124 recomputeBounds(true);
128 void AirportDiagram::addRunway(FGRunwayRef rwy)
130 Q_FOREACH(RunwayData rd, m_runways) {
131 if (rd.runway == rwy->reciprocalRunway()) {
132 return; // only add one end of reciprocal runways
137 r.p1 = project(rwy->geod());
138 r.p2 = project(rwy->end());
139 r.widthM = qRound(rwy->widthM());
143 recomputeBounds(false);
147 void AirportDiagram::doComputeBounds()
149 Q_FOREACH(const RunwayData& r, m_runways) {
154 Q_FOREACH(const TaxiwayData& t, m_taxiways) {
159 Q_FOREACH(const ParkingData& p, m_parking) {
163 if (m_selectedRunway && (m_approachDistanceNm > 0.0)) {
164 double d = SG_NM_TO_METER * m_approachDistanceNm;
165 QPointF pt = project(m_selectedRunway->pointOnCenterline(-d));
170 void AirportDiagram::addParking(FGParkingRef park)
172 ParkingData pd = { project(park->geod()), park };
173 m_parking.push_back(pd);
174 recomputeBounds(false);
179 void AirportDiagram::paintContents(QPainter* p)
181 // fit bounds within our available space, allowing for a margin
182 // const int MARGIN = 32; // pixels
183 // double ratioInX = (width() - MARGIN * 2) / m_bounds.width();
184 // double ratioInY = (height() - MARGIN * 2) / m_bounds.height();
185 // double scale = std::min(ratioInX, ratioInY);
187 QTransform t(transform());
191 QBrush brush(QColor(0x9f, 0x9f, 0x9f));
192 Q_FOREACH(const QPainterPath& path, m_pavements) {
197 Q_FOREACH(const TaxiwayData& t, m_taxiways) {
198 QPen pen(QColor(0x9f, 0x9f, 0x9f));
199 pen.setWidth(t.widthM);
201 p->drawLine(t.p1, t.p2);
209 Q_FOREACH(const RunwayData& r, m_runways) {
210 QColor color(Qt::magenta);
211 if ((r.runway == m_selectedRunway) || (r.runway->reciprocalRunway() == m_selectedRunway)) {
218 pen.setWidth(r.widthM);
221 p->drawLine(r.p1, r.p2);
224 QString ident = QString::fromStdString(r.runway->ident());
227 p->rotate(r.runway->headingDeg());
228 // invert scaling factor so we can use screen pixel sizes here
229 p->scale(1.0 / m_scale, 1.0/ m_scale);
231 p->setPen((r.runway == m_selectedRunway) ? Qt::yellow : Qt::magenta);
232 p->drawText(QRect(-100, 5, 200, 200), ident, Qt::AlignHCenter | Qt::AlignTop);
234 FGRunway* recip = r.runway->reciprocalRunway();
235 QString recipIdent = QString::fromStdString(recip->ident());
239 p->rotate(recip->headingDeg());
240 p->scale(1.0 / m_scale, 1.0/ m_scale);
242 p->setPen((r.runway->reciprocalRunway() == m_selectedRunway) ? Qt::yellow : Qt::magenta);
243 p->drawText(QRect(-100, 5, 200, 200), recipIdent, Qt::AlignHCenter | Qt::AlignTop);
246 if (m_selectedRunway && (m_approachDistanceNm > 0.0)) {
248 // draw approach extension point
249 double d = SG_NM_TO_METER * m_approachDistanceNm;
250 QPointF pt = project(m_selectedRunway->pointOnCenterline(-d));
251 QPointF pt2 = project(m_selectedRunway->geod());
252 QPen pen(Qt::yellow, 10);
254 p->drawLine(pt, pt2);
258 void AirportDiagram::mouseReleaseEvent(QMouseEvent* me)
261 return; // ignore panning drag+release ops here
263 QTransform t(transform());
264 double minDist = std::numeric_limits<double>::max();
265 FGRunwayRef bestRunway;
267 Q_FOREACH(const RunwayData& r, m_runways) {
268 QPointF p1(t.map(r.p1)), p2(t.map(r.p2));
270 double d = distanceToLineSegment(QVector2D(me->pos()),
275 bestRunway = r.runway->reciprocalRunway();
277 bestRunway = r.runway;
283 if (minDist < 16.0) {
284 emit clickedRunway(bestRunway);
288 void AirportDiagram::buildTaxiways()
291 for (unsigned int tIndex=0; tIndex < m_airport->numTaxiways(); ++tIndex) {
292 FGTaxiwayRef tx = m_airport->getTaxiwayByIndex(tIndex);
295 td.p1 = project(tx->geod());
296 td.p2 = project(tx->pointOnCenterline(tx->lengthM()));
298 td.widthM = tx->widthM();
299 m_taxiways.append(td);
303 void AirportDiagram::buildPavements()
306 for (unsigned int pIndex=0; pIndex < m_airport->numPavements(); ++pIndex) {
307 FGPavementRef pave = m_airport->getPavementByIndex(pIndex);
308 if (pave->getNodeList().empty()) {
315 QPointF p0 = project(pave->getNodeList().front()->mPos);
317 FGPavement::NodeList::const_iterator it;
318 for (it = pave->getNodeList().begin(); it != pave->getNodeList().end(); ) {
319 const FGPavement::BezierNode *bn = dynamic_cast<const FGPavement::BezierNode *>(it->get());
320 bool close = (*it)->mClose;
322 // increment iterator so we can look at the next point
324 QPointF nextPoint = (it == pave->getNodeList().end()) ? startPoint : project((*it)->mPos);
327 QPointF control = project(bn->mControl);
328 QPointF endPoint = close ? startPoint : nextPoint;
329 pp.quadTo(control, endPoint);
331 // straight line segment
343 startPoint = QPointF();
347 } // of nodes iteration
353 m_pavements.append(pp);
354 } // of pavements iteration