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"
26 #include <Airports/airport.hxx>
27 #include <Airports/runways.hxx>
28 #include <Airports/parking.hxx>
29 #include <Airports/pavement.hxx>
31 /* equatorial and polar earth radius */
32 const float rec = 6378137; // earth radius, equator (?)
33 const float rpol = 6356752.314f; // earth radius, polar (?)
35 //Returns Earth radius at a given latitude (Ellipsoide equation with two equal axis)
36 static float earth_radius_lat( float lat )
38 double a = cos(lat)/rec;
39 double b = sin(lat)/rpol;
40 return 1.0f / sqrt( a * a + b * b );
44 AirportDiagram::AirportDiagram(QWidget* pr) :
47 setSizePolicy(QSizePolicy::MinimumExpanding,
48 QSizePolicy::MinimumExpanding);
49 setMinimumSize(100, 100);
52 void AirportDiagram::setAirport(FGAirportRef apt)
55 m_projectionCenter = apt ? apt->geod() : SGGeod();
57 m_bounds = QRectF(); // clear
68 void AirportDiagram::addRunway(FGRunwayRef rwy)
70 Q_FOREACH(RunwayData rd, m_runways) {
71 if (rd.runway == rwy->reciprocalRunway()) {
72 return; // only add one end of reciprocal runways
76 QPointF p1 = project(rwy->geod()),
77 p2 = project(rwy->end());
84 r.widthM = qRound(rwy->widthM());
90 void AirportDiagram::addParking(FGParking* park)
92 QPointF p = project(park->geod());
97 void AirportDiagram::paintEvent(QPaintEvent* pe)
100 p.setRenderHints(QPainter::Antialiasing);
101 p.fillRect(rect(), QColor(0x3f, 0x3f, 0x3f));
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);
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());
117 QBrush brush(QColor(0x9f, 0x9f, 0x9f));
118 Q_FOREACH(const QPainterPath& path, m_pavements) {
123 Q_FOREACH(const TaxiwayData& t, m_taxiways) {
124 QPen pen(QColor(0x9f, 0x9f, 0x9f));
125 pen.setWidth(t.widthM);
127 p.drawLine(t.p1, t.p2);
131 QPen pen(Qt::magenta);
136 Q_FOREACH(const RunwayData& r, m_runways) {
139 pen.setWidth(r.widthM);
141 p.drawLine(r.p1, r.p2);
144 QString ident = QString::fromStdString(r.runway->ident());
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);
151 p.drawText(QRect(-100, 5, 200, 200), ident, Qt::AlignHCenter | Qt::AlignTop);
153 FGRunway* recip = r.runway->reciprocalRunway();
154 QString recipIdent = QString::fromStdString(recip->ident());
158 p.rotate(recip->headingDeg());
159 p.scale(1.0 / scale, 1.0/ scale);
161 p.drawText(QRect(-100, 5, 200, 200), recipIdent, Qt::AlignHCenter | Qt::AlignTop);
166 void AirportDiagram::extendBounds(const QPointF& p)
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());
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());
181 QPointF AirportDiagram::project(const SGGeod& geod) const
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;
190 double c = acos( sin(ref_lat) * sin(lat) + cos(ref_lat) * cos(lat) * cos(lonDiff) );
192 // angular distance from center is 0
193 return QPointF(0.0, 0.0);
196 double k = c / sin(c);
198 if (ref_lat == (90 * SG_DEGREES_TO_RADIANS))
200 x = (SGD_PI / 2 - lat) * sin(lonDiff);
201 y = -(SGD_PI / 2 - lat) * cos(lonDiff);
203 else if (ref_lat == -(90 * SG_DEGREES_TO_RADIANS))
205 x = (SGD_PI / 2 + lat) * sin(lonDiff);
206 y = (SGD_PI / 2 + lat) * cos(lonDiff);
210 x = k * cos(lat) * sin(lonDiff);
211 y = k * ( cos(ref_lat) * sin(lat) - sin(ref_lat) * cos(lat) * cos(lonDiff) );
214 return QPointF(x, -y) * r * m_scale;
217 void AirportDiagram::buildTaxiways()
220 for (unsigned int tIndex=0; tIndex < m_airport->numTaxiways(); ++tIndex) {
221 FGTaxiwayRef tx = m_airport->getTaxiwayByIndex(tIndex);
224 td.p1 = project(tx->geod());
225 td.p2 = project(tx->pointOnCenterline(tx->lengthM()));
228 td.widthM = tx->widthM();
229 m_taxiways.append(td);
233 void AirportDiagram::buildPavements()
236 for (unsigned int pIndex=0; pIndex < m_airport->numPavements(); ++pIndex) {
237 FGPavementRef pave = m_airport->getPavementByIndex(pIndex);
238 if (pave->getNodeList().empty()) {
245 QPointF p0 = project(pave->getNodeList().front()->mPos);
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;
252 // increment iterator so we can look at the next point
254 QPointF nextPoint = (it == pave->getNodeList().end()) ? startPoint : project((*it)->mPos);
257 QPointF control = project(bn->mControl);
258 QPointF endPoint = close ? startPoint : nextPoint;
259 pp.quadTo(control, endPoint);
261 // straight line segment
273 startPoint = QPointF();
277 } // of nodes iteration
283 m_pavements.append(pp);
284 } // of pavements iteration