]> git.mxchange.org Git - flightgear.git/blob - src/GUI/AirportDiagram.cxx
Initial work on rendering parking locations.
[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     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);
93
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);
100 }
101
102 AirportDiagram::~AirportDiagram()
103 {
104
105 }
106
107 void AirportDiagram::setAirport(FGAirportRef apt)
108 {
109     m_airport = apt;
110     m_projectionCenter = apt ? apt->geod() : SGGeod();
111     m_runways.clear();
112     m_approachDistanceNm = -1.0;
113     m_parking.clear();
114
115     if (apt) {
116         buildTaxiways();
117         buildPavements();
118     }
119
120     clearIgnoredNavaids();
121     addIgnoredNavaid(apt);
122
123     recomputeBounds(true);
124     update();
125 }
126
127 FGRunwayRef AirportDiagram::selectedRunway() const
128 {
129     return m_selectedRunway;
130 }
131
132 void AirportDiagram::setSelectedRunway(FGRunwayRef r)
133 {
134     if (r == m_selectedRunway) {
135         return;
136     }
137     
138     m_selectedRunway = r;
139     update();
140 }
141
142 void AirportDiagram::setApproachExtensionDistance(double distanceNm)
143 {
144     m_approachDistanceNm = distanceNm;
145     recomputeBounds(true);
146     update();
147 }
148
149 void AirportDiagram::addRunway(FGRunwayRef rwy)
150 {
151     Q_FOREACH(RunwayData rd, m_runways) {
152         if (rd.runway == rwy->reciprocalRunway()) {
153             return; // only add one end of reciprocal runways
154         }
155     }
156
157     RunwayData r;
158     r.p1 = project(rwy->geod());
159     r.p2 = project(rwy->end());
160     r.widthM = qRound(rwy->widthM());
161     r.runway = rwy;
162     m_runways.append(r);
163
164     recomputeBounds(false);
165     update();
166 }
167
168 void AirportDiagram::doComputeBounds()
169 {
170     Q_FOREACH(const RunwayData& r, m_runways) {
171         extendBounds(r.p1);
172         extendBounds(r.p2);
173     }
174
175     Q_FOREACH(const TaxiwayData& t, m_taxiways) {
176         extendBounds(t.p1);
177         extendBounds(t.p2);
178     }
179
180     Q_FOREACH(const ParkingData& p, m_parking) {
181         extendBounds(p.pt);
182     }
183
184     if (m_selectedRunway && (m_approachDistanceNm > 0.0)) {
185         double d = SG_NM_TO_METER * m_approachDistanceNm;
186         QPointF pt = project(m_selectedRunway->pointOnCenterline(-d));
187         extendBounds(pt);
188     }
189 }
190
191 void AirportDiagram::addParking(FGParkingRef park)
192 {
193     ParkingData pd = { project(park->geod()), park };
194     m_parking.push_back(pd);
195     recomputeBounds(false);
196     update();
197 }
198
199
200 void AirportDiagram::paintContents(QPainter* p)
201 {
202     QTransform t = p->transform();
203
204 // pavements
205     QBrush brush(QColor(0x9f, 0x9f, 0x9f));
206     Q_FOREACH(const QPainterPath& path, m_pavements) {
207         p->drawPath(path);
208     }
209
210 // taxiways
211     Q_FOREACH(const TaxiwayData& t, m_taxiways) {
212         QPen pen(QColor(0x9f, 0x9f, 0x9f));
213         pen.setWidth(t.widthM);
214         p->setPen(pen);
215         p->drawLine(t.p1, t.p2);
216     }
217
218
219     drawParkings(p);
220
221 // runways
222     QFont f;
223     f.setPixelSize(14);
224     p->setFont(f);
225
226     // draw ILS first so underneath all runways
227     QPen pen(QColor(0x5f, 0x5f, 0x5f));
228     pen.setWidth(1);
229     pen.setCosmetic(true);
230     p->setPen(pen);
231
232     Q_FOREACH(const RunwayData& r, m_runways) {
233         drawILS(p, r.runway);
234         drawILS(p, r.runway->reciprocalRunway());
235     }
236
237     bool drawAircraft = false;
238     SGGeod aircraftPos;
239     int headingDeg;
240
241     // now draw the runways for real
242     Q_FOREACH(const RunwayData& r, m_runways) {
243
244         QColor color(Qt::magenta);
245         if ((r.runway == m_selectedRunway) || (r.runway->reciprocalRunway() == m_selectedRunway)) {
246             color = Qt::yellow;
247         }
248         
249         p->setTransform(t);
250
251         QPen pen(color);
252         pen.setWidth(r.widthM);
253         p->setPen(pen);
254         
255         p->drawLine(r.p1, r.p2);
256
257     // draw idents
258         QString ident = QString::fromStdString(r.runway->ident());
259
260         p->translate(r.p1);
261         p->rotate(r.runway->headingDeg());
262         // invert scaling factor so we can use screen pixel sizes here
263         p->scale(1.0 / m_scale, 1.0/ m_scale);
264         
265         p->setPen((r.runway == m_selectedRunway) ? Qt::yellow : Qt::magenta);
266         p->drawText(QRect(-100, 5, 200, 200), ident, Qt::AlignHCenter | Qt::AlignTop);
267
268         FGRunway* recip = r.runway->reciprocalRunway();
269         QString recipIdent = QString::fromStdString(recip->ident());
270
271         p->setTransform(t);
272         p->translate(r.p2);
273         p->rotate(recip->headingDeg());
274         p->scale(1.0 / m_scale, 1.0/ m_scale);
275
276         p->setPen((r.runway->reciprocalRunway() == m_selectedRunway) ? Qt::yellow : Qt::magenta);
277         p->drawText(QRect(-100, 5, 200, 200), recipIdent, Qt::AlignHCenter | Qt::AlignTop);
278     }
279
280     if (m_selectedRunway) {
281         drawAircraft = true;
282         aircraftPos = m_selectedRunway->geod();
283         headingDeg = m_selectedRunway->headingDeg();
284     }
285
286     if (m_selectedRunway && (m_approachDistanceNm > 0.0)) {
287         p->setTransform(t);
288         // draw approach extension point
289         double d = SG_NM_TO_METER * m_approachDistanceNm;
290         QPointF pt = project(m_selectedRunway->pointOnCenterline(-d));
291         QPointF pt2 = project(m_selectedRunway->geod());
292         QPen pen(Qt::yellow);
293         pen.setWidth(2.0 / m_scale);
294         p->setPen(pen);
295         p->drawLine(pt, pt2);
296
297         aircraftPos = m_selectedRunway->pointOnCenterline(-d);
298     }
299
300     if (drawAircraft) {
301         p->setTransform(t);
302         paintAirplaneIcon(p, aircraftPos, headingDeg);
303     }
304 }
305
306
307 void AirportDiagram::drawParkings(QPainter* painter)
308 {
309     QTransform t = painter->transform();
310
311
312     QFont f = painter->font();
313     f.setPixelSize(16);
314     painter->setFont(f);
315
316     Q_FOREACH(const ParkingData& p, m_parking) {
317         painter->setTransform(t);
318         painter->translate(p.pt);
319
320         double hdg = p.parking->getHeading();
321         bool useLeftIcon = false;
322         QRect labelRect(-62, -14, 40, 28);
323
324         if (hdg > 180.0) {
325             hdg += 90;
326             useLeftIcon = true;
327             labelRect = QRect(22, -14, 40, 28);
328         } else {
329             hdg -= 90;
330         }
331
332         painter->rotate(hdg);
333
334         painter->setBrush(QColor(255, 196, 196)); // kind of pink
335         painter->drawPath(useLeftIcon ? m_parkingIconLeftPath : m_parkingIconPath);
336
337         painter->fillRect(labelRect, Qt::white);
338
339         // draw text
340         painter->setPen(Qt::black);
341         painter->drawText(labelRect,
342                           Qt::AlignVCenter | Qt::AlignHCenter,
343                           QString::fromStdString(p.parking->name()));
344     }
345
346     painter->setTransform(t);
347 }
348
349 void AirportDiagram::drawILS(QPainter* painter, FGRunwayRef runway) const
350 {
351     if (!runway)
352         return;
353
354     FGNavRecord* loc = runway->ILS();
355     if (!loc)
356         return;
357
358     double halfBeamWidth = loc->localizerWidth() * 0.5;
359     QPointF threshold = project(runway->threshold());
360     double rangeM = loc->get_range() * SG_NM_TO_METER;
361     double radial = loc->get_multiuse();
362     SG_NORMALIZE_RANGE(radial, 0.0, 360.0);
363
364 // compute the three end points at the wide end of the arrow
365     QPointF endCentre = project(SGGeodesy::direct(loc->geod(), radial, -rangeM));
366     QPointF endR = project(SGGeodesy::direct(loc->geod(), radial + halfBeamWidth, -rangeM * 1.1));
367     QPointF endL = project(SGGeodesy::direct(loc->geod(), radial - halfBeamWidth, -rangeM * 1.1));
368
369     painter->drawLine(threshold, endCentre);
370     painter->drawLine(threshold, endL);
371     painter->drawLine(threshold, endR);
372     painter->drawLine(endL, endCentre);
373     painter->drawLine(endR, endCentre);
374 }
375
376 void AirportDiagram::mouseReleaseEvent(QMouseEvent* me)
377 {
378     if (m_didPan)
379         return; // ignore panning drag+release ops here
380
381     QTransform t(transform());
382     double minDist = std::numeric_limits<double>::max();
383     FGRunwayRef bestRunway;
384     
385     Q_FOREACH(const RunwayData& r, m_runways) {
386         QPointF p1(t.map(r.p1)), p2(t.map(r.p2));
387         double t;
388         double d = distanceToLineSegment(QVector2D(me->pos()),
389                                          QVector2D(p1),
390                                          QVector2D(p2), &t);
391         if (d < minDist) {
392             if (t > 0.5) {
393                 bestRunway = r.runway->reciprocalRunway();
394             } else {
395                 bestRunway = r.runway;
396             }
397             minDist = d;
398         }
399     }
400     
401     if (minDist < 16.0) {
402         emit clickedRunway(bestRunway);
403     }
404 }
405
406 void AirportDiagram::buildTaxiways()
407 {
408     m_taxiways.clear();
409     for (unsigned int tIndex=0; tIndex < m_airport->numTaxiways(); ++tIndex) {
410         FGTaxiwayRef tx = m_airport->getTaxiwayByIndex(tIndex);
411
412         TaxiwayData td;
413         td.p1 = project(tx->geod());
414         td.p2 = project(tx->pointOnCenterline(tx->lengthM()));
415
416         td.widthM = tx->widthM();
417         m_taxiways.append(td);
418     }
419 }
420
421 void AirportDiagram::buildPavements()
422 {
423     m_pavements.clear();
424     for (unsigned int pIndex=0; pIndex < m_airport->numPavements(); ++pIndex) {
425         FGPavementRef pave = m_airport->getPavementByIndex(pIndex);
426         if (pave->getNodeList().empty()) {
427             continue;
428         }
429
430         QPainterPath pp;
431         QPointF startPoint;
432         bool closed = true;
433         QPointF p0 = project(pave->getNodeList().front()->mPos);
434
435         FGPavement::NodeList::const_iterator it;
436         for (it = pave->getNodeList().begin(); it != pave->getNodeList().end(); ) {
437             const FGPavement::BezierNode *bn = dynamic_cast<const FGPavement::BezierNode *>(it->get());
438             bool close = (*it)->mClose;
439
440             // increment iterator so we can look at the next point
441             ++it;
442             QPointF nextPoint = (it == pave->getNodeList().end()) ? startPoint : project((*it)->mPos);
443
444             if (bn) {
445                 QPointF control = project(bn->mControl);
446                 QPointF endPoint = close ? startPoint : nextPoint;
447                 pp.quadTo(control, endPoint);
448             } else {
449                 // straight line segment
450                 if (closed) {
451                     pp.moveTo(p0);
452                     closed = false;
453                     startPoint = p0;
454                 } else
455                     pp.lineTo(p0);
456             }
457
458             if (close) {
459                 closed = true;
460                 pp.closeSubpath();
461                 startPoint = QPointF();
462             }
463
464             p0 = nextPoint;
465         } // of nodes iteration
466
467         if (!closed) {
468             pp.closeSubpath();
469         }
470
471         m_pavements.append(pp);
472     } // of pavements iteration
473 }