]> git.mxchange.org Git - flightgear.git/blob - src/GUI/AirportDiagram.cxx
Enable anti-aliasing in the 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 <QPainter>
24 #include <QDebug>
25
26 #include <Airports/airport.hxx>
27 #include <Airports/runways.hxx>
28 #include <Airports/parking.hxx>
29 #include <Airports/pavement.hxx>
30
31 /* equatorial and polar earth radius */
32 const float rec  = 6378137;          // earth radius, equator (?)
33 const float rpol = 6356752.314f;      // earth radius, polar   (?)
34
35 //Returns Earth radius at a given latitude (Ellipsoide equation with two equal axis)
36 static float earth_radius_lat( float lat )
37 {
38     double a = cos(lat)/rec;
39     double b = sin(lat)/rpol;
40     return 1.0f / sqrt( a * a + b * b );
41 }
42
43
44 AirportDiagram::AirportDiagram(QWidget* pr) :
45 QWidget(pr)
46 {
47     setSizePolicy(QSizePolicy::MinimumExpanding,
48                   QSizePolicy::MinimumExpanding);
49     setMinimumSize(100, 100);
50 }
51
52 void AirportDiagram::setAirport(FGAirportRef apt)
53 {
54     m_airport = apt;
55     m_projectionCenter = apt ? apt->geod() : SGGeod();
56     m_scale = 1.0;
57     m_bounds = QRectF(); // clear
58     m_runways.clear();
59
60     if (apt) {
61         buildTaxiways();
62         buildPavements();
63     }
64
65     update();
66 }
67
68 void AirportDiagram::addRunway(FGRunwayRef rwy)
69 {
70     Q_FOREACH(RunwayData rd, m_runways) {
71         if (rd.runway == rwy->reciprocalRunway()) {
72             return; // only add one end of reciprocal runways
73         }
74     }
75
76     QPointF p1 = project(rwy->geod()),
77     p2 = project(rwy->end());
78     extendBounds(p1);
79     extendBounds(p2);
80
81     RunwayData r;
82     r.p1 = p1;
83     r.p2 = p2;
84     r.widthM = qRound(rwy->widthM());
85     r.runway = rwy;
86     m_runways.append(r);
87     update();
88 }
89
90 void AirportDiagram::addParking(FGParking* park)
91 {
92     QPointF p = project(park->geod());
93     extendBounds(p);
94     update();
95 }
96
97 void AirportDiagram::paintEvent(QPaintEvent* pe)
98 {
99     QPainter p(this);
100     p.setRenderHints(QPainter::Antialiasing);
101     p.fillRect(rect(), QColor(0x3f, 0x3f, 0x3f));
102
103     // fit bounds within our available space, allowing for a margin
104     const int MARGIN = 32; // pixels
105     double ratioInX = (width() - MARGIN * 2) / m_bounds.width();
106     double ratioInY = (height() - MARGIN * 2) / m_bounds.height();
107     double scale = std::min(ratioInX, ratioInY);
108
109     QTransform t;
110     t.translate(width() / 2, height() / 2); // center projection origin in the widget
111     t.scale(scale, scale);
112     // center the bounding box (may not be at the origin)
113     t.translate(-m_bounds.center().x(), -m_bounds.center().y());
114     p.setTransform(t);
115
116 // pavements
117     QBrush brush(QColor(0x9f, 0x9f, 0x9f));
118     Q_FOREACH(const QPainterPath& path, m_pavements) {
119         p.drawPath(path);
120     }
121
122 // taxiways
123     Q_FOREACH(const TaxiwayData& t, m_taxiways) {
124         QPen pen(QColor(0x9f, 0x9f, 0x9f));
125         pen.setWidth(t.widthM);
126         p.setPen(pen);
127         p.drawLine(t.p1, t.p2);
128     }
129
130 // runways
131     QPen pen(Qt::magenta);
132     QFont f;
133     f.setPixelSize(14);
134     p.setFont(f);
135
136     Q_FOREACH(const RunwayData& r, m_runways) {
137         p.setTransform(t);
138
139         pen.setWidth(r.widthM);
140         p.setPen(pen);
141         p.drawLine(r.p1, r.p2);
142
143     // draw idents
144         QString ident = QString::fromStdString(r.runway->ident());
145
146         p.translate(r.p1);
147         p.rotate(r.runway->headingDeg());
148         // invert scaling factor so we can use screen pixel sizes here
149         p.scale(1.0 / scale, 1.0/ scale);
150
151         p.drawText(QRect(-100, 5, 200, 200), ident, Qt::AlignHCenter | Qt::AlignTop);
152
153         FGRunway* recip = r.runway->reciprocalRunway();
154         QString recipIdent = QString::fromStdString(recip->ident());
155
156         p.setTransform(t);
157         p.translate(r.p2);
158         p.rotate(recip->headingDeg());
159         p.scale(1.0 / scale, 1.0/ scale);
160
161         p.drawText(QRect(-100, 5, 200, 200), recipIdent, Qt::AlignHCenter | Qt::AlignTop);
162     }
163 }
164
165
166 void AirportDiagram::extendBounds(const QPointF& p)
167 {
168     if (p.x() < m_bounds.left()) {
169         m_bounds.setLeft(p.x());
170     } else if (p.x() > m_bounds.right()) {
171         m_bounds.setRight(p.x());
172     }
173
174     if (p.y() < m_bounds.top()) {
175         m_bounds.setTop(p.y());
176     } else if (p.y() > m_bounds.bottom()) {
177         m_bounds.setBottom(p.y());
178     }
179 }
180
181 QPointF AirportDiagram::project(const SGGeod& geod) const
182 {
183     double r = earth_radius_lat(geod.getLatitudeRad());
184     double ref_lat = m_projectionCenter.getLatitudeRad(),
185     ref_lon = m_projectionCenter.getLongitudeRad(),
186     lat = geod.getLatitudeRad(),
187     lon = geod.getLongitudeRad(),
188     lonDiff = lon - ref_lon;
189
190     double c = acos( sin(ref_lat) * sin(lat) + cos(ref_lat) * cos(lat) * cos(lonDiff) );
191     if (c == 0.0) {
192         // angular distance from center is 0
193         return QPointF(0.0, 0.0);
194     }
195
196     double k = c / sin(c);
197     double x, y;
198     if (ref_lat == (90 * SG_DEGREES_TO_RADIANS))
199     {
200         x = (SGD_PI / 2 - lat) * sin(lonDiff);
201         y = -(SGD_PI / 2 - lat) * cos(lonDiff);
202     }
203     else if (ref_lat == -(90 * SG_DEGREES_TO_RADIANS))
204     {
205         x = (SGD_PI / 2 + lat) * sin(lonDiff);
206         y = (SGD_PI / 2 + lat) * cos(lonDiff);
207     }
208     else
209     {
210         x = k * cos(lat) * sin(lonDiff);
211         y = k * ( cos(ref_lat) * sin(lat) - sin(ref_lat) * cos(lat) * cos(lonDiff) );
212     }
213
214     return QPointF(x, -y) * r * m_scale;
215 }
216
217 void AirportDiagram::buildTaxiways()
218 {
219     m_taxiways.clear();
220     for (unsigned int tIndex=0; tIndex < m_airport->numTaxiways(); ++tIndex) {
221         FGTaxiwayRef tx = m_airport->getTaxiwayByIndex(tIndex);
222
223         TaxiwayData td;
224         td.p1 = project(tx->geod());
225         td.p2 = project(tx->pointOnCenterline(tx->lengthM()));
226         extendBounds(td.p1);
227         extendBounds(td.p2);
228         td.widthM = tx->widthM();
229         m_taxiways.append(td);
230     }
231 }
232
233 void AirportDiagram::buildPavements()
234 {
235     m_pavements.clear();
236     for (unsigned int pIndex=0; pIndex < m_airport->numPavements(); ++pIndex) {
237         FGPavementRef pave = m_airport->getPavementByIndex(pIndex);
238         if (pave->getNodeList().empty()) {
239             continue;
240         }
241
242         QPainterPath pp;
243         QPointF startPoint;
244         bool closed = true;
245         QPointF p0 = project(pave->getNodeList().front()->mPos);
246
247         FGPavement::NodeList::const_iterator it;
248         for (it = pave->getNodeList().begin(); it != pave->getNodeList().end(); ) {
249             const FGPavement::BezierNode *bn = dynamic_cast<const FGPavement::BezierNode *>(it->get());
250             bool close = (*it)->mClose;
251
252             // increment iterator so we can look at the next point
253             ++it;
254             QPointF nextPoint = (it == pave->getNodeList().end()) ? startPoint : project((*it)->mPos);
255
256             if (bn) {
257                 QPointF control = project(bn->mControl);
258                 QPointF endPoint = close ? startPoint : nextPoint;
259                 pp.quadTo(control, endPoint);
260             } else {
261                 // straight line segment
262                 if (closed) {
263                     pp.moveTo(p0);
264                     closed = false;
265                     startPoint = p0;
266                 } else
267                     pp.lineTo(p0);
268             }
269
270             if (close) {
271                 closed = true;
272                 pp.closeSubpath();
273                 startPoint = QPointF();
274             }
275
276             p0 = nextPoint;
277         } // of nodes iteration
278
279         if (!closed) {
280             pp.closeSubpath();
281         }
282
283         m_pavements.append(pp);
284     } // of pavements iteration
285 }