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