#include <Navaids/navrecord.hxx>
#include <Navaids/positioned.hxx>
#include <Airports/airport.hxx>
+#include <Navaids/PolyLine.hxx>
+
+#include "QtLauncher_fwd.hxx"
/* equatorial and polar earth radius */
const float rec = 6378137; // earth radius, equator (?)
const float rpol = 6356752.314f; // earth radius, polar (?)
+const double MINIMUM_SCALE = 0.002;
+
//Returns Earth radius at a given latitude (Ellipsoide equation with two equal axis)
static float earth_radius_lat( float lat )
{
return t;
}
+void BaseDiagram::clearIgnoredNavaids()
+{
+ m_ignored.clear();
+}
+
+void BaseDiagram::addIgnoredNavaid(FGPositionedRef pos)
+{
+ if (isNavaidIgnored(pos))
+ return;
+ m_ignored.push_back(pos);
+}
+
void BaseDiagram::extendRect(QRectF &r, const QPointF &p)
{
if (p.x() < r.left()) {
QTransform t(transform());
p.setTransform(t);
+ paintPolygonData(&p);
+
paintNavaids(&p);
paintContents(&p);
}
+void BaseDiagram::paintAirplaneIcon(QPainter* painter, const SGGeod& geod, int headingDeg)
+{
+ QPointF pos = project(geod);
+ QPixmap pix(":/airplane-icon");
+ pos = painter->transform().map(pos);
+ painter->resetTransform();
+ painter->translate(pos.x(), pos.y());
+ painter->rotate(headingDeg);
+
+ painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
+ QRect airplaneIconRect = pix.rect();
+ airplaneIconRect.moveCenter(QPoint(0,0));
+ painter->drawPixmap(airplaneIconRect, pix);
+}
+
+void BaseDiagram::paintPolygonData(QPainter* painter)
+{
+ QTransform xf = painter->transform();
+ QTransform invT = xf.inverted();
+
+ SGGeod topLeft = unproject(invT.map(rect().topLeft()), m_projectionCenter);
+ SGGeod viewCenter = unproject(invT.map(rect().center()), m_projectionCenter);
+ SGGeod bottomRight = unproject(invT.map(rect().bottomRight()), m_projectionCenter);
+
+ double drawRangeNm = std::max(SGGeodesy::distanceNm(viewCenter, topLeft),
+ SGGeodesy::distanceNm(viewCenter, bottomRight));
+
+ flightgear::PolyLineList lines(flightgear::PolyLine::linesNearPos(viewCenter, drawRangeNm,
+ flightgear::PolyLine::COASTLINE));
+
+ QPen waterPen(QColor(64, 64, 255), 1);
+ waterPen.setCosmetic(true);
+ painter->setPen(waterPen);
+ flightgear::PolyLineList::const_iterator it;
+ for (it=lines.begin(); it != lines.end(); ++it) {
+ paintGeodVec(painter, (*it)->points());
+ }
+
+ lines = flightgear::PolyLine::linesNearPos(viewCenter, drawRangeNm,
+ flightgear::PolyLine::URBAN);
+ for (it=lines.begin(); it != lines.end(); ++it) {
+ fillClosedGeodVec(painter, QColor(192, 192, 96), (*it)->points());
+ }
+
+ lines = flightgear::PolyLine::linesNearPos(viewCenter, drawRangeNm,
+ flightgear::PolyLine::RIVER);
+
+ painter->setPen(waterPen);
+ for (it=lines.begin(); it != lines.end(); ++it) {
+ paintGeodVec(painter, (*it)->points());
+ }
+
+
+ lines = flightgear::PolyLine::linesNearPos(viewCenter, drawRangeNm,
+ flightgear::PolyLine::LAKE);
+
+ for (it=lines.begin(); it != lines.end(); ++it) {
+ fillClosedGeodVec(painter, QColor(128, 128, 255),
+ (*it)->points());
+ }
+
+
+}
+
+void BaseDiagram::paintGeodVec(QPainter* painter, const flightgear::SGGeodVec& vec)
+{
+ QVector<QPointF> projected;
+ projected.reserve(vec.size());
+ flightgear::SGGeodVec::const_iterator it;
+ for (it=vec.begin(); it != vec.end(); ++it) {
+ projected.append(project(*it));
+ }
+
+ painter->drawPolyline(projected.data(), projected.size());
+}
+
+void BaseDiagram::fillClosedGeodVec(QPainter* painter, const QColor& color, const flightgear::SGGeodVec& vec)
+{
+ QVector<QPointF> projected;
+ projected.reserve(vec.size());
+ flightgear::SGGeodVec::const_iterator it;
+ for (it=vec.begin(); it != vec.end(); ++it) {
+ projected.append(project(*it));
+ }
+
+ painter->setPen(Qt::NoPen);
+ painter->setBrush(color);
+ painter->drawPolygon(projected.data(), projected.size());
+}
+
class MapFilter : public FGPositioned::TypeFilter
{
public:
- MapFilter()
+
+ MapFilter(LauncherAircraftType aircraft)
{
// addType(FGPositioned::FIX);
- addType(FGPositioned::AIRPORT);
addType(FGPositioned::NDB);
addType(FGPositioned::VOR);
+
+ if (aircraft == Helicopter) {
+ addType(FGPositioned::HELIPAD);
+ }
+
+ if (aircraft == Seaplane) {
+ addType(FGPositioned::SEAPORT);
+ } else {
+ addType(FGPositioned::AIRPORT);
+ }
}
virtual bool pass(FGPositioned* aPos) const
{
bool ok = TypeFilter::pass(aPos);
+ // fix-filtering code disabled since fixed are entirely disabled
+#if 0
if (ok && (aPos->type() == FGPositioned::FIX)) {
// ignore fixes which end in digits
if (aPos->ident().length() > 4 && isdigit(aPos->ident()[3]) && isdigit(aPos->ident()[4])) {
return false;
}
}
-
+#endif
return ok;
}
};
+void BaseDiagram::splitItems(const FGPositionedList& in, FGPositionedList& navaids,
+ FGPositionedList& ports)
+{
+ FGPositionedList::const_iterator it = in.begin();
+ for (; it != in.end(); ++it) {
+ if (FGAirport::isAirportType(it->ptr())) {
+ ports.push_back(*it);
+ } else {
+ navaids.push_back(*it);
+ }
+ }
+}
+
+bool orderAirportsByRunwayLength(const FGPositionedRef& a,
+ const FGPositionedRef& b)
+{
+ FGAirport* aptA = static_cast<FGAirport*>(a.ptr());
+ FGAirport* aptB = static_cast<FGAirport*>(b.ptr());
+
+ return aptA->longestRunway()->lengthFt() > aptB->longestRunway()->lengthFt();
+}
void BaseDiagram::paintNavaids(QPainter* painter)
{
QTransform xf = painter->transform();
painter->setTransform(QTransform()); // reset to identity
QTransform invT = xf.inverted();
- SGGeod topLeft = unproject(invT.map(QPointF(0,0)), m_projectionCenter);
- double minRunwayLengthFt = (16 / m_scale) * SG_METER_TO_FEET;
- // add 10nm fudge factor
- double drawRangeNm = SGGeodesy::distanceNm(m_projectionCenter, topLeft) + 10.0;
- //qDebug() << "draw range computed as:" << drawRangeNm;
- MapFilter f;
- FGPositionedList items = FGPositioned::findWithinRange(m_projectionCenter, drawRangeNm, &f);
+ SGGeod topLeft = unproject(invT.map(rect().topLeft()), m_projectionCenter);
+ SGGeod viewCenter = unproject(invT.map(rect().center()), m_projectionCenter);
+ SGGeod bottomRight = unproject(invT.map(rect().bottomRight()), m_projectionCenter);
+
+ double drawRangeNm = std::max(SGGeodesy::distanceNm(viewCenter, topLeft),
+ SGGeodesy::distanceNm(viewCenter, bottomRight));
+
+ MapFilter f(m_aircraftType);
+ FGPositionedList items = FGPositioned::findWithinRange(viewCenter, drawRangeNm, &f);
+
+ FGPositionedList navaids, ports;
+ splitItems(items, navaids, ports);
+
+ if (ports.size() >= 40) {
+ FGPositionedList::iterator middle = ports.begin() + 40;
+ std::partial_sort(ports.begin(), middle, ports.end(),
+ orderAirportsByRunwayLength);
+ ports.resize(40);
+ }
- // pass 0 - icons
+ m_labelRects.clear();
+ m_labelRects.reserve(items.size());
FGPositionedList::const_iterator it;
- for (it = items.begin(); it != items.end(); ++it) {
- bool drawAsIcon = true;
+ for (it = ports.begin(); it != ports.end(); ++it) {
+ paintNavaid(painter, xf, *it);
+ }
- if ((*it)->type() == FGPositioned::AIRPORT) {
- FGAirport* apt = static_cast<FGAirport*>(it->ptr());
- if (apt->hasHardRunwayOfLengthFt(minRunwayLengthFt)) {
+ for (it = navaids.begin(); it != navaids.end(); ++it) {
+ paintNavaid(painter, xf, *it);
+ }
- drawAsIcon = false;
- painter->setTransform(xf);
- QVector<QLineF> lines = projectAirportRuwaysWithCenter(apt, m_projectionCenter);
- QPen pen(QColor(0x03, 0x83, 0xbf), 8);
- pen.setCosmetic(true);
- painter->setPen(pen);
- painter->drawLines(lines);
+ // restore transform
+ painter->setTransform(xf);
+}
- QPen linePen(Qt::white, 2);
- linePen.setCosmetic(true);
- painter->setPen(linePen);
- painter->drawLines(lines);
+QRect boundsOfLines(const QVector<QLineF>& lines)
+{
+ QRect r;
+ Q_FOREACH(const QLineF& l, lines) {
+ r = r.united(QRectF(l.p1(), l.p2()).toRect());
+ }
- painter->resetTransform();
- }
+ return r;
+}
+
+void BaseDiagram::paintNavaid(QPainter* painter, const QTransform& t, const FGPositionedRef &pos)
+{
+ if (isNavaidIgnored(pos))
+ return;
+
+ bool drawAsIcon = true;
+ const double minRunwayLengthFt = (16 / m_scale) * SG_METER_TO_FEET;
+ const FGPositioned::Type ty(pos->type());
+ const bool isNDB = (ty == FGPositioned::NDB);
+ QRect iconRect;
+
+ if (ty == FGPositioned::AIRPORT) {
+ FGAirport* apt = static_cast<FGAirport*>(pos.ptr());
+ if (apt->hasHardRunwayOfLengthFt(minRunwayLengthFt)) {
+
+ drawAsIcon = false;
+ painter->setTransform(t);
+ QVector<QLineF> lines = projectAirportRuwaysWithCenter(apt, m_projectionCenter);
+
+ QPen pen(QColor(0x03, 0x83, 0xbf), 8);
+ pen.setCosmetic(true);
+ painter->setPen(pen);
+ painter->drawLines(lines);
+
+ QPen linePen(Qt::white, 2);
+ linePen.setCosmetic(true);
+ painter->setPen(linePen);
+ painter->drawLines(lines);
+
+ painter->resetTransform();
+
+ iconRect = t.mapRect(boundsOfLines(lines));
}
+ }
+
+ if (drawAsIcon) {
+ QPixmap pm = iconForPositioned(pos);
+ QPointF loc = t.map(project(pos->geod()));
+ iconRect = pm.rect();
+ iconRect.moveCenter(loc.toPoint());
+ painter->drawPixmap(iconRect, pm);
+ }
+
+ // compute label text so we can measure it
+ QString label;
+ if (FGAirport::isAirportType(pos.ptr())) {
+ label = QString::fromStdString(pos->name());
+ label = fixNavaidName(label);
+ } else {
+ label = QString::fromStdString(pos->ident());
+ }
+
+ if (ty == FGPositioned::NDB) {
+ FGNavRecord* nav = static_cast<FGNavRecord*>(pos.ptr());
+ label.append("\n").append(QString::number(nav->get_freq() / 100));
+ } else if (ty == FGPositioned::VOR) {
+ FGNavRecord* nav = static_cast<FGNavRecord*>(pos.ptr());
+ label.append("\n").append(QString::number(nav->get_freq() / 100.0, 'f', 1));
+ }
+
+ QRect textBounds = painter->boundingRect(QRect(0, 0, 100, 100),
+ Qt::TextWordWrap, label);
+ int textFlags;
+ textBounds = rectAndFlagsForLabel(pos->guid(), iconRect,
+ textBounds.size(),
+ textFlags);
+
+ painter->setPen(isNDB ? QColor(0x9b, 0x5d, 0xa2) : QColor(0x03, 0x83, 0xbf));
+ painter->drawText(textBounds, textFlags, label);
+}
+
+bool BaseDiagram::isNavaidIgnored(const FGPositionedRef &pos) const
+{
+ return m_ignored.contains(pos);
+}
+
+bool BaseDiagram::isLabelRectAvailable(const QRect &r) const
+{
+ Q_FOREACH(const QRect& lr, m_labelRects) {
+ if (lr.intersects(r))
+ return false;
+ }
+
+ return true;
+}
+
+int BaseDiagram::textFlagsForLabelPosition(LabelPosition pos)
+{
+#if 0
+ switch (pos) {
+ case LABEL_RIGHT: return Qt::AlignLeft | Qt::AlignVCenter;
+ case LABEL_ABOVE: return Qt::AlignHCenter | Qt::A
+ }
+#endif
+ return 0;
+}
- if (drawAsIcon) {
- QPixmap pm = iconForPositioned(*it);
- QPointF loc = xf.map(project((*it)->geod()));
- loc -= QPointF(pm.width() >> 1, pm.height() >> 1);
- painter->drawPixmap(loc, pm);
+QRect BaseDiagram::rectAndFlagsForLabel(PositionedID guid, const QRect& item,
+ const QSize &bounds,
+ int& flags) const
+{
+ m_labelRects.append(item);
+ int pos = m_labelPositions.value(guid, LABEL_RIGHT);
+ bool firstAttempt = true;
+ flags = Qt::TextWordWrap;
+
+ while (pos < LAST_POSITION) {
+ QRect r = labelPositioned(item, bounds, static_cast<LabelPosition>(pos));
+ if (isLabelRectAvailable(r)) {
+ m_labelRects.append(r);
+ m_labelPositions[guid] = static_cast<LabelPosition>(pos);
+ flags |= textFlagsForLabelPosition(static_cast<LabelPosition>(pos));
+ return r;
+ } else if (firstAttempt && (pos != LABEL_RIGHT)) {
+ pos = LABEL_RIGHT;
+ } else {
+ ++pos;
}
+
+ firstAttempt = false;
}
- // restore transform
- painter->setTransform(xf);
+ return QRect(item.x(), item.y(), bounds.width(), bounds.height());
+}
+
+QRect BaseDiagram::labelPositioned(const QRect& itemRect,
+ const QSize& bounds,
+ LabelPosition lp) const
+{
+ const int SHORT_MARGIN = 4;
+ const int DIAGONAL_MARGIN = 12;
+
+ QPoint topLeft = itemRect.topLeft();
+
+ switch (lp) {
+ // cardinal compass points are short (close in)
+ case LABEL_RIGHT:
+ topLeft = QPoint(itemRect.right() + SHORT_MARGIN,
+ itemRect.center().y() - bounds.height() / 2);
+ break;
+ case LABEL_ABOVE:
+ topLeft = QPoint(itemRect.center().x() - (bounds.width() / 2),
+ itemRect.top() - (SHORT_MARGIN + bounds.height()));
+ break;
+ case LABEL_BELOW:
+ topLeft = QPoint(itemRect.center().x() - (bounds.width() / 2),
+ itemRect.bottom() + SHORT_MARGIN);
+ break;
+ case LABEL_LEFT:
+ topLeft = QPoint(itemRect.left() - (SHORT_MARGIN + bounds.width()),
+ itemRect.center().y() - bounds.height() / 2);
+ break;
+
+ // first diagonals are further out (to hopefully have a better chance
+ // of finding clear space
+
+ case LABEL_NE:
+ topLeft = QPoint(itemRect.right() + DIAGONAL_MARGIN,
+ itemRect.top() - (DIAGONAL_MARGIN + bounds.height()));
+ break;
+
+ case LABEL_NW:
+ topLeft = QPoint(itemRect.left() - (DIAGONAL_MARGIN + bounds.width()),
+ itemRect.top() - (DIAGONAL_MARGIN + bounds.height()));
+ break;
+
+ case LABEL_SE:
+ topLeft = QPoint(itemRect.right() + DIAGONAL_MARGIN,
+ itemRect.bottom() + DIAGONAL_MARGIN);
+ break;
+
+ case LABEL_SW:
+ topLeft = QPoint(itemRect.left() - (DIAGONAL_MARGIN + bounds.width()),
+ itemRect.bottom() + DIAGONAL_MARGIN);
+ break;
+ default:
+ qWarning() << Q_FUNC_INFO << "Implement me";
+
+ }
+
+ return QRect(topLeft, bounds);
}
void BaseDiagram::mousePressEvent(QMouseEvent *me)
m_wheelAngleDeltaAccumulator += delta;
if (m_wheelAngleDeltaAccumulator > 120) {
m_wheelAngleDeltaAccumulator = 0;
- m_scale *= 2.0;
+
+ m_scale *= 1.5;
+
} else if (m_wheelAngleDeltaAccumulator < -120) {
m_wheelAngleDeltaAccumulator = 0;
- m_scale *= 0.5;
+
+ m_scale *= 0.75;
}
+ SG_CLAMP_RANGE(m_scale, MINIMUM_SCALE, 1.0);
update();
}
y = k * ( cos(ref_lat) * sin(lat) - sin(ref_lat) * cos(lat) * cos(lonDiff) );
}
+ // flip for top-left origin
return QPointF(x, -y) * r;
}
return center;
}
- double x = xy.x(), y = xy.y();
+ // invert y to balance the equivalent in project()
+ double x = xy.x(),
+ y = -xy.y();
lat = asin( cos(c) * sin(ref_lat) + (y * sin(c) * cos(ref_lat)) / rho);
if (ref_lat == (90 * SG_DEGREES_TO_RADIANS)) // north pole
return project(geod, m_projectionCenter);
}
-QPixmap BaseDiagram::iconForPositioned(const FGPositionedRef& pos)
+QPixmap BaseDiagram::iconForPositioned(const FGPositionedRef& pos,
+ const IconOptions& options)
{
// if airport type, check towered or untowered
+ bool small = options.testFlag(SmallIcons);
bool isTowered = false;
if (FGAirport::isAirportType(pos)) {
switch (pos->type()) {
case FGPositioned::VOR:
- // check for VORTAC
+ if (static_cast<FGNavRecord*>(pos.ptr())->isVORTAC())
+ return QPixmap(":/vortac-icon");
if (static_cast<FGNavRecord*>(pos.ptr())->hasDME())
return QPixmap(":/vor-dme-icon");
return QPixmap(":/vor-icon");
case FGPositioned::AIRPORT:
- return iconForAirport(static_cast<FGAirport*>(pos.ptr()));
+ return iconForAirport(static_cast<FGAirport*>(pos.ptr()), options);
case FGPositioned::HELIPORT:
return QPixmap(":/heliport-icon");
case FGPositioned::SEAPORT:
return QPixmap(isTowered ? ":/seaport-tower-icon" : ":/seaport-icon");
case FGPositioned::NDB:
- return QPixmap(":/ndb-icon");
+ return QPixmap(small ? ":/ndb-small-icon" : ":/ndb-icon");
case FGPositioned::FIX:
return QPixmap(":/waypoint-icon");
return QPixmap();
}
-QPixmap BaseDiagram::iconForAirport(FGAirport* apt)
+QPixmap BaseDiagram::iconForAirport(FGAirport* apt, const IconOptions& options)
{
+ if (apt->isClosed()) {
+ return QPixmap(":/airport-closed-icon");
+ }
+
if (!apt->hasHardRunwayOfLengthFt(1500)) {
return QPixmap(apt->hasTower() ? ":/airport-tower-icon" : ":/airport-icon");
}
- if (apt->hasHardRunwayOfLengthFt(8500)) {
+ if (options.testFlag(LargeAirportPlans) && apt->hasHardRunwayOfLengthFt(8500)) {
QPixmap result(32, 32);
result.fill(Qt::transparent);
{
return r;
}
+void BaseDiagram::setAircraftType(LauncherAircraftType type)
+{
+ m_aircraftType = type;
+ update();
+}
+
QVector<QLineF> BaseDiagram::projectAirportRuwaysIntoRect(FGAirportRef apt, const QRectF &bounds)
{
QVector<QLineF> r = projectAirportRuwaysWithCenter(apt, apt->geod());