]> git.mxchange.org Git - flightgear.git/blob - src/GUI/AirportDiagram.cxx
Work on LocationWidget for Qt launcher
[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 <QPainter>
26 #include <QDebug>
27 #include <QVector2D>
28 #include <QMouseEvent>
29
30 #include <Airports/airport.hxx>
31 #include <Airports/runways.hxx>
32 #include <Airports/parking.hxx>
33 #include <Airports/pavement.hxx>
34
35 static double distanceToLineSegment(const QVector2D& p, const QVector2D& a,
36                                     const QVector2D& b, double* outT = NULL)
37 {
38     QVector2D ab(b - a);
39     QVector2D ac(p - a);
40     
41     // Squared length, to avoid a sqrt
42     const qreal len2 = ab.lengthSquared();
43     
44     // Line null, the projection can't exist, we return the first point
45     if (qIsNull(len2)) {
46         if (outT) {
47             *outT = 0.0;
48         }
49         return (p - a).length();
50     }
51     
52     // Parametric value of the projection on the line
53     const qreal t = (ac.x() * ab.x() + ac.y() * ab.y()) / len2;
54     
55     if (t < 0.0) {
56         // Point is before the first point
57         if (outT) {
58             *outT = 0.0;
59         }
60         return (p - a).length();
61     } else if (t > 1.0) {
62         // Point is after the second point
63         if (outT) {
64             *outT = 1.0;
65         }
66         return (p - b).length();
67     } else {
68         if (outT) {
69             *outT = t;
70         }
71         
72         const QVector2D proj = a + t * ab;
73         return (proj - p).length();
74     }
75     
76     return 0.0;
77 }
78
79 AirportDiagram::AirportDiagram(QWidget* pr) :
80     BaseDiagram(pr),
81     m_approachDistanceNm(-1.0)
82 {
83 }
84
85 AirportDiagram::~AirportDiagram()
86 {
87
88 }
89
90 void AirportDiagram::setAirport(FGAirportRef apt)
91 {
92     m_airport = apt;
93     m_projectionCenter = apt ? apt->geod() : SGGeod();
94     m_runways.clear();
95     m_approachDistanceNm = -1.0;
96
97     if (apt) {
98         buildTaxiways();
99         buildPavements();
100     }
101
102     recomputeBounds(true);
103     update();
104 }
105
106 FGRunwayRef AirportDiagram::selectedRunway() const
107 {
108     return m_selectedRunway;
109 }
110
111 void AirportDiagram::setSelectedRunway(FGRunwayRef r)
112 {
113     if (r == m_selectedRunway) {
114         return;
115     }
116     
117     m_selectedRunway = r;
118     update();
119 }
120
121 void AirportDiagram::setApproachExtensionDistance(double distanceNm)
122 {
123     m_approachDistanceNm = distanceNm;
124     recomputeBounds(true);
125     update();
126 }
127
128 void AirportDiagram::addRunway(FGRunwayRef rwy)
129 {
130     Q_FOREACH(RunwayData rd, m_runways) {
131         if (rd.runway == rwy->reciprocalRunway()) {
132             return; // only add one end of reciprocal runways
133         }
134     }
135
136     RunwayData r;
137     r.p1 = project(rwy->geod());
138     r.p2 = project(rwy->end());
139     r.widthM = qRound(rwy->widthM());
140     r.runway = rwy;
141     m_runways.append(r);
142
143     recomputeBounds(false);
144     update();
145 }
146
147 void AirportDiagram::doComputeBounds()
148 {
149     Q_FOREACH(const RunwayData& r, m_runways) {
150         extendBounds(r.p1);
151         extendBounds(r.p2);
152     }
153
154     Q_FOREACH(const TaxiwayData& t, m_taxiways) {
155         extendBounds(t.p1);
156         extendBounds(t.p2);
157     }
158
159     Q_FOREACH(const ParkingData& p, m_parking) {
160         extendBounds(p.pt);
161     }
162
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));
166         extendBounds(pt);
167     }
168 }
169
170 void AirportDiagram::addParking(FGParkingRef park)
171 {
172     ParkingData pd = { project(park->geod()), park };
173     m_parking.push_back(pd);
174     recomputeBounds(false);
175     update();
176 }
177
178
179 void AirportDiagram::paintContents(QPainter* p)
180 {
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);
186
187     QTransform t(transform());
188     p->setTransform(t);
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     Q_FOREACH(const RunwayData& r, m_runways) {
210         QColor color(Qt::magenta);
211         if ((r.runway == m_selectedRunway) || (r.runway->reciprocalRunway() == m_selectedRunway)) {
212             color = Qt::yellow;
213         }
214         
215         p->setTransform(t);
216
217         QPen pen(color);
218         pen.setWidth(r.widthM);
219         p->setPen(pen);
220         
221         p->drawLine(r.p1, r.p2);
222
223     // draw idents
224         QString ident = QString::fromStdString(r.runway->ident());
225
226         p->translate(r.p1);
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);
230         
231         p->setPen((r.runway == m_selectedRunway) ? Qt::yellow : Qt::magenta);
232         p->drawText(QRect(-100, 5, 200, 200), ident, Qt::AlignHCenter | Qt::AlignTop);
233
234         FGRunway* recip = r.runway->reciprocalRunway();
235         QString recipIdent = QString::fromStdString(recip->ident());
236
237         p->setTransform(t);
238         p->translate(r.p2);
239         p->rotate(recip->headingDeg());
240         p->scale(1.0 / m_scale, 1.0/ m_scale);
241
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);
244     }
245
246     if (m_selectedRunway && (m_approachDistanceNm > 0.0)) {
247         p->setTransform(t);
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);
253         p->setPen(pen);
254         p->drawLine(pt, pt2);
255     }
256 }
257
258 void AirportDiagram::mouseReleaseEvent(QMouseEvent* me)
259 {
260     if (m_didPan)
261         return; // ignore panning drag+release ops here
262
263     QTransform t(transform());
264     double minDist = std::numeric_limits<double>::max();
265     FGRunwayRef bestRunway;
266     
267     Q_FOREACH(const RunwayData& r, m_runways) {
268         QPointF p1(t.map(r.p1)), p2(t.map(r.p2));
269         double t;
270         double d = distanceToLineSegment(QVector2D(me->pos()),
271                                          QVector2D(p1),
272                                          QVector2D(p2), &t);
273         if (d < minDist) {
274             if (t > 0.5) {
275                 bestRunway = r.runway->reciprocalRunway();
276             } else {
277                 bestRunway = r.runway;
278             }
279             minDist = d;
280         }
281     }
282     
283     if (minDist < 16.0) {
284         emit clickedRunway(bestRunway);
285     }
286 }
287
288 void AirportDiagram::buildTaxiways()
289 {
290     m_taxiways.clear();
291     for (unsigned int tIndex=0; tIndex < m_airport->numTaxiways(); ++tIndex) {
292         FGTaxiwayRef tx = m_airport->getTaxiwayByIndex(tIndex);
293
294         TaxiwayData td;
295         td.p1 = project(tx->geod());
296         td.p2 = project(tx->pointOnCenterline(tx->lengthM()));
297
298         td.widthM = tx->widthM();
299         m_taxiways.append(td);
300     }
301 }
302
303 void AirportDiagram::buildPavements()
304 {
305     m_pavements.clear();
306     for (unsigned int pIndex=0; pIndex < m_airport->numPavements(); ++pIndex) {
307         FGPavementRef pave = m_airport->getPavementByIndex(pIndex);
308         if (pave->getNodeList().empty()) {
309             continue;
310         }
311
312         QPainterPath pp;
313         QPointF startPoint;
314         bool closed = true;
315         QPointF p0 = project(pave->getNodeList().front()->mPos);
316
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;
321
322             // increment iterator so we can look at the next point
323             ++it;
324             QPointF nextPoint = (it == pave->getNodeList().end()) ? startPoint : project((*it)->mPos);
325
326             if (bn) {
327                 QPointF control = project(bn->mControl);
328                 QPointF endPoint = close ? startPoint : nextPoint;
329                 pp.quadTo(control, endPoint);
330             } else {
331                 // straight line segment
332                 if (closed) {
333                     pp.moveTo(p0);
334                     closed = false;
335                     startPoint = p0;
336                 } else
337                     pp.lineTo(p0);
338             }
339
340             if (close) {
341                 closed = true;
342                 pp.closeSubpath();
343                 startPoint = QPointF();
344             }
345
346             p0 = nextPoint;
347         } // of nodes iteration
348
349         if (!closed) {
350             pp.closeSubpath();
351         }
352
353         m_pavements.append(pp);
354     } // of pavements iteration
355 }