Skip to content

Commit

Permalink
Added display of required (by approaches) or calculated vertical desc…
Browse files Browse the repository at this point in the history
…ent flight path angle to elevation profile map and tooltip.

Vertical profile calculation now considers vertical path requirements.
Showing vertical path and required descent rate in aircraft progress in descent and approach phase.
Optimizations using QStringBuilder for all information and progress displays.
#803
  • Loading branch information
albar965 committed Oct 22, 2021
1 parent 638f808 commit ee651cf
Show file tree
Hide file tree
Showing 18 changed files with 501 additions and 278 deletions.
16 changes: 0 additions & 16 deletions src/common/formatter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,22 +99,6 @@ QString formatMinutesHoursDaysLong(double time)
return retval;
}

QString formatFloatUnit(float value, const QString& unit, int precision)
{
if(unit.isEmpty())
return QString(QObject::tr("%L1")).arg(QLocale().toString(value, 'f', precision));
else
return QString(QObject::tr("%L1 %2")).arg(QLocale().toString(value, 'f', precision)).arg(unit);
}

QString formatDoubleUnit(double value, const QString& unit, int precision)
{
if(unit.isEmpty())
return QString(QObject::tr("%L1")).arg(QLocale().toString(value, 'f', precision));
else
return QString(QObject::tr("%L1 %2")).arg(QLocale().toString(value, 'f', precision)).arg(unit);
}

QString formatDate(int timeT)
{
QDateTime dateTime;
Expand Down
4 changes: 0 additions & 4 deletions src/common/formatter.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,6 @@ QString formatMinutesHoursDays(double time);
/* Format a decimal time in hours to X d Y h Z m format */
QString formatMinutesHoursDaysLong(double time);

/* Format a value to a x:xx nm string where nm is a unit */
QString formatDoubleUnit(double value, const QString& unit = QString(), int precision = 0);
QString formatFloatUnit(float value, const QString& unit = QString(), int precision = 0);

/* Format elapsed time to minutes and seconds */
QString formatElapsed(const QElapsedTimer& timer);

Expand Down
283 changes: 148 additions & 135 deletions src/common/htmlinfobuilder.cpp

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions src/common/maptypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,32 @@ struct MapRunwayEnd
QString name, leftVasiType, rightVasiType, pattern;
float heading, /* degree true */
leftVasiPitch = 0.f, rightVasiPitch = 0.f;

bool hasAnyVasi() const
{
return hasLeftVasi() || hasRightVasi();
}

bool hasLeftVasi() const
{
return leftVasiPitch > 0.f;
}

bool hasRightVasi() const
{
return rightVasiPitch > 0.f;
}

QString leftVasiTypeStr() const
{
return leftVasiType == "UNKN" ? QString() : leftVasiType;
}

QString rightVasiTypeStr() const
{
return rightVasiType == "UNKN" ? QString() : rightVasiType;
}

bool secondary;
bool navdata; /* true if source is third party nav database, false if source is simulator data */
};
Expand Down
41 changes: 23 additions & 18 deletions src/common/proctypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -332,36 +332,41 @@ QString restrictionText(const MapProcedureLeg& procedureLeg)
if(procedureLeg.speedRestriction.isValid())
restrictions.append(proc::speedRestrictionTextShort(procedureLeg.speedRestriction));

if(procedureLeg.verticalAngle < map::INVALID_ANGLE_VALUE)
restrictions.append(QObject::tr("%L1°").arg(procedureLeg.verticalAngle, 0, 'g', 3));
if(procedureLeg.verticalAngle < -0.1f)
restrictions.append(QObject::tr("%L1°").arg(std::abs(procedureLeg.verticalAngle), 0, 'g', 3));

return restrictions.join(QObject::tr("/"));
}

QString altRestrictionText(const MapAltRestriction& restriction)
{
switch(restriction.descriptor)
if(restriction.verticalAngleAlt < map::INVALID_ALTITUDE_VALUE)
return QObject::tr("At %1 (path)").arg(Unit::altFeet(restriction.verticalAngleAlt));
else
{
case proc::MapAltRestriction::ILS_AT:
case proc::MapAltRestriction::ILS_AT_OR_ABOVE:
return QObject::tr("ILS GS %1").arg(Unit::altFeet(restriction.alt1));
switch(restriction.descriptor)
{
case proc::MapAltRestriction::ILS_AT:
case proc::MapAltRestriction::ILS_AT_OR_ABOVE:
return QObject::tr("ILS GS %1").arg(Unit::altFeet(restriction.alt1));

case proc::MapAltRestriction::NONE:
return QString();
case proc::MapAltRestriction::NONE:
return QString();

case proc::MapAltRestriction::AT:
return QObject::tr("At %1").arg(Unit::altFeet(restriction.alt1));
case proc::MapAltRestriction::AT:
return QObject::tr("At %1").arg(Unit::altFeet(restriction.alt1));

case proc::MapAltRestriction::AT_OR_ABOVE:
return QObject::tr("At or above %1").arg(Unit::altFeet(restriction.alt1));
case proc::MapAltRestriction::AT_OR_ABOVE:
return QObject::tr("At or above %1").arg(Unit::altFeet(restriction.alt1));

case proc::MapAltRestriction::AT_OR_BELOW:
return QObject::tr("At or below %1").arg(Unit::altFeet(restriction.alt1));
case proc::MapAltRestriction::AT_OR_BELOW:
return QObject::tr("At or below %1").arg(Unit::altFeet(restriction.alt1));

case proc::MapAltRestriction::BETWEEN:
return QObject::tr("At or above %1 and at or below %2").
arg(Unit::altFeet(restriction.alt2)).
arg(Unit::altFeet(restriction.alt1));
case proc::MapAltRestriction::BETWEEN:
return QObject::tr("At or above %1 and at or below %2").
arg(Unit::altFeet(restriction.alt2)).
arg(Unit::altFeet(restriction.alt1));
}
}
return QString();
}
Expand Down
3 changes: 2 additions & 1 deletion src/common/proctypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ struct MapAltRestriction
};

Descriptor descriptor = NONE;
float alt1, alt2;
float alt1, alt2,
verticalAngleAlt = map::INVALID_ALTITUDE_VALUE; /* Forced since calculated from vertical angle */

/* Indicator used to force lowest altitude on final FAF and FACF to avoid arriving above glide slope or VASI */
bool forceFinal = false;
Expand Down
4 changes: 2 additions & 2 deletions src/perf/aircraftperfcontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ bool AircraftPerfController::isWindManual() const

float AircraftPerfController::cruiseAlt()
{
float alt = NavApp::getAltitudeLegs().getCruiseAltitide();
float alt = NavApp::getAltitudeLegs().getCruiseAltitude();

if(atools::almostEqual(alt, 0.f) || alt > map::INVALID_ALTITUDE_VALUE / 2.f)
alt = NavApp::getRouteCruiseAltFtWidget();
Expand Down Expand Up @@ -1065,7 +1065,7 @@ void AircraftPerfController::fuelReport(atools::util::HtmlBuilder& html, bool pr
html.row2(tr("Average Ground Speed:"), Unit::speedKts(altLegs.getAverageGroundSpeed()), flags);
html.row2(tr("True Airspeed at Cruise:"), Unit::speedKts(perf->getCruiseSpeed()), flags);

float mach = atools::geo::tasToMachFromAlt(altLegs.getCruiseAltitide(),
float mach = atools::geo::tasToMachFromAlt(altLegs.getCruiseAltitude(),
static_cast<float>(perf->getCruiseSpeed()));
if(mach > 0.4f)
html.row2(tr("Mach at cruise:"), QLocale().toString(mach, 'f', 2), flags);
Expand Down
85 changes: 71 additions & 14 deletions src/profile/profilewidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ void ProfileWidget::paintIls(QPainter& painter, const Route& route)

// Calculate altitude at end of feather
// tan a = GK / AK; // tan a * AK = GK;
float ydiff1 = std::tan(atools::geo::toRadians(ils.slope)) * featherLen;
float ydiff1 = atools::geo::tanDeg(ils.slope) * featherLen;

// Calculate screen points for end of feather
int y2 = altitudeY(altitudeLegs.getDestinationAltitude() + ydiff1);
Expand Down Expand Up @@ -541,13 +541,11 @@ void ProfileWidget::paintVasi(QPainter& painter, const Route& route)
// Collect left and right VASI
QVector<std::pair<float, QString> > vasiList;

if(runwayEnd.rightVasiPitch > 0.f)
vasiList.append(std::make_pair(runwayEnd.rightVasiPitch,
runwayEnd.rightVasiType == "UNKN" ? QString() : runwayEnd.rightVasiType));
if(runwayEnd.hasRightVasi())
vasiList.append(std::make_pair(runwayEnd.rightVasiPitch, runwayEnd.rightVasiTypeStr()));

if(runwayEnd.leftVasiPitch > 0.f)
vasiList.append(std::make_pair(runwayEnd.leftVasiPitch,
runwayEnd.leftVasiType == "UNKN" ? QString() : runwayEnd.leftVasiType));
if(runwayEnd.hasLeftVasi())
vasiList.append(std::make_pair(runwayEnd.leftVasiPitch, runwayEnd.leftVasiTypeStr()));

if(vasiList.isEmpty())
return;
Expand All @@ -574,7 +572,7 @@ void ProfileWidget::paintVasi(QPainter& painter, const Route& route)

// Calculate altitude at end of guide
// tan a = GK / AK; // tan a * AK = GK;
float ydiff1 = std::tan(atools::geo::toRadians(vasi.first)) * featherLen;
float ydiff1 = atools::geo::tanDeg(vasi.first) * featherLen;

// Calculate screen points for end of guide
int yUpper = altitudeY(altitudeLegs.getDestinationAltitude() + ydiff1);
Expand Down Expand Up @@ -864,7 +862,7 @@ void ProfileWidget::paintEvent(QPaintEvent *)
}

// Draw ILS or VASI guidance ============================
mapcolors::scaleFont(&painter, 0.9f);
mapcolors::scaleFont(&painter, 0.95f);

if(NavApp::getMainUi()->actionProfileShowVasi->isChecked())
paintVasi(painter, route);
Expand All @@ -889,19 +887,66 @@ void ProfileWidget::paintEvent(QPaintEvent *)
std::min(passedRouteLeg + 1, waypointX.size()) : 0;
}

setFont(optData.getMapFont());
mapcolors::scaleFont(&painter, optData.getDisplayTextSizeFlightplan() / 100.f, &painter.font());

if(NavApp::getMapWidgetGui()->getShownMapFeaturesDisplay().testFlag(map::FLIGHTPLAN))
{
// Draw background line ======================================================
float flightplanOutlineWidth = (optData.getDisplayThicknessFlightplan() / 100.f) * 7;
float flightplanWidth = (optData.getDisplayThicknessFlightplan() / 100.f) * 4;
painter.setPen(QPen(mapcolors::routeOutlineColor, flightplanOutlineWidth, Qt::SolidLine, Qt::RoundCap,
Qt::RoundJoin));
painter.setPen(QPen(mapcolors::routeOutlineColor, flightplanOutlineWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));

for(int i = passedRouteLeg; i < waypointX.size(); i++)
{
const proc::MapProcedureLeg& leg = route.value(i).getProcedureLeg();
if(i > 0 && !leg.isCircleToLand() && !leg.isStraightIn() && !leg.isVectors() && !leg.isManual())
{
// Draw line ========================================
painter.drawPolyline(altLegs.at(i));
}

// Draw flight path angle label for descent ==========================
if(altitudeLegs.isValidProfile())
{
const RouteAltitudeLeg& altLeg = altitudeLegs.value(i);
const QPolygonF& geometry = altLeg.getGeometry();
QVector<float> angles;
bool proc = false;
if(altLeg.isVerticalProcAngleValid() && geometry.size() == 2)
{
// A required vertical angle given by procedure
angles.append(altLeg.getVerticalProcAngle());
proc = true;
}
else
// Calculated list of angles by aircraft performance
angles = altLeg.getVerticalGeoAngles();

// Highlight procedure given required angles
painter.setBackgroundMode(proc ? Qt::OpaqueMode : Qt::TransparentMode);
painter.setBackground(proc ? mapcolors::profileAltRestrictionFill : Qt::transparent);

// Iterate over geometry for this route leg =========================
for(int j = 1; j < geometry.size(); j++)
{
float pathAngle = angles.value(j - 1, map::INVALID_ANGLE_VALUE);
if(pathAngle < -0.5f)
{
QString txt = tr(" %1° ► ").arg(std::abs(pathAngle), 0, 'g', proc ? 3 : 2);
int textW = painter.fontMetrics().horizontalAdvance(txt);
QLineF line(toScreen(geometry.at(j - 1)), toScreen(geometry.at(j)));
if(line.length() > textW)
{
painter.translate(line.center());
painter.rotate(atools::geo::angleFromQt(line.angle()) - 90.); // Rotate for display angle
painter.drawText(-textW / 2, atools::roundToInt(painter.fontMetrics().ascent() + flightplanOutlineWidth / 2.f), txt);
painter.resetTransform();
}
}
}
} // if(altitudeLegs.isValidProfile())
} // if(i > 0 && !leg.isCircleToLand() && !leg.isStraightIn() && !leg.isVectors() && !leg.isManual())
} // for(int i = passedRouteLeg; i < waypointX.size(); i++)

// Draw passed ======================================================
painter.setPen(QPen(optData.getFlightplanPassedSegmentColor(), flightplanWidth,
Expand Down Expand Up @@ -970,6 +1015,7 @@ void ProfileWidget::paintEvent(QPaintEvent *)
int airportSize = atools::roundToInt((optData.getDisplaySymbolSizeAirport() * sizeScaleSymbol / 100.) * 10.);

painter.setBackgroundMode(Qt::TransparentMode);
setFont(optData.getMapFont());
mapcolors::scaleFont(&painter, optData.getDisplayTextSizeFlightplan() / 100.f, &painter.font());

// Draw the most unimportant symbols and texts first - userpoints, invalid and procedure ============================
Expand Down Expand Up @@ -1106,7 +1152,7 @@ void ProfileWidget::paintEvent(QPaintEvent *)
symPainter.textBox(&painter, texts, color, symPt.x() + 5,
std::min(symPt.y() + 14, h), textatt::ROUTE_BG_COLOR, 255);
}
}
} // for(int routeIndex : indexes)

// Draw the most important airport symbols on top ============================================
waypointIndex = waypointX.size();
Expand Down Expand Up @@ -1213,7 +1259,7 @@ void ProfileWidget::paintEvent(QPaintEvent *)
}
}
}
} // if(NavApp::getMapWidget()->getShownMapFeatures() & map::FLIGHTPLAN)
} // if(!route.isFlightplanEmpty())

// Departure altitude label =========================================================
QColor labelColor = mapcolors::profileLabelColor;
Expand Down Expand Up @@ -1831,6 +1877,17 @@ void ProfileWidget::buildTooltip(int x, bool force)
}
}

// Vertical angle ===============================================================
bool required = false;
float verticalAngle = legList->route.getVerticalAngleAtDistance(distanceToGo, &required);

#ifdef DEBUG_INFORMATION_PROFILE
html.br().b(required ? "[required angle" : "[angle").text(tr(" %L1 °]").arg(std::abs(verticalAngle), 0, 'g', 6));
#endif
if(verticalAngle < -0.1f)
html.br().b(required ? tr("Required Flight Path Angle: ") : tr("Flight path angle: ")).
text(tr("%L1 °").arg(std::abs(verticalAngle), 0, 'g', required ? 3 : 2));

// Ground ===============================================================
QStringList groundText;
if(groundElevation < map::INVALID_ALTITUDE_VALUE)
Expand Down
11 changes: 7 additions & 4 deletions src/query/mapquery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1162,6 +1162,13 @@ void MapQuery::initQueries()

deInitQueries();

// Check for holding table in nav (Navigraph) database and then in simulator database (X-Plane only)
SqlDatabase *holdingDb = SqlUtil::getDbWithTableAndRows("holding", {dbNav, dbSim});
qDebug() << Q_FUNC_INFO << "Holding database" << (holdingDb == nullptr ? "None" : holdingDb->databaseName());

SqlDatabase *msaDb = SqlUtil::getDbWithTableAndRows("airport_msa", {dbNav, dbSim});
qDebug() << Q_FUNC_INFO << "Airport MSA database" << (msaDb == nullptr ? "None" : msaDb->databaseName());

vorByIdentQuery = new SqlQuery(dbNav);
vorByIdentQuery->prepare("select " + vorQueryBase + " from vor where " + whereIdentRegion);

Expand Down Expand Up @@ -1203,10 +1210,6 @@ void MapQuery::initQueries()
ilsByIdQuery = new SqlQuery(dbSim);
ilsByIdQuery->prepare("select " + ilsQueryBase + " from ils where ils_id = :id");

// Check for holding table in nav (Navigraph) database and then in simulator database (X-Plane only)
SqlDatabase *holdingDb = SqlUtil::getDbWithTableAndRows("holding", {dbNav, dbSim});
qDebug() << Q_FUNC_INFO << "Holding database" << (holdingDb == nullptr ? "None" : holdingDb->databaseName());

if(holdingDb != nullptr)
{
holdingByIdQuery = new SqlQuery(holdingDb);
Expand Down
Loading

0 comments on commit ee651cf

Please sign in to comment.