]> git.mxchange.org Git - flightgear.git/blob - src/GUI/AirportDiagram.cxx
Draw aircraft on airport diagram
[flightgear.git] / src / GUI / AirportDiagram.cxx
1 // AirportDiagram.cxx - part of GUI launcher using Qt5
2 //
3 // Written by James Turner, started December 2014.
4 //
5 // Copyright (C) 2014 James Turner <zakalawe@mac.com>
6 //
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.
11 //
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.
16 //
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.
20
21 #include "AirportDiagram.hxx"
22
23 #include <limits>
24
25 #include <simgear/sg_inlines.h>
26
27 #include <QPainter>
28 #include <QDebug>
29 #include <QVector2D>
30 #include <QMouseEvent>
31
32 #include <Airports/airport.hxx>
33 #include <Airports/runways.hxx>
34 #include <Airports/parking.hxx>
35 #include <Airports/pavement.hxx>
36
37 #include <Navaids/navrecord.hxx>
38
39 static double distanceToLineSegment(const QVector2D& p, const QVector2D& a,
40                                     const QVector2D& b, double* outT = NULL)
41 {
42     QVector2D ab(b - a);
43     QVector2D ac(p - a);
44     
45     // Squared length, to avoid a sqrt
46     const qreal len2 = ab.lengthSquared();
47     
48     // Line null, the projection can't exist, we return the first point
49     if (qIsNull(len2)) {
50         if (outT) {
51             *outT = 0.0;
52         }
53         return (p - a).length();
54     }
55     
56     // Parametric value of the projection on the line
57     const qreal t = (ac.x() * ab.x() + ac.y() * ab.y()) / len2;
58     
59     if (t < 0.0) {
60         // Point is before the first point
61         if (outT) {
62             *outT = 0.0;
63         }
64         return (p - a).length();
65     } else if (t > 1.0) {
66         // Point is after the second point
67         if (outT) {
68             *outT = 1.0;
69         }
70         return (p - b).length();
71     } else {
72         if (outT) {
73             *outT = t;
74         }
75         
76         const QVector2D proj = a + t * ab;
77         return (proj - p).length();
78     }
79     
80     return 0.0;
81 }
82
83 AirportDiagram::AirportDiagram(QWidget* pr) :
84     BaseDiagram(pr),
85     m_approachDistanceNm(-1.0)
86 {
87 }
88
89 AirportDiagram::~AirportDiagram()
90 {
91
92 }
93
94 void AirportDiagram::setAirport(FGAirportRef apt)
95 {
96     m_airport = apt;
97     m_projectionCenter = apt ? apt->geod() : SGGeod();
98     m_runways.clear();
99     m_approachDistanceNm = -1.0;
100
101     if (apt) {
102         buildTaxiways();
103         buildPavements();
104     }
105
106     clearIgnoredNavaids();
107     addIgnoredNavaid(apt);
108
109     recomputeBounds(true);
110     update();
111 }
112
113 FGRunwayRef AirportDiagram::selectedRunway() const
114 {
115     return m_selectedRunway;
116 }
117
118 void AirportDiagram::setSelectedRunway(FGRunwayRef r)
119 {
120     if (r == m_selectedRunway) {
121         return;
122     }
123     
124     m_selectedRunway = r;
125     update();
126 }
127
128 void AirportDiagram::setApproachExtensionDistance(double distanceNm)
129 {
130     m_approachDistanceNm = distanceNm;
131     recomputeBounds(true);
132     update();
133 }
134
135 void AirportDiagram::addRunway(FGRunwayRef rwy)
136 {
137     Q_FOREACH(RunwayData rd, m_runways) {
138         if (rd.runway == rwy->reciprocalRunway()) {
139             return; // only add one end of reciprocal runways
140         }
141     }
142
143     RunwayData r;
144     r.p1 = project(rwy->geod());
145     r.p2 = project(rwy->end());
146     r.widthM = qRound(rwy->widthM());
147     r.runway = rwy;
148     m_runways.append(r);
149
150     recomputeBounds(false);
151     update();
152 }
153
154 void AirportDiagram::doComputeBounds()
155 {
156     Q_FOREACH(const RunwayData& r, m_runways) {
157         extendBounds(r.p1);
158         extendBounds(r.p2);
159     }
160
161     Q_FOREACH(const TaxiwayData& t, m_taxiways) {
162         extendBounds(t.p1);
163         extendBounds(t.p2);
164     }
165
166     Q_FOREACH(const ParkingData& p, m_parking) {
167         extendBounds(p.pt);
168     }
169
170     if (m_selectedRunway && (m_approachDistanceNm > 0.0)) {
171         double d = SG_NM_TO_METER * m_approachDistanceNm;
172         QPointF pt = project(m_selectedRunway->pointOnCenterline(-d));
173         extendBounds(pt);
174     }
175 }
176
177 void AirportDiagram::addParking(FGParkingRef park)
178 {
179     ParkingData pd = { project(park->geod()), park };
180     m_parking.push_back(pd);
181     recomputeBounds(false);
182     update();
183 }
184
185
186 void AirportDiagram::paintContents(QPainter* p)
187 {
188     QTransform t = p->transform();
189
190 // pavements
191     QBrush brush(QColor(0x9f, 0x9f, 0x9f));
192     Q_FOREACH(const QPainterPath& path, m_pavements) {
193         p->drawPath(path);
194     }
195
196 // taxiways
197     Q_FOREACH(const TaxiwayData& t, m_taxiways) {
198         QPen pen(QColor(0x9f, 0x9f, 0x9f));
199         pen.setWidth(t.widthM);
200         p->setPen(pen);
201         p->drawLine(t.p1, t.p2);
202     }
203
204 // runways
205     QFont f;
206     f.setPixelSize(14);
207     p->setFont(f);
208
209     // draw ILS first so underneath all runways
210     QPen pen(QColor(0x5f, 0x5f, 0x5f));
211     pen.setWidth(1);
212     pen.setCosmetic(true);
213     p->setPen(pen);
214
215     Q_FOREACH(const RunwayData& r, m_runways) {
216         drawILS(p, r.runway);
217         drawILS(p, r.runway->reciprocalRunway());
218     }
219
220     bool drawAircraft = false;
221     SGGeod aircraftPos;
222     int headingDeg;
223
224     // now draw the runways for real
225     Q_FOREACH(const RunwayData& r, m_runways) {
226
227         QColor color(Qt::magenta);
228         if ((r.runway == m_selectedRunway) || (r.runway->reciprocalRunway() == m_selectedRunway)) {
229             color = Qt::yellow;
230         }
231         
232         p->setTransform(t);
233
234         QPen pen(color);
235         pen.setWidth(r.widthM);
236         p->setPen(pen);
237         
238         p->drawLine(r.p1, r.p2);
239
240     // draw idents
241         QString ident = QString::fromStdString(r.runway->ident());
242
243         p->translate(r.p1);
244         p->rotate(r.runway->headingDeg());
245         // invert scaling factor so we can use screen pixel sizes here
246         p->scale(1.0 / m_scale, 1.0/ m_scale);
247         
248         p->setPen((r.runway == m_selectedRunway) ? Qt::yellow : Qt::magenta);
249         p->drawText(QRect(-100, 5, 200, 200), ident, Qt::AlignHCenter | Qt::AlignTop);
250
251         FGRunway* recip = r.runway->reciprocalRunway();
252         QString recipIdent = QString::fromStdString(recip->ident());
253
254         p->setTransform(t);
255         p->translate(r.p2);
256         p->rotate(recip->headingDeg());
257         p->scale(1.0 / m_scale, 1.0/ m_scale);
258
259         p->setPen((r.runway->reciprocalRunway() == m_selectedRunway) ? Qt::yellow : Qt::magenta);
260         p->drawText(QRect(-100, 5, 200, 200), recipIdent, Qt::AlignHCenter | Qt::AlignTop);
261     }
262
263     if (m_selectedRunway) {
264         drawAircraft = true;
265         aircraftPos = m_selectedRunway->geod();
266         headingDeg = m_selectedRunway->headingDeg();
267     }
268
269     if (m_selectedRunway && (m_approachDistanceNm > 0.0)) {
270         p->setTransform(t);
271         // draw approach extension point
272         double d = SG_NM_TO_METER * m_approachDistanceNm;
273         QPointF pt = project(m_selectedRunway->pointOnCenterline(-d));
274         QPointF pt2 = project(m_selectedRunway->geod());
275         QPen pen(Qt::yellow);
276         pen.setWidth(2.0 / m_scale);
277         p->setPen(pen);
278         p->drawLine(pt, pt2);
279
280         aircraftPos = m_selectedRunway->pointOnCenterline(-d);
281     }
282
283     if (drawAircraft) {
284         p->setTransform(t);
285         paintAirplaneIcon(p, aircraftPos, headingDeg);
286     }
287 }
288
289 void AirportDiagram::drawILS(QPainter* painter, FGRunwayRef runway) const
290 {
291     if (!runway)
292         return;
293
294     FGNavRecord* loc = runway->ILS();
295     if (!loc)
296         return;
297
298     double halfBeamWidth = loc->localizerWidth() * 0.5;
299     QPointF threshold = project(runway->threshold());
300     double rangeM = loc->get_range() * SG_NM_TO_METER;
301     double radial = loc->get_multiuse();
302     SG_NORMALIZE_RANGE(radial, 0.0, 360.0);
303
304 // compute the three end points at the wide end of the arrow
305     QPointF endCentre = project(SGGeodesy::direct(loc->geod(), radial, -rangeM));
306     QPointF endR = project(SGGeodesy::direct(loc->geod(), radial + halfBeamWidth, -rangeM * 1.1));
307     QPointF endL = project(SGGeodesy::direct(loc->geod(), radial - halfBeamWidth, -rangeM * 1.1));
308
309     painter->drawLine(threshold, endCentre);
310     painter->drawLine(threshold, endL);
311     painter->drawLine(threshold, endR);
312     painter->drawLine(endL, endCentre);
313     painter->drawLine(endR, endCentre);
314 }
315
316 void AirportDiagram::mouseReleaseEvent(QMouseEvent* me)
317 {
318     if (m_didPan)
319         return; // ignore panning drag+release ops here
320
321     QTransform t(transform());
322     double minDist = std::numeric_limits<double>::max();
323     FGRunwayRef bestRunway;
324     
325     Q_FOREACH(const RunwayData& r, m_runways) {
326         QPointF p1(t.map(r.p1)), p2(t.map(r.p2));
327         double t;
328         double d = distanceToLineSegment(QVector2D(me->pos()),
329                                          QVector2D(p1),
330                                          QVector2D(p2), &t);
331         if (d < minDist) {
332             if (t > 0.5) {
333                 bestRunway = r.runway->reciprocalRunway();
334             } else {
335                 bestRunway = r.runway;
336             }
337             minDist = d;
338         }
339     }
340     
341     if (minDist < 16.0) {
342         emit clickedRunway(bestRunway);
343     }
344 }
345
346 void AirportDiagram::buildTaxiways()
347 {
348     m_taxiways.clear();
349     for (unsigned int tIndex=0; tIndex < m_airport->numTaxiways(); ++tIndex) {
350         FGTaxiwayRef tx = m_airport->getTaxiwayByIndex(tIndex);
351
352         TaxiwayData td;
353         td.p1 = project(tx->geod());
354         td.p2 = project(tx->pointOnCenterline(tx->lengthM()));
355
356         td.widthM = tx->widthM();
357         m_taxiways.append(td);
358     }
359 }
360
361 void AirportDiagram::buildPavements()
362 {
363     m_pavements.clear();
364     for (unsigned int pIndex=0; pIndex < m_airport->numPavements(); ++pIndex) {
365         FGPavementRef pave = m_airport->getPavementByIndex(pIndex);
366         if (pave->getNodeList().empty()) {
367             continue;
368         }
369
370         QPainterPath pp;
371         QPointF startPoint;
372         bool closed = true;
373         QPointF p0 = project(pave->getNodeList().front()->mPos);
374
375         FGPavement::NodeList::const_iterator it;
376         for (it = pave->getNodeList().begin(); it != pave->getNodeList().end(); ) {
377             const FGPavement::BezierNode *bn = dynamic_cast<const FGPavement::BezierNode *>(it->get());
378             bool close = (*it)->mClose;
379
380             // increment iterator so we can look at the next point
381             ++it;
382             QPointF nextPoint = (it == pave->getNodeList().end()) ? startPoint : project((*it)->mPos);
383
384             if (bn) {
385                 QPointF control = project(bn->mControl);
386                 QPointF endPoint = close ? startPoint : nextPoint;
387                 pp.quadTo(control, endPoint);
388             } else {
389                 // straight line segment
390                 if (closed) {
391                     pp.moveTo(p0);
392                     closed = false;
393                     startPoint = p0;
394                 } else
395                     pp.lineTo(p0);
396             }
397
398             if (close) {
399                 closed = true;
400                 pp.closeSubpath();
401                 startPoint = QPointF();
402             }
403
404             p0 = nextPoint;
405         } // of nodes iteration
406
407         if (!closed) {
408             pp.closeSubpath();
409         }
410
411         m_pavements.append(pp);
412     } // of pavements iteration
413 }