1 // BaseDiagram.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 "BaseDiagram.hxx"
28 #include <QMouseEvent>
30 #include <Navaids/navrecord.hxx>
31 #include <Navaids/positioned.hxx>
32 #include <Airports/airport.hxx>
34 /* equatorial and polar earth radius */
35 const float rec = 6378137; // earth radius, equator (?)
36 const float rpol = 6356752.314f; // earth radius, polar (?)
38 //Returns Earth radius at a given latitude (Ellipsoide equation with two equal axis)
39 static float earth_radius_lat( float lat )
41 double a = cos(lat)/rec;
42 double b = sin(lat)/rpol;
43 return 1.0f / sqrt( a * a + b * b );
46 BaseDiagram::BaseDiagram(QWidget* pr) :
49 m_wheelAngleDeltaAccumulator(0)
51 setSizePolicy(QSizePolicy::MinimumExpanding,
52 QSizePolicy::MinimumExpanding);
53 setMinimumSize(100, 100);
56 QTransform BaseDiagram::transform() const
59 t.translate(width() / 2, height() / 2); // center projection origin in the widget
60 t.scale(m_scale, m_scale);
62 // apply any pan offset that exists
63 t.translate(m_panOffset.x(), m_panOffset.y());
64 // center the bounding box (may not be at the origin)
65 t.translate(-m_bounds.center().x(), -m_bounds.center().y());
69 void BaseDiagram::extendRect(QRectF &r, const QPointF &p)
71 if (p.x() < r.left()) {
73 } else if (p.x() > r.right()) {
77 if (p.y() < r.top()) {
79 } else if (p.y() > r.bottom()) {
84 void BaseDiagram::paintEvent(QPaintEvent* pe)
87 p.setRenderHints(QPainter::Antialiasing);
88 p.fillRect(rect(), QColor(0x3f, 0x3f, 0x3f));
91 // fit bounds within our available space, allowing for a margin
92 const int MARGIN = 32; // pixels
93 double ratioInX = (width() - MARGIN * 2) / m_bounds.width();
94 double ratioInY = (height() - MARGIN * 2) / m_bounds.height();
95 m_scale = std::min(ratioInX, ratioInY);
98 QTransform t(transform());
106 class MapFilter : public FGPositioned::TypeFilter
111 // addType(FGPositioned::FIX);
112 addType(FGPositioned::AIRPORT);
113 addType(FGPositioned::NDB);
114 addType(FGPositioned::VOR);
117 virtual bool pass(FGPositioned* aPos) const
119 bool ok = TypeFilter::pass(aPos);
120 if (ok && (aPos->type() == FGPositioned::FIX)) {
121 // ignore fixes which end in digits
122 if (aPos->ident().length() > 4 && isdigit(aPos->ident()[3]) && isdigit(aPos->ident()[4])) {
132 void BaseDiagram::paintNavaids(QPainter* painter)
134 QTransform xf = painter->transform();
135 painter->setTransform(QTransform()); // reset to identity
136 QTransform invT = xf.inverted();
137 SGGeod topLeft = unproject(invT.map(QPointF(0,0)), m_projectionCenter);
139 double minRunwayLengthFt = (16 / m_scale) * SG_METER_TO_FEET;
140 // add 10nm fudge factor
141 double drawRangeNm = SGGeodesy::distanceNm(m_projectionCenter, topLeft) + 10.0;
142 //qDebug() << "draw range computed as:" << drawRangeNm;
145 FGPositionedList items = FGPositioned::findWithinRange(m_projectionCenter, drawRangeNm, &f);
149 FGPositionedList::const_iterator it;
150 for (it = items.begin(); it != items.end(); ++it) {
151 bool drawAsIcon = true;
153 if ((*it)->type() == FGPositioned::AIRPORT) {
154 FGAirport* apt = static_cast<FGAirport*>(it->ptr());
155 if (apt->hasHardRunwayOfLengthFt(minRunwayLengthFt)) {
158 painter->setTransform(xf);
159 QVector<QLineF> lines = projectAirportRuwaysWithCenter(apt, m_projectionCenter);
161 QPen pen(QColor(0x03, 0x83, 0xbf), 8);
162 pen.setCosmetic(true);
163 painter->setPen(pen);
164 painter->drawLines(lines);
166 QPen linePen(Qt::white, 2);
167 linePen.setCosmetic(true);
168 painter->setPen(linePen);
169 painter->drawLines(lines);
171 painter->resetTransform();
176 QPixmap pm = iconForPositioned(*it);
177 QPointF loc = xf.map(project((*it)->geod()));
178 loc -= QPointF(pm.width() >> 1, pm.height() >> 1);
179 painter->drawPixmap(loc, pm);
184 painter->setTransform(xf);
187 void BaseDiagram::mousePressEvent(QMouseEvent *me)
189 m_lastMousePos = me->pos();
193 void BaseDiagram::mouseMoveEvent(QMouseEvent *me)
195 m_autoScalePan = false;
197 QPointF delta = me->pos() - m_lastMousePos;
198 m_lastMousePos = me->pos();
200 // offset is stored in metres so we don't have to modify it when
202 m_panOffset += (delta / m_scale);
210 return (v == 0) ? 0 : (v < 0) ? -1 : 1;
213 void BaseDiagram::wheelEvent(QWheelEvent *we)
215 m_autoScalePan = false;
217 int delta = we->angleDelta().y();
221 if (intSign(m_wheelAngleDeltaAccumulator) != intSign(delta)) {
222 m_wheelAngleDeltaAccumulator = 0;
225 m_wheelAngleDeltaAccumulator += delta;
226 if (m_wheelAngleDeltaAccumulator > 120) {
227 m_wheelAngleDeltaAccumulator = 0;
229 } else if (m_wheelAngleDeltaAccumulator < -120) {
230 m_wheelAngleDeltaAccumulator = 0;
237 void BaseDiagram::paintContents(QPainter* painter)
241 void BaseDiagram::recomputeBounds(bool resetZoom)
247 m_autoScalePan = true;
249 m_panOffset = QPointF();
255 void BaseDiagram::doComputeBounds()
257 // no-op in the base class
260 void BaseDiagram::extendBounds(const QPointF& p)
262 extendRect(m_bounds, p);
265 QPointF BaseDiagram::project(const SGGeod& geod, const SGGeod& center)
267 double r = earth_radius_lat(geod.getLatitudeRad());
268 double ref_lat = center.getLatitudeRad(),
269 ref_lon = center.getLongitudeRad(),
270 lat = geod.getLatitudeRad(),
271 lon = geod.getLongitudeRad(),
272 lonDiff = lon - ref_lon;
274 double c = acos( sin(ref_lat) * sin(lat) + cos(ref_lat) * cos(lat) * cos(lonDiff) );
276 // angular distance from center is 0
277 return QPointF(0.0, 0.0);
280 double k = c / sin(c);
282 if (ref_lat == (90 * SG_DEGREES_TO_RADIANS))
284 x = (SGD_PI / 2 - lat) * sin(lonDiff);
285 y = -(SGD_PI / 2 - lat) * cos(lonDiff);
287 else if (ref_lat == -(90 * SG_DEGREES_TO_RADIANS))
289 x = (SGD_PI / 2 + lat) * sin(lonDiff);
290 y = (SGD_PI / 2 + lat) * cos(lonDiff);
294 x = k * cos(lat) * sin(lonDiff);
295 y = k * ( cos(ref_lat) * sin(lat) - sin(ref_lat) * cos(lat) * cos(lonDiff) );
298 return QPointF(x, -y) * r;
301 SGGeod BaseDiagram::unproject(const QPointF& xy, const SGGeod& center)
303 double r = earth_radius_lat(center.getLatitudeRad());
306 ref_lat = center.getLatitudeRad(),
307 ref_lon = center.getLongitudeRad(),
308 rho = QVector2D(xy).length(),
315 double x = xy.x(), y = xy.y();
316 lat = asin( cos(c) * sin(ref_lat) + (y * sin(c) * cos(ref_lat)) / rho);
318 if (ref_lat == (90 * SG_DEGREES_TO_RADIANS)) // north pole
320 lon = ref_lon + atan(-x/y);
322 else if (ref_lat == -(90 * SG_DEGREES_TO_RADIANS)) // south pole
324 lon = ref_lon + atan(x/y);
328 lon = ref_lon + atan(x* sin(c) / (rho * cos(ref_lat) * cos(c) - y * sin(ref_lat) * sin(c)));
331 return SGGeod::fromRad(lon, lat);
334 QPointF BaseDiagram::project(const SGGeod& geod) const
336 return project(geod, m_projectionCenter);
339 QPixmap BaseDiagram::iconForPositioned(const FGPositionedRef& pos)
341 // if airport type, check towered or untowered
343 bool isTowered = false;
344 if (FGAirport::isAirportType(pos)) {
345 FGAirport* apt = static_cast<FGAirport*>(pos.ptr());
346 isTowered = apt->hasTower();
349 switch (pos->type()) {
350 case FGPositioned::VOR:
353 if (static_cast<FGNavRecord*>(pos.ptr())->hasDME())
354 return QPixmap(":/vor-dme-icon");
356 return QPixmap(":/vor-icon");
358 case FGPositioned::AIRPORT:
359 return iconForAirport(static_cast<FGAirport*>(pos.ptr()));
361 case FGPositioned::HELIPORT:
362 return QPixmap(":/heliport-icon");
363 case FGPositioned::SEAPORT:
364 return QPixmap(isTowered ? ":/seaport-tower-icon" : ":/seaport-icon");
365 case FGPositioned::NDB:
366 return QPixmap(":/ndb-icon");
367 case FGPositioned::FIX:
368 return QPixmap(":/waypoint-icon");
377 QPixmap BaseDiagram::iconForAirport(FGAirport* apt)
379 if (!apt->hasHardRunwayOfLengthFt(1500)) {
380 return QPixmap(apt->hasTower() ? ":/airport-tower-icon" : ":/airport-icon");
383 if (apt->hasHardRunwayOfLengthFt(8500)) {
384 QPixmap result(32, 32);
385 result.fill(Qt::transparent);
388 p.setRenderHint(QPainter::Antialiasing, true);
389 QRectF b = result.rect().adjusted(4, 4, -4, -4);
390 QVector<QLineF> lines = projectAirportRuwaysIntoRect(apt, b);
392 p.setPen(QPen(QColor(0x03, 0x83, 0xbf), 8));
395 p.setPen(QPen(Qt::white, 2));
401 QPixmap result(25, 25);
402 result.fill(Qt::transparent);
406 p.setRenderHint(QPainter::Antialiasing, true);
409 p.setBrush(apt->hasTower() ? QColor(0x03, 0x83, 0xbf) :
410 QColor(0x9b, 0x5d, 0xa2));
411 p.drawEllipse(QPointF(13, 13), 10, 10);
413 FGRunwayRef r = apt->longestRunway();
415 p.setPen(QPen(Qt::white, 2));
417 p.rotate(r->headingDeg());
418 p.drawLine(0, -8, 0, 8);
424 QVector<QLineF> BaseDiagram::projectAirportRuwaysWithCenter(FGAirportRef apt, const SGGeod& c)
428 const FGRunwayList& runways(apt->getRunwaysWithoutReciprocals());
429 FGRunwayList::const_iterator it;
431 for (it = runways.begin(); it != runways.end(); ++it) {
432 FGRunwayRef rwy = *it;
433 QPointF p1 = project(rwy->geod(), c);
434 QPointF p2 = project(rwy->end(), c);
435 r.append(QLineF(p1, p2));
441 QVector<QLineF> BaseDiagram::projectAirportRuwaysIntoRect(FGAirportRef apt, const QRectF &bounds)
443 QVector<QLineF> r = projectAirportRuwaysWithCenter(apt, apt->geod());
446 Q_FOREACH(const QLineF& l, r) {
447 extendRect(extent, l.p1());
448 extendRect(extent, l.p2());
451 // find constraining scale factor
452 double ratioInX = bounds.width() / extent.width();
453 double ratioInY = bounds.height() / extent.height();
456 t.translate(bounds.left(), bounds.top());
457 t.scale(std::min(ratioInX, ratioInY),
458 std::min(ratioInX, ratioInY));
459 t.translate(-extent.left(), -extent.top()); // move unscaled to 0,0
461 for (int i=0; i<r.size(); ++i) {