]> git.mxchange.org Git - flightgear.git/blob - src/GUI/AirportDiagram.cxx
Code cleanups, code updates and fix at least on (possible) devide-by-zero
[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     m_helipads.clear();
115
116     if (apt) {
117         buildTaxiways();
118         buildPavements();
119     }
120
121     clearIgnoredNavaids();
122     addIgnoredNavaid(apt);
123
124     recomputeBounds(true);
125     update();
126 }
127
128 FGRunwayRef AirportDiagram::selectedRunway() const
129 {
130     return m_selectedRunway;
131 }
132
133 void AirportDiagram::setSelectedRunway(FGRunwayRef r)
134 {
135     if (r == m_selectedRunway) {
136         return;
137     }
138     
139     m_selectedRunway = r;
140     update();
141 }
142
143 void AirportDiagram::setSelectedHelipad(FGHelipadRef pad)
144 {
145
146 }
147
148 void AirportDiagram::setApproachExtensionDistance(double distanceNm)
149 {
150     m_approachDistanceNm = distanceNm;
151     recomputeBounds(true);
152     update();
153 }
154
155 void AirportDiagram::addRunway(FGRunwayRef rwy)
156 {
157     Q_FOREACH(RunwayData rd, m_runways) {
158         if (rd.runway == rwy->reciprocalRunway()) {
159             return; // only add one end of reciprocal runways
160         }
161     }
162
163     RunwayData r;
164     r.p1 = project(rwy->geod());
165     r.p2 = project(rwy->end());
166     r.widthM = qRound(rwy->widthM());
167     r.runway = rwy;
168     m_runways.append(r);
169
170     recomputeBounds(false);
171     update();
172 }
173
174 void AirportDiagram::doComputeBounds()
175 {
176     Q_FOREACH(const RunwayData& r, m_runways) {
177         extendBounds(r.p1);
178         extendBounds(r.p2);
179     }
180
181     Q_FOREACH(const TaxiwayData& t, m_taxiways) {
182         extendBounds(t.p1);
183         extendBounds(t.p2);
184     }
185
186     Q_FOREACH(const ParkingData& p, m_parking) {
187         extendBounds(p.pt);
188     }
189
190     Q_FOREACH(const HelipadData& p, m_helipads) {
191         extendBounds(p.pt);
192     }
193
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));
197         extendBounds(pt);
198     }
199 }
200
201 void AirportDiagram::addParking(FGParkingRef park)
202 {
203     ParkingData pd = { project(park->geod()), park };
204     m_parking.push_back(pd);
205     recomputeBounds(false);
206     update();
207 }
208
209 void AirportDiagram::addHelipad(FGHelipadRef pad)
210 {
211     HelipadData pd = { project(pad->geod()), pad };
212     m_helipads.push_back(pd);
213     recomputeBounds(false);
214     update();
215 }
216
217
218 void AirportDiagram::paintContents(QPainter* p)
219 {
220     QTransform t = p->transform();
221
222 // pavements
223     QBrush brush(QColor(0x9f, 0x9f, 0x9f));
224     Q_FOREACH(const QPainterPath& path, m_pavements) {
225         p->drawPath(path);
226     }
227
228 // taxiways
229     Q_FOREACH(const TaxiwayData& t, m_taxiways) {
230         QPen pen(QColor(0x9f, 0x9f, 0x9f));
231         pen.setWidth(t.widthM);
232         p->setPen(pen);
233         p->drawLine(t.p1, t.p2);
234     }
235
236     drawHelipads(p);
237     drawParkings(p);
238
239 // runways
240     QFont f;
241     f.setPixelSize(14);
242     p->setFont(f);
243
244     // draw ILS first so underneath all runways
245     QPen pen(QColor(0x5f, 0x5f, 0x5f));
246     pen.setWidth(1);
247     pen.setCosmetic(true);
248     p->setPen(pen);
249
250     Q_FOREACH(const RunwayData& r, m_runways) {
251         drawILS(p, r.runway);
252         drawILS(p, r.runway->reciprocalRunway());
253     }
254
255     bool drawAircraft = false;
256     SGGeod aircraftPos;
257     int headingDeg;
258
259     // now draw the runways for real
260     Q_FOREACH(const RunwayData& r, m_runways) {
261
262         QColor color(Qt::magenta);
263         if ((r.runway == m_selectedRunway) || (r.runway->reciprocalRunway() == m_selectedRunway)) {
264             color = Qt::yellow;
265         }
266         
267         p->setTransform(t);
268
269         QPen pen(color);
270         pen.setWidth(r.widthM);
271         p->setPen(pen);
272         
273         p->drawLine(r.p1, r.p2);
274
275     // draw idents
276         QString ident = QString::fromStdString(r.runway->ident());
277
278         p->translate(r.p1);
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);
282         
283         p->setPen((r.runway == m_selectedRunway) ? Qt::yellow : Qt::magenta);
284         p->drawText(QRect(-100, 5, 200, 200), ident, Qt::AlignHCenter | Qt::AlignTop);
285
286         FGRunway* recip = r.runway->reciprocalRunway();
287         QString recipIdent = QString::fromStdString(recip->ident());
288
289         p->setTransform(t);
290         p->translate(r.p2);
291         p->rotate(recip->headingDeg());
292         p->scale(1.0 / m_scale, 1.0/ m_scale);
293
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);
296     }
297
298     if (m_selectedRunway) {
299         drawAircraft = true;
300         aircraftPos = m_selectedRunway->geod();
301         headingDeg = m_selectedRunway->headingDeg();
302     }
303
304     if (m_selectedRunway && (m_approachDistanceNm > 0.0)) {
305         p->setTransform(t);
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);
312         p->setPen(pen);
313         p->drawLine(pt, pt2);
314
315         aircraftPos = m_selectedRunway->pointOnCenterline(-d);
316     }
317
318     if (drawAircraft) {
319         p->setTransform(t);
320         paintAirplaneIcon(p, aircraftPos, headingDeg);
321     }
322 }
323
324 void AirportDiagram::drawHelipads(QPainter* painter)
325 {
326     QTransform t = painter->transform();
327     QPixmap icon(":/heliport-icon");
328
329     QRect r = icon.rect();
330     r.moveCenter(QPoint(0, 0));
331
332     Q_FOREACH(const HelipadData& p, m_helipads) {
333         painter->setTransform(t);
334         painter->translate(p.pt);
335         painter->drawPixmap(r, icon);
336     }
337 }
338
339 void AirportDiagram::drawParkings(QPainter* painter)
340 {
341     QTransform t = painter->transform();
342
343
344     QFont f = painter->font();
345     f.setPixelSize(16);
346     painter->setFont(f);
347
348     Q_FOREACH(const ParkingData& p, m_parking) {
349         painter->setTransform(t);
350         painter->translate(p.pt);
351
352         double hdg = p.parking->getHeading();
353         bool useLeftIcon = false;
354         QRect labelRect(-62, -14, 40, 28);
355
356         if (hdg > 180.0) {
357             hdg += 90;
358             useLeftIcon = true;
359             labelRect = QRect(22, -14, 40, 28);
360         } else {
361             hdg -= 90;
362         }
363
364         painter->rotate(hdg);
365
366         painter->setBrush(QColor(255, 196, 196)); // kind of pink
367         painter->drawPath(useLeftIcon ? m_parkingIconLeftPath : m_parkingIconPath);
368
369         painter->fillRect(labelRect, Qt::white);
370
371         // draw text
372         painter->setPen(Qt::black);
373         painter->drawText(labelRect,
374                           Qt::AlignVCenter | Qt::AlignHCenter,
375                           QString::fromStdString(p.parking->name()));
376     }
377
378     painter->setTransform(t);
379 }
380
381 void AirportDiagram::drawILS(QPainter* painter, FGRunwayRef runway) const
382 {
383     if (!runway)
384         return;
385
386     FGNavRecord* loc = runway->ILS();
387     if (!loc)
388         return;
389
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);
395
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));
400
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);
406 }
407
408 static double pointDistance(const QPointF& p1, const QPointF& p2)
409 {
410     QPointF d = p2 - p1;
411     return ::sqrt((d.x() * d.x()) + (d.y() * d.y()));
412 }
413
414 void AirportDiagram::mouseReleaseEvent(QMouseEvent* me)
415 {
416     if (m_didPan)
417         return; // ignore panning drag+release ops here
418
419     QTransform t(transform());
420     double minDist = std::numeric_limits<double>::max();
421     FGRunwayRef bestRunway;
422     FGHelipadRef bestHelipad;
423     FGParkingRef bestParking;
424
425     Q_FOREACH(const RunwayData& r, m_runways) {
426         QPointF p1(t.map(r.p1)), p2(t.map(r.p2));
427         double t;
428         double d = distanceToLineSegment(QVector2D(me->pos()),
429                                          QVector2D(p1),
430                                          QVector2D(p2), &t);
431         if (d < minDist) {
432             if (t > 0.5) {
433                 bestRunway = r.runway->reciprocalRunway();
434             } else {
435                 bestRunway = r.runway;
436             }
437             minDist = d;
438         }
439     }
440
441     Q_FOREACH(const ParkingData& parking, m_parking) {
442         double d = pointDistance(me->pos(), t.map(parking.pt));
443         if (d < minDist) {
444             bestParking = parking.parking;
445             bestRunway.clear();
446             minDist = d;
447         }
448     }
449
450     Q_FOREACH(const HelipadData& pad, m_helipads) {
451         double d = pointDistance(me->pos(), t.map(pad.pt));
452         if (d < minDist) {
453             bestHelipad = pad.helipad;
454             bestRunway.clear();
455             bestParking.clear();
456             minDist = d;
457         }
458     }
459
460     
461     if (minDist < 16.0) {
462         if (bestRunway)
463             emit clickedRunway(bestRunway);
464         else if (bestParking)
465             emit clickedParking(bestParking);
466         else if (bestHelipad)
467             emit clickedHelipad(bestHelipad);
468     }
469 }
470
471 void AirportDiagram::buildTaxiways()
472 {
473     m_taxiways.clear();
474     for (unsigned int tIndex=0; tIndex < m_airport->numTaxiways(); ++tIndex) {
475         FGTaxiwayRef tx = m_airport->getTaxiwayByIndex(tIndex);
476
477         TaxiwayData td;
478         td.p1 = project(tx->geod());
479         td.p2 = project(tx->pointOnCenterline(tx->lengthM()));
480
481         td.widthM = tx->widthM();
482         m_taxiways.append(td);
483     }
484 }
485
486 void AirportDiagram::buildPavements()
487 {
488     m_pavements.clear();
489     for (unsigned int pIndex=0; pIndex < m_airport->numPavements(); ++pIndex) {
490         FGPavementRef pave = m_airport->getPavementByIndex(pIndex);
491         if (pave->getNodeList().empty()) {
492             continue;
493         }
494
495         QPainterPath pp;
496         QPointF startPoint;
497         bool closed = true;
498         QPointF p0 = project(pave->getNodeList().front()->mPos);
499
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;
504
505             // increment iterator so we can look at the next point
506             ++it;
507             QPointF nextPoint = (it == pave->getNodeList().end()) ? startPoint : project((*it)->mPos);
508
509             if (bn) {
510                 QPointF control = project(bn->mControl);
511                 QPointF endPoint = close ? startPoint : nextPoint;
512                 pp.quadTo(control, endPoint);
513             } else {
514                 // straight line segment
515                 if (closed) {
516                     pp.moveTo(p0);
517                     closed = false;
518                     startPoint = p0;
519                 } else
520                     pp.lineTo(p0);
521             }
522
523             if (close) {
524                 closed = true;
525                 pp.closeSubpath();
526                 startPoint = QPointF();
527             }
528
529             p0 = nextPoint;
530         } // of nodes iteration
531
532         if (!closed) {
533             pp.closeSubpath();
534         }
535
536         m_pavements.append(pp);
537     } // of pavements iteration
538 }