1 // AirportDiagram.cxx - part of GUI launcher using Qt5
3 // Written by James Turner, started December 2014.
5 // Copyright (C) 2014 James Turner <zakalawe@mac.com>
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.
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.
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.
21 #include "AirportDiagram.hxx"
28 #include <QMouseEvent>
30 #include <Airports/airport.hxx>
31 #include <Airports/runways.hxx>
32 #include <Airports/parking.hxx>
33 #include <Airports/pavement.hxx>
35 /* equatorial and polar earth radius */
36 const float rec = 6378137; // earth radius, equator (?)
37 const float rpol = 6356752.314f; // earth radius, polar (?)
39 //Returns Earth radius at a given latitude (Ellipsoide equation with two equal axis)
40 static float earth_radius_lat( float lat )
42 double a = cos(lat)/rec;
43 double b = sin(lat)/rpol;
44 return 1.0f / sqrt( a * a + b * b );
47 static double distanceToLineSegment(const QVector2D& p, const QVector2D& a,
48 const QVector2D& b, double* outT = NULL)
53 // Squared length, to avoid a sqrt
54 const qreal len2 = ab.lengthSquared();
56 // Line null, the projection can't exist, we return the first point
61 return (p - a).length();
64 // Parametric value of the projection on the line
65 const qreal t = (ac.x() * ab.x() + ac.y() * ab.y()) / len2;
68 // Point is before the first point
72 return (p - a).length();
74 // Point is after the second point
78 return (p - b).length();
84 const QVector2D proj = a + t * ab;
85 return (proj - p).length();
91 AirportDiagram::AirportDiagram(QWidget* pr) :
94 setSizePolicy(QSizePolicy::MinimumExpanding,
95 QSizePolicy::MinimumExpanding);
96 setMinimumSize(100, 100);
99 void AirportDiagram::setAirport(FGAirportRef apt)
102 m_projectionCenter = apt ? apt->geod() : SGGeod();
104 m_bounds = QRectF(); // clear
115 FGRunwayRef AirportDiagram::selectedRunway() const
117 return m_selectedRunway;
120 void AirportDiagram::setSelectedRunway(FGRunwayRef r)
122 if (r == m_selectedRunway) {
126 m_selectedRunway = r;
130 void AirportDiagram::addRunway(FGRunwayRef rwy)
132 Q_FOREACH(RunwayData rd, m_runways) {
133 if (rd.runway == rwy->reciprocalRunway()) {
134 return; // only add one end of reciprocal runways
138 QPointF p1 = project(rwy->geod()),
139 p2 = project(rwy->end());
146 r.widthM = qRound(rwy->widthM());
152 void AirportDiagram::addParking(FGParking* park)
154 QPointF p = project(park->geod());
159 QTransform AirportDiagram::transform() const
161 // fit bounds within our available space, allowing for a margin
162 const int MARGIN = 32; // pixels
163 double ratioInX = (width() - MARGIN * 2) / m_bounds.width();
164 double ratioInY = (height() - MARGIN * 2) / m_bounds.height();
165 double scale = std::min(ratioInX, ratioInY);
168 t.translate(width() / 2, height() / 2); // center projection origin in the widget
169 t.scale(scale, scale);
170 // center the bounding box (may not be at the origin)
171 t.translate(-m_bounds.center().x(), -m_bounds.center().y());
175 void AirportDiagram::paintEvent(QPaintEvent* pe)
178 p.setRenderHints(QPainter::Antialiasing);
179 p.fillRect(rect(), QColor(0x3f, 0x3f, 0x3f));
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);
187 QTransform t(transform());
191 QBrush brush(QColor(0x9f, 0x9f, 0x9f));
192 Q_FOREACH(const QPainterPath& path, m_pavements) {
197 Q_FOREACH(const TaxiwayData& t, m_taxiways) {
198 QPen pen(QColor(0x9f, 0x9f, 0x9f));
199 pen.setWidth(t.widthM);
201 p.drawLine(t.p1, t.p2);
209 Q_FOREACH(const RunwayData& r, m_runways) {
210 QColor color(Qt::magenta);
211 if ((r.runway == m_selectedRunway) || (r.runway->reciprocalRunway() == m_selectedRunway)) {
218 pen.setWidth(r.widthM);
221 p.drawLine(r.p1, r.p2);
224 QString ident = QString::fromStdString(r.runway->ident());
227 p.rotate(r.runway->headingDeg());
228 // invert scaling factor so we can use screen pixel sizes here
229 p.scale(1.0 / scale, 1.0/ scale);
231 p.setPen((r.runway == m_selectedRunway) ? Qt::yellow : Qt::magenta);
232 p.drawText(QRect(-100, 5, 200, 200), ident, Qt::AlignHCenter | Qt::AlignTop);
234 FGRunway* recip = r.runway->reciprocalRunway();
235 QString recipIdent = QString::fromStdString(recip->ident());
239 p.rotate(recip->headingDeg());
240 p.scale(1.0 / scale, 1.0/ scale);
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);
247 void AirportDiagram::mouseReleaseEvent(QMouseEvent* me)
249 QTransform t(transform());
250 double minDist = std::numeric_limits<double>::max();
251 FGRunwayRef bestRunway;
253 Q_FOREACH(const RunwayData& r, m_runways) {
254 QPointF p1(t.map(r.p1)), p2(t.map(r.p2));
256 double d = distanceToLineSegment(QVector2D(me->pos()),
261 bestRunway = r.runway->reciprocalRunway();
263 bestRunway = r.runway;
269 if (minDist < 16.0) {
270 emit clickedRunway(bestRunway);
274 void AirportDiagram::extendBounds(const QPointF& p)
276 if (p.x() < m_bounds.left()) {
277 m_bounds.setLeft(p.x());
278 } else if (p.x() > m_bounds.right()) {
279 m_bounds.setRight(p.x());
282 if (p.y() < m_bounds.top()) {
283 m_bounds.setTop(p.y());
284 } else if (p.y() > m_bounds.bottom()) {
285 m_bounds.setBottom(p.y());
289 QPointF AirportDiagram::project(const SGGeod& geod) const
291 double r = earth_radius_lat(geod.getLatitudeRad());
292 double ref_lat = m_projectionCenter.getLatitudeRad(),
293 ref_lon = m_projectionCenter.getLongitudeRad(),
294 lat = geod.getLatitudeRad(),
295 lon = geod.getLongitudeRad(),
296 lonDiff = lon - ref_lon;
298 double c = acos( sin(ref_lat) * sin(lat) + cos(ref_lat) * cos(lat) * cos(lonDiff) );
300 // angular distance from center is 0
301 return QPointF(0.0, 0.0);
304 double k = c / sin(c);
306 if (ref_lat == (90 * SG_DEGREES_TO_RADIANS))
308 x = (SGD_PI / 2 - lat) * sin(lonDiff);
309 y = -(SGD_PI / 2 - lat) * cos(lonDiff);
311 else if (ref_lat == -(90 * SG_DEGREES_TO_RADIANS))
313 x = (SGD_PI / 2 + lat) * sin(lonDiff);
314 y = (SGD_PI / 2 + lat) * cos(lonDiff);
318 x = k * cos(lat) * sin(lonDiff);
319 y = k * ( cos(ref_lat) * sin(lat) - sin(ref_lat) * cos(lat) * cos(lonDiff) );
322 return QPointF(x, -y) * r * m_scale;
325 void AirportDiagram::buildTaxiways()
328 for (unsigned int tIndex=0; tIndex < m_airport->numTaxiways(); ++tIndex) {
329 FGTaxiwayRef tx = m_airport->getTaxiwayByIndex(tIndex);
332 td.p1 = project(tx->geod());
333 td.p2 = project(tx->pointOnCenterline(tx->lengthM()));
336 td.widthM = tx->widthM();
337 m_taxiways.append(td);
341 void AirportDiagram::buildPavements()
344 for (unsigned int pIndex=0; pIndex < m_airport->numPavements(); ++pIndex) {
345 FGPavementRef pave = m_airport->getPavementByIndex(pIndex);
346 if (pave->getNodeList().empty()) {
353 QPointF p0 = project(pave->getNodeList().front()->mPos);
355 FGPavement::NodeList::const_iterator it;
356 for (it = pave->getNodeList().begin(); it != pave->getNodeList().end(); ) {
357 const FGPavement::BezierNode *bn = dynamic_cast<const FGPavement::BezierNode *>(it->get());
358 bool close = (*it)->mClose;
360 // increment iterator so we can look at the next point
362 QPointF nextPoint = (it == pave->getNodeList().end()) ? startPoint : project((*it)->mPos);
365 QPointF control = project(bn->mControl);
366 QPointF endPoint = close ? startPoint : nextPoint;
367 pp.quadTo(control, endPoint);
369 // straight line segment
381 startPoint = QPointF();
385 } // of nodes iteration
391 m_pavements.append(pp);
392 } // of pavements iteration