void GlidePolarTest::TestBasic() { polar.Update(); ok1(equals(polar.polar.a, polar.ideal_polar.a)); ok1(equals(polar.polar.b, polar.ideal_polar.b)); ok1(equals(polar.polar.c, polar.ideal_polar.c)); ok1(equals(polar.SinkRate(Units::ToSysUnit(fixed(80), Unit::KILOMETER_PER_HOUR)), 0.606)); ok1(equals(polar.SinkRate(Units::ToSysUnit(fixed(120), Unit::KILOMETER_PER_HOUR)), 0.99)); ok1(equals(polar.SinkRate(Units::ToSysUnit(fixed(160), Unit::KILOMETER_PER_HOUR)), 1.918)); ok1(equals(polar.GetSMax(), polar.SinkRate(polar.GetVMax()))); ok1(equals(polar.GetVMin(), 19.934640523)); ok1(equals(polar.GetSMin(), polar.SinkRate(polar.GetVMin()))); ok1(equals(polar.GetVTakeoff(), polar.GetVMin() / 2)); ok1(equals(polar.GetVBestLD(), 25.830434162)); ok1(equals(polar.GetSBestLD(), polar.SinkRate(polar.GetVBestLD()))); ok1(equals(polar.GetBestLD(), polar.GetVBestLD() / polar.GetSBestLD())); ok1(equals(polar.GetTotalMass(), 318)); ok1(equals(polar.GetWingLoading(), 32.448979592)); ok1(equals(polar.GetBallast(), 0)); ok1(equals(polar.GetBallastLitres(), 0)); ok1(polar.IsBallastable()); ok1(!polar.HasBallast()); }
/** * Specialisation based on simplified theoretical MC cross-country * speeds. Assumes cruise at best LD (ignoring wind) for current MC * setting, climb rate at MC setting, with direct descent possible * at sink rate of cruise. */ explicit AirspaceAircraftPerformance(const GlidePolar &polar) :vertical_tolerance(0), cruise_speed(polar.GetVBestLD()), cruise_descent(polar.GetSBestLD()), descent_rate(polar.GetSMax()), climb_rate(polar.GetMC()), max_speed(polar.GetVMax()) { assert(polar.IsValid()); }
/** * Specialisation of AirspaceAircraftPerformance for tasks where * part of the path is in cruise, part in final glide. This is * intended to be used temporarily only. * * This simplifies the path by assuming flight is constant altitude * or descent to the task point elevation. */ AirspaceAircraftPerformance(const GlidePolar &polar, const GlideResult &solution) :vertical_tolerance(0.001), cruise_speed(positive(solution.time_elapsed) ? solution.vector.distance / solution.time_elapsed : fixed(1)), cruise_descent(positive(solution.time_elapsed) ? (positive(solution.height_climb) ? -solution.height_climb : solution.height_glide) / solution.time_elapsed : fixed(0)), descent_rate(polar.GetSBestLD()), climb_rate(positive(solution.time_elapsed) && positive(solution.height_climb) ? polar.GetMC() : fixed(0)), max_speed(cruise_speed) { assert(polar.IsValid()); assert(solution.IsOk()); assert(solution.IsAchievable()); }
static void Test(const fixed distance, const fixed altitude, const SpeedVector wind) { const GeoVector vector(distance, Angle::Zero()); const GlideState state(vector, fixed(2000), fixed(2000) + altitude, wind); const GlideResult result = MacCready::Solve(glide_settings, glide_polar, state); const fixed ld_ground = glide_polar.GetLDOverGround(vector.bearing, wind); const fixed mc = glide_polar.GetMC(); const fixed v_climb_progress = mc * ld_ground - state.head_wind; const fixed initial_glide_distance = state.altitude_difference * ld_ground; if (initial_glide_distance >= distance || (!positive(mc) && !positive(v_climb_progress))) { /* reachable by pure glide */ ok1(result.validity == GlideResult::Validity::OK); const fixed best_speed = glide_polar.GetBestGlideRatioSpeed(state.head_wind); const fixed best_sink = glide_polar.SinkRate(best_speed); const fixed ld_ground2 = positive(mc) ? ld_ground : (best_speed - state.head_wind) / best_sink; const fixed height_glide = distance / ld_ground2; const fixed height_climb = fixed(0); const fixed altitude_difference = altitude - height_glide; ok1(equals(result.head_wind, wind.norm)); ok1(equals(result.vector.distance, distance)); ok1(equals(result.height_climb, height_climb)); ok1(equals(result.height_glide, height_glide)); ok1(equals(result.altitude_difference, altitude_difference)); return; } if (!positive(v_climb_progress)) { /* excessive wind */ ok1(result.validity == GlideResult::Validity::WIND_EXCESSIVE); return; } /* const fixed drifted_distance = (distance - initial_glide_distance) * state.head_wind / v_climb_progress; */ const fixed drifted_height_climb = (distance - initial_glide_distance) * mc / v_climb_progress; const fixed drifted_height_glide = drifted_height_climb + state.altitude_difference; const fixed height_glide = drifted_height_glide; const fixed altitude_difference = altitude - height_glide; const fixed height_climb = drifted_height_climb; const fixed time_climb = height_climb / mc; const fixed time_glide = height_glide / glide_polar.GetSBestLD(); const fixed time_elapsed = time_climb + time_glide; /* more tolerance with strong wind because this unit test doesn't optimise pure glide */ const int accuracy = positive(altitude) && positive(wind.norm) ? (wind.norm > fixed(5) ? 5 : 10) : ACCURACY; ok1(result.validity == GlideResult::Validity::OK); ok1(equals(result.head_wind, wind.norm)); ok1(equals(result.vector.distance, distance)); ok1(equals(result.height_climb, height_climb, accuracy)); ok1(equals(result.height_glide, height_glide, accuracy)); ok1(equals(result.altitude_difference, altitude_difference, accuracy)); ok1(equals(result.time_elapsed, time_elapsed, accuracy)); }
void RenderVarioHistogram(Canvas &canvas, const PixelRect rc, const ChartLook &chart_look, const FlightStatistics &fs, const GlidePolar &glide_polar) { ChartRenderer chart(chart_look, canvas, rc); const auto acc = std::max(fs.vario_cruise_histogram.GetAccumulator(), fs.vario_circling_histogram.GetAccumulator()); if (!acc) { chart.DrawNoData(); return; } const auto scale = std::max(fs.vario_cruise_histogram.GetMaxY(), fs.vario_circling_histogram.GetMaxY()) * 1.2; chart.ScaleXFromValue(0); chart.ScaleXFromValue(scale); const auto s = -glide_polar.GetSBestLD(); const auto mc = glide_polar.GetMC(); chart.ScaleYFromValue(fs.vario_cruise_histogram.GetMinX()); chart.ScaleYFromValue(fs.vario_cruise_histogram.GetMaxX()); chart.ScaleYFromValue(s); chart.ScaleYFromValue(mc); // draw red area at higher than cruise sink rate, blue area above mc { PixelRect rc_upper = chart.GetChartRect(); rc_upper.bottom = chart.ScreenY(mc); DrawVerticalGradient(canvas, rc_upper, chart_look.color_positive, COLOR_WHITE, COLOR_WHITE); } { PixelRect rc_lower = chart.GetChartRect(); rc_lower.top = chart.ScreenY(s); DrawVerticalGradient(canvas, rc_lower, COLOR_WHITE, chart_look.color_negative, COLOR_WHITE); } canvas.SelectNullPen(); canvas.Select(chart_look.black_brush); chart.DrawFilledLineGraph(fs.vario_circling_histogram, true); canvas.Select(chart_look.blank_brush); chart.DrawFilledLineGraph(fs.vario_cruise_histogram, true); // draw these after shaded regions, so they overlay chart.DrawLineGraph(fs.vario_cruise_histogram, ChartLook::STYLE_GREEN, true); chart.DrawLineGraph(fs.vario_circling_histogram, ChartLook::STYLE_RED, true); // draw current MC setting chart.DrawLine(0, mc, scale, mc, ChartLook::STYLE_REDTHICKDASH); chart.DrawLine(0, s, scale, s, ChartLook::STYLE_BLUETHINDASH); // draw labels and other overlays chart.DrawYGrid(Units::ToSysVSpeed(1), 1, ChartRenderer::UnitFormat::NUMERIC); const double tref = chart.GetXMin()*0.1+chart.GetXMax()*0.9; chart.DrawLabel(_T("MC"), tref, mc); chart.DrawLabel(_T("S cruise"), tref, s); chart.DrawYLabel(_T("w"), Units::GetVerticalSpeedName()); }