コード例 #1
0
/**
 * Reads the current terrain height
 */
void
GlideComputerAirData::TerrainHeight(const MoreData &basic,
                                    TerrainInfo &calculated)
{
  if (!basic.location_available || terrain == NULL) {
    calculated.terrain_valid = false;
    calculated.terrain_altitude = fixed_zero;
    calculated.altitude_agl_valid = false;
    calculated.altitude_agl = fixed_zero;
    return;
  }

  short Alt = terrain->GetTerrainHeight(basic.location);
  if (RasterBuffer::IsSpecial(Alt)) {
    if (RasterBuffer::IsWater(Alt))
      /* assume water is 0m MSL; that's the best guess */
      Alt = 0;
    else {
      calculated.terrain_valid = false;
      calculated.terrain_altitude = fixed_zero;
      calculated.altitude_agl_valid = false;
      calculated.altitude_agl = fixed_zero;
      return;
    }
  }

  calculated.terrain_valid = true;
  calculated.terrain_altitude = fixed(Alt);

  if (basic.NavAltitudeAvailable()) {
    calculated.altitude_agl = basic.nav_altitude - calculated.terrain_altitude;
    calculated.altitude_agl_valid = true;
  } else
    calculated.altitude_agl_valid = false;
}
コード例 #2
0
ファイル: TraceComputer.cpp プロジェクト: osteocool/XCSoar-1
void
TraceComputer::Update(const ComputerSettings &settings_computer,
                      const MoreData &basic, const DerivedInfo &calculated)
{
  /* time warps are handled by the Trace class */

  if (!basic.time_available || !basic.location_available ||
      !basic.NavAltitudeAvailable() ||
      !calculated.flight.flying)
    return;

  // either olc or basic trace requires trace_full
  if (settings_computer.contest.enable ||
      settings_computer.task.enable_trace) {
    const TracePoint point(basic);

    mutex.Lock();
    full.push_back(point);
    mutex.Unlock();

    // only olc requires trace_sprint
    if (settings_computer.contest.enable)
      sprint.push_back(point);
  }
}
コード例 #3
0
ファイル: StatsComputer.cpp プロジェクト: kwtskran/XCSoar
/**
 * Logs GPS fixes for stats
 * @return True if valid fix (fix distance <= 200m), False otherwise
 */
bool
StatsComputer::DoLogging(const MoreData &basic,
                         const DerivedInfo &calculated)
{
    /// @todo consider putting this sanity check inside Parser
    bool location_jump = basic.location_available && last_location.IsValid() &&
                         basic.location.DistanceS(last_location) > 200;

    last_location = basic.location_available
                    ? basic.location : GeoPoint::Invalid();

    if (location_jump || !basic.location_available)
        // prevent bad fixes from being logged or added to OLC store
        return false;

    if (calculated.flight.flying &&
            stats_clock.CheckAdvance(basic.time, PERIOD)) {
        flightstats.AddAltitudeTerrain(calculated.flight.flight_time,
                                       calculated.terrain_altitude);

        if (basic.NavAltitudeAvailable())
            flightstats.AddAltitude(calculated.flight.flight_time,
                                    basic.nav_altitude);

        if (calculated.task_stats.IsPirkerSpeedAvailable())
            flightstats.AddTaskSpeed(calculated.flight.flight_time,
                                     calculated.task_stats.get_pirker_speed());
    }

    return true;
}
コード例 #4
0
ファイル: NearestAirspace.cpp プロジェクト: Advi42/XCSoar
gcc_pure
NearestAirspace
NearestAirspace::FindHorizontal(const MoreData &basic,
                                const ProtectedAirspaceWarningManager &airspace_warnings,
                                const Airspaces &airspace_database)
{
  if (!basic.location_available)
    /* can't check for airspaces without a GPS fix */
    return NearestAirspace();

  /* find the nearest airspace */
  //consider only active airspaces
  const auto outside_and_active =
    MakeAndPredicate(ActiveAirspacePredicate(&airspace_warnings),
                     OutsideAirspacePredicate(AGeoPoint(basic.location, 0)));

  //if altitude is available, filter airspaces in same height as airplane
  if (basic.NavAltitudeAvailable()) {
    /* check altitude; hard-coded margin of 50m (for now) */
    const auto outside_and_active_and_height =
      MakeAndPredicate(outside_and_active,
                       AirspacePredicateHeightRange(basic.nav_altitude - 50,
                                                    basic.nav_altitude + 50));
    const auto predicate = WrapAirspacePredicate(outside_and_active_and_height);
    return ::FindHorizontal(basic.location, airspace_database, predicate);
  } else {
    /* only filter outside and active */
    const auto predicate = WrapAirspacePredicate(outside_and_active);
    return ::FindHorizontal(basic.location, airspace_database, predicate);
  }
}
コード例 #5
0
ファイル: Airspace.cpp プロジェクト: damianob/xcsoar
 HorizontalAirspaceCondition(const MoreData &basic,
                             const DerivedInfo &calculated)
   :location(basic.location),
    altitude_available(basic.NavAltitudeAvailable())
 {
   if (altitude_available) {
     altitude.altitude = basic.nav_altitude;
     altitude.altitude_agl = calculated.altitude_agl;
   }
 }
コード例 #6
0
ファイル: Aircraft.cpp プロジェクト: Advi42/XCSoar
const AircraftState
ToAircraftState(const MoreData &info, const DerivedInfo &calculated)
{
  AircraftState aircraft;

  /* SPEED_STATE */
  aircraft.ground_speed = info.ground_speed;
  aircraft.true_airspeed = info.true_airspeed;

  /* ALTITUDE_STATE */
  aircraft.altitude = info.NavAltitudeAvailable()
    ? info.nav_altitude
    : 0.;

  aircraft.working_band_fraction = calculated.common_stats.height_fraction_working;

  aircraft.altitude_agl =
    info.NavAltitudeAvailable() && calculated.terrain_valid
    ? calculated.altitude_agl
    : 0.;

  /* VARIO_INFO */
  aircraft.vario = info.brutto_vario;
  aircraft.netto_vario = info.netto_vario;

  /* AIRCRAFT_STATE */
  aircraft.time = info.time_available ? info.time : -1.;
  aircraft.location = info.location_available
    ? info.location
    : GeoPoint::Invalid();
  aircraft.track = info.track;
  aircraft.g_load = info.acceleration.available
    ? info.acceleration.g_load
    : 1.;
  aircraft.wind = calculated.GetWindOrZero();
  aircraft.flying = calculated.flight.flying;

  return aircraft;
}
コード例 #7
0
void
GlideRatioComputer::Compute(const MoreData &basic,
                            const DerivedInfo &calculated,
                            VarioInfo &vario_info,
                            const ComputerSettings &settings)
{
  if (!basic.NavAltitudeAvailable()) {
    Reset();
    vario_info.gr = INVALID_GR;
    vario_info.average_gr = 0;
    return;
  }

  if (!last_location_available) {
    /* need two consecutive valid locations; if the previous one
       wasn't valid, skip this iteration and try the next one */
    Reset();
    vario_info.gr = INVALID_GR;
    vario_info.average_gr = 0;

    last_location = basic.location;
    last_location_available = basic.location_available;
    last_altitude = basic.nav_altitude;
    return;
  }

  if (!basic.location_available.Modified(last_location_available))
    return;

  auto DistanceFlown = basic.location.DistanceS(last_location);

  // Glide ratio over ground
  vario_info.gr =
    UpdateGR(vario_info.gr, DistanceFlown,
             last_altitude - basic.nav_altitude, 0.1);

  if (calculated.flight.flying && !calculated.circling) {
    if (!gr_calculator_initialised) {
      gr_calculator_initialised = true;
      gr_calculator.Initialize(settings);
    }

    gr_calculator.Add((int)DistanceFlown, (int)basic.nav_altitude);
    vario_info.average_gr = gr_calculator.Calculate();
  } else
    gr_calculator_initialised = false;

  last_location = basic.location;
  last_location_available = basic.location_available;
  last_altitude = basic.nav_altitude;
}
コード例 #8
0
ファイル: TrackingGlue.cpp プロジェクト: rkohel/XCSoar
void
TrackingGlue::OnTimer(const MoreData &basic, const DerivedInfo &calculated)
{
#ifdef HAVE_SKYLINES_TRACKING
  skylines.Tick(basic);
#endif

#ifdef HAVE_LIVETRACK24
  if (!settings.livetrack24.enabled)
    /* disabled by configuration */
    /* note that we are allowed to read "settings" without locking the
       mutex, because the background thread never writes to this
       attribute */
    return;

  if (!basic.time_available || !basic.gps.real || !basic.location_available)
    /* can't track without a valid GPS fix */
    return;

  if (!clock.CheckUpdate(settings.interval * 1000))
    /* later */
    return;

  ScopeLock protect(mutex);
  if (IsBusy())
    /* still running, skip this submission */
    return;

  date_time = basic.date_time_utc;
  if (!date_time.IsDatePlausible())
    /* use "today" if the GPS didn't provide a date */
    (BrokenDate &)date_time = BrokenDate::TodayUTC();

  location = basic.location;
  /* XXX use nav_altitude? */
  altitude = basic.NavAltitudeAvailable() && basic.nav_altitude > 0
    ? (unsigned)basic.nav_altitude
    : 0u;
  ground_speed = basic.ground_speed_available
    ? (unsigned)Units::ToUserUnit(basic.ground_speed, Unit::KILOMETER_PER_HOUR)
    : 0u;
  track = basic.track_available
    ? basic.track
    : Angle::Zero();

  last_flying = flying;
  flying = calculated.flight.flying;

  Trigger();
#endif
}
コード例 #9
0
void
GlideComputerAirData::GR(const MoreData &basic, const MoreData &last_basic,
                         const DerivedInfo &calculated, VarioInfo &vario_info)
{
  if (!basic.NavAltitudeAvailable() || !last_basic.NavAltitudeAvailable()) {
    vario_info.ld_vario = INVALID_GR;
    vario_info.gr = INVALID_GR;
    return;
  }

  if (basic.HasTimeRetreatedSince(last_basic)) {
    vario_info.ld_vario = INVALID_GR;
    vario_info.gr = INVALID_GR;
  }

  const bool time_advanced = basic.HasTimeAdvancedSince(last_basic);
  if (time_advanced) {
    fixed DistanceFlown = basic.location.Distance(last_basic.location);

    // Glide ratio over ground
    vario_info.gr =
      UpdateGR(vario_info.gr, DistanceFlown,
               last_basic.nav_altitude - basic.nav_altitude, fixed(0.1));

    if (calculated.flight.flying && !calculated.circling)
      gr_calculator.Add((int)DistanceFlown, (int)basic.nav_altitude);
  }

  // Lift / drag instantaneous from vario, updated every reading..
  if (basic.total_energy_vario_available && basic.airspeed_available &&
      calculated.flight.flying) {
    vario_info.ld_vario =
      UpdateGR(vario_info.ld_vario, basic.indicated_airspeed,
               -basic.total_energy_vario, fixed(0.3));
  } else {
    vario_info.ld_vario = INVALID_GR;
  }
}
コード例 #10
0
ファイル: WarningComputer.cpp プロジェクト: CnZoom/XcSoarPull
void
WarningComputer::Update(const ComputerSettings &settings_computer,
                        const MoreData &basic,
                        const DerivedInfo &calculated,
                        AirspaceWarningsInfo &result)
{
  if (!basic.time_available)
    return;

  const fixed dt = delta_time.Update(basic.time, fixed(1), fixed(20));
  if (negative(dt))
    /* time warp */
    Reset();

  if (!positive(dt))
    return;

  airspaces.SetFlightLevels(settings_computer.pressure);

  AirspaceActivity day(calculated.date_time_local.day_of_week);
  airspaces.SetActivity(day);

  if (!settings_computer.airspace.enable_warnings ||
      !basic.location_available || !basic.NavAltitudeAvailable()) {
    if (initialised) {
      initialised = false;
      protected_manager.Clear();
    }

    return;
  }

  const AircraftState as = ToAircraftState(basic, calculated);
  ProtectedAirspaceWarningManager::ExclusiveLease lease(protected_manager);

  lease->SetConfig(settings_computer.airspace.warnings);

  if (!initialised) {
    initialised = true;
    lease->Reset(as);
  }

  if (lease->Update(as, settings_computer.polar.glide_polar_task,
                    calculated.task_stats,
                    calculated.circling,
                    uround(dt)))
    result.latest.Update(basic.clock);
}
コード例 #11
0
void
GlideComputerAirData::MaxHeightGain(const MoreData &basic,
                                    DerivedInfo &calculated)
{
  if (!basic.NavAltitudeAvailable() || !calculated.flight.flying)
    return;

  if (positive(calculated.min_altitude)) {
    fixed height_gain = basic.nav_altitude - calculated.min_altitude;
    calculated.max_height_gain = max(height_gain, calculated.max_height_gain);
  } else {
    calculated.min_altitude = basic.nav_altitude;
  }

  calculated.min_altitude = min(basic.nav_altitude, calculated.min_altitude);
}
コード例 #12
0
ファイル: RouteComputer.cpp プロジェクト: rjsikarwar/XCSoar
void
RouteComputer::ProcessRoute(const MoreData &basic, DerivedInfo &calculated,
                            const GlideSettings &settings,
                            const RoutePlannerConfig &config,
                            const GlidePolar &glide_polar,
                            const GlidePolar &safety_polar)
{
  if (!basic.location_available || !basic.NavAltitudeAvailable())
    return;

  protected_route_planner.SetPolars(settings, glide_polar, safety_polar,
                                    calculated.GetWindOrZero());

  Reach(basic, calculated, config);
  TerrainWarning(basic, calculated, config);
}
コード例 #13
0
void
ThermalBandComputer::Compute(const MoreData &basic,
                             const DerivedInfo &calculated,
                             ThermalBandInfo &tbi,
                             const ComputerSettings &settings)
{
  if (!basic.NavAltitudeAvailable())
    return;

  const fixed h_safety =
    settings.task.route_planner.safety_height_terrain +
    calculated.GetTerrainBaseFallback();

  tbi.working_band_height = basic.TE_altitude - h_safety;
  if (negative(tbi.working_band_height)) {
    tbi.working_band_fraction = fixed(0);
    return;
  }

  const fixed max_height = tbi.max_thermal_height;
  if (positive(max_height))
    tbi.working_band_fraction = tbi.working_band_height / max_height;
  else
    tbi.working_band_fraction = fixed(1);

  tbi.working_band_ceiling = std::max(max_height + h_safety,
                                      basic.TE_altitude);


  last_vario_available.FixTimeWarp(basic.brutto_vario_available);
  if (basic.brutto_vario_available.Modified(last_vario_available)) {
    last_vario_available = basic.brutto_vario_available;

    // JMW TODO accuracy: Should really work out dt here,
    //           but i'm assuming constant time steps

    if (tbi.max_thermal_height == fixed(0))
      tbi.max_thermal_height = tbi.working_band_height;

    // only do this if in thermal and have been climbing
    if (calculated.circling && calculated.turning &&
        positive(calculated.average))
      tbi.Add(tbi.working_band_height, basic.brutto_vario);
  }
}
コード例 #14
0
void
CirclingComputer::MaxHeightGain(const MoreData &basic,
                                const FlyingState &flight,
                                CirclingInfo &circling_info)
{
  if (!basic.NavAltitudeAvailable() || !flight.flying)
    return;

  if (min_altitude > 0) {
    auto height_gain = basic.nav_altitude - min_altitude;
    circling_info.max_height_gain =
      std::max(height_gain, circling_info.max_height_gain);
  } else {
    min_altitude = basic.nav_altitude;
  }

  min_altitude = std::min(basic.nav_altitude, min_altitude);
}
コード例 #15
0
void
GlideComputerAirData::CruiseLD(const MoreData &basic, DerivedInfo &calculated)
{
  if (!calculated.circling && basic.NavAltitudeAvailable()) {
    if (negative(calculated.cruise_start_time)) {
      calculated.cruise_start_location = basic.location;
      calculated.cruise_start_altitude = basic.nav_altitude;
      calculated.cruise_start_time = basic.time;
    } else {
      fixed DistanceFlown =
        basic.location.Distance(calculated.cruise_start_location);

      calculated.cruise_ld =
          UpdateLD(calculated.cruise_ld, DistanceFlown,
                   calculated.cruise_start_altitude - basic.nav_altitude,
                   fixed_half);
    }
  }
}
コード例 #16
0
ファイル: WaypointRenderer.cpp プロジェクト: Advi42/XCSoar
  void CalculateReachabilityDirect(const MoreData &basic,
                                   const SpeedVector &wind,
                                   const MacCready &mac_cready,
                                   const TaskBehaviour &task_behaviour) {
    assert(basic.location_available);
    assert(basic.NavAltitudeAvailable());

    const auto elevation = waypoint->elevation +
      task_behaviour.safety_height_arrival;
    const GlideState state(GeoVector(basic.location, waypoint->location),
                           elevation, basic.nav_altitude, wind);

    const GlideResult result = mac_cready.SolveStraight(state);
    if (!result.IsOk())
      return;

    reach.direct = result.pure_glide_altitude_difference;
    if (result.pure_glide_altitude_difference > 0)
      reachable = WaypointRenderer::ReachableTerrain;
  }
コード例 #17
0
/**
 * Logs GPS fixes for stats
 * @return True if valid fix (fix distance <= 200m), False otherwise
 */
bool
GlideComputerStats::DoLogging(const MoreData &basic,
                              const NMEAInfo &last_basic,
                              const DerivedInfo &calculated,
                              const LoggerSettings &settings_logger)
{
  /// @todo consider putting this sanity check inside Parser
  if (basic.location.Distance(last_basic.location) > fixed(200))
    // prevent bad fixes from being logged or added to OLC store
    return false;

  // log points more often in circling mode
  if (calculated.circling)
    log_clock.set_dt(fixed(settings_logger.logger_time_step_circling));
  else
    log_clock.set_dt(fixed(settings_logger.logger_time_step_cruise));

  if (fast_log_num) {
    log_clock.set_dt(fixed_one);
    fast_log_num--;
  }

  if (log_clock.check_advance(basic.time) && logger != NULL)
      logger->LogPoint(basic);

  if (calculated.flight.flying &&
      stats_clock.check_advance(basic.time)) {
    flightstats.AddAltitudeTerrain(calculated.flight.flight_time,
                                   calculated.terrain_altitude);

    if (basic.NavAltitudeAvailable())
      flightstats.AddAltitude(calculated.flight.flight_time,
                              basic.nav_altitude);

    if (calculated.task_stats.IsPirkerSpeedAvailable())
      flightstats.AddTaskSpeed(calculated.flight.flight_time,
                               calculated.task_stats.get_pirker_speed());
  }

  return true;
}
コード例 #18
0
void
WarningComputer::Update(const ComputerSettings &settings_computer,
                        const MoreData &basic, const MoreData &last_basic,
                        const DerivedInfo &calculated,
                        AirspaceWarningsInfo &result)
{
  if (!basic.HasTimeAdvancedSince(last_basic) ||
      !clock.check_advance(basic.time))
    return;

  airspaces.set_flight_levels(settings_computer.pressure);

  AirspaceActivity day(calculated.date_time_local.day_of_week);
  airspaces.set_activity(day);

  if (!settings_computer.airspace.enable_warnings ||
      !basic.location_available || !basic.NavAltitudeAvailable()) {
    if (initialised) {
      initialised = false;
      protected_manager.clear();
    }

    return;
  }

  const AircraftState as = ToAircraftState(basic, calculated);
  ProtectedAirspaceWarningManager::ExclusiveLease lease(protected_manager);

  if (!initialised) {
    initialised = true;
    lease->Reset(as);
  }

  if (lease->Update(as, settings_computer.glide_polar_task,
                    calculated.task_stats,
                    calculated.circling,
                    uround(basic.time - last_basic.time)))
    result.latest.Update(basic.clock);
}
コード例 #19
0
void
GlideComputerAirData::ThermalSources(const MoreData &basic,
                                     const DerivedInfo &calculated,
                                     ThermalLocatorInfo &thermal_locator)
{
  if (!thermal_locator.estimate_valid ||
      !basic.NavAltitudeAvailable() ||
      !calculated.last_thermal.IsDefined() ||
      negative(calculated.last_thermal.lift_rate))
    return;

  if (calculated.wind_available &&
      calculated.wind.norm / calculated.last_thermal.lift_rate > fixed(10.0)) {
    // thermal strength is so weak compared to wind that source estimate
    // is unlikely to be reliable, so don't calculate or remember it
    return;
  }

  GeoPoint ground_location;
  fixed ground_altitude = fixed_minus_one;
  EstimateThermalBase(terrain, thermal_locator.estimate_location,
                      basic.nav_altitude,
                      calculated.last_thermal.lift_rate,
                      calculated.GetWindOrZero(),
                      ground_location,
                      ground_altitude);

  if (positive(ground_altitude)) {
    ThermalSource &source =
      thermal_locator.AllocateSource(basic.time);

    source.lift_rate = calculated.last_thermal.lift_rate;
    source.location = ground_location;
    source.ground_height = ground_altitude;
    source.time = basic.time;
  }
}
コード例 #20
0
ファイル: NearestAirspace.cpp プロジェクト: CnZoom/XcSoarPull
gcc_pure
NearestAirspace
NearestAirspace::FindHorizontal(const MoreData &basic,
                                const ProtectedAirspaceWarningManager &airspace_warnings,
                                const Airspaces &airspace_database)
{
  if (!basic.location_available)
    /* can't check for airspaces without a GPS fix */
    return NearestAirspace();

  /* find the nearest airspace */
  //consider only active airspaces
  const WrapAirspacePredicate<ActiveAirspacePredicate> active_predicate(&airspace_warnings);
  const WrapAirspacePredicate<OutsideAirspacePredicate> outside_predicate(AGeoPoint(basic.location, RoughAltitude(0)));
  const AndAirspacePredicate outside_and_active_predicate(active_predicate, outside_predicate);
  const Airspace *airspace;

  //if altitude is available, filter airspaces in same height as airplane
  if (basic.NavAltitudeAvailable()) {
    /* check altitude; hard-coded margin of 50m (for now) */
    WrapAirspacePredicate<AirspacePredicateHeightRange> height_range_predicate(RoughAltitude(basic.nav_altitude-fixed(50)),
                                                                               RoughAltitude(basic.nav_altitude+fixed(50)));
     AndAirspacePredicate and_predicate(outside_and_active_predicate, height_range_predicate);
     airspace = airspace_database.FindNearest(basic.location, and_predicate);
  } else //only filter outside and active
    airspace = airspace_database.FindNearest(basic.location, outside_and_active_predicate);

  if (airspace == nullptr)
    return NearestAirspace();

  const AbstractAirspace &as = airspace->GetAirspace();

  /* calculate distance to the nearest point */
  const GeoPoint closest = as.ClosestPoint(basic.location,
                                           airspace_database.GetProjection());
  return NearestAirspace(as, basic.location.Distance(closest));
}
コード例 #21
0
void
ThermalBandRenderer::_DrawThermalBand(const MoreData &basic,
                                      const DerivedInfo& calculated,
                                      const ComputerSettings &settings_computer,
                                      ChartRenderer &chart,
                                      const TaskBehaviour& task_props,
                                      const bool is_infobox,
                                      const OrderedTaskBehaviour *ordered_props) const
{
  const ThermalBandInfo &thermal_band = calculated.thermal_band;

  // calculate height above safety height
  fixed hoffset = task_props.route_planner.safety_height_terrain +
    calculated.GetTerrainBaseFallback();

  fixed h = fixed(0);
  if (basic.NavAltitudeAvailable()) {
    h = basic.nav_altitude - hoffset;
    chart.ScaleYFromValue(h);
  }

  bool draw_start_height = false;
  fixed hstart = fixed(0);

  draw_start_height = ordered_props
    && calculated.ordered_task_stats.task_valid
    && ordered_props->start_constraints.max_height != 0
    && calculated.terrain_valid;
  if (draw_start_height) {
    hstart = fixed(ordered_props->start_constraints.max_height);
    if (ordered_props->start_constraints.max_height_ref == AltitudeReference::AGL &&
        calculated.terrain_valid)
      hstart += calculated.terrain_altitude;

    hstart -= hoffset;
    chart.ScaleYFromValue(hstart);
  }

  // no thermalling has been done above safety height
  if (!positive(calculated.thermal_band.max_thermal_height))
    return;

  // calculate averages
  int numtherm = 0;

  fixed Wmax = fixed(0);
  fixed Wav = fixed(0);
  fixed Wt[ThermalBandInfo::NUMTHERMALBUCKETS];
  fixed ht[ThermalBandInfo::NUMTHERMALBUCKETS];

  for (unsigned i = 0; i < ThermalBandInfo::NUMTHERMALBUCKETS; ++i) {
    if (thermal_band.thermal_profile_n[i] < 6) 
      continue;

    if (positive(thermal_band.thermal_profile_w[i])) {
      // height of this thermal point [0,mth]
      // requires 5 items in bucket before displaying, to eliminate kinks
      fixed wthis = thermal_band.thermal_profile_w[i] / thermal_band.thermal_profile_n[i];
      ht[numtherm] = i * calculated.thermal_band.max_thermal_height 
        / ThermalBandInfo::NUMTHERMALBUCKETS;
      Wt[numtherm] = wthis;
      Wmax = std::max(Wmax, wthis);
      Wav+= wthis;
      numtherm++;
    }
  }
  chart.ScaleXFromValue(Wmax);
  if (!numtherm)
    return;
  chart.ScaleXFromValue(fixed(1.5)*Wav/numtherm);

  if ((!draw_start_height) && (numtherm<=1))
    // don't display if insufficient statistics
    // but do draw if start height needs to be drawn
    return;

  const Pen *fpen = is_infobox ? NULL : &look.pen;

  // position of thermal band
  if (numtherm > 1) {
    std::vector< std::pair<fixed, fixed> > thermal_profile;
    thermal_profile.reserve(numtherm);
    for (int i = 0; i < numtherm; ++i)
      thermal_profile.emplace_back(Wt[i], ht[i]);

    if (!is_infobox) {
#ifdef ENABLE_OPENGL
      const GLEnable blend(GL_BLEND);
#endif
      chart.DrawFilledY(thermal_profile, look.brush, fpen);
    } else
      chart.DrawFilledY(thermal_profile, look.brush, fpen);
  }

  // position of thermal band
  if (basic.NavAltitudeAvailable()) {
    const Pen &pen = is_infobox && look.inverse
      ? look.white_pen : look.black_pen;
    chart.DrawLine(fixed(0), h,
                   settings_computer.polar.glide_polar_task.GetMC(), h, pen);

    if (is_infobox && look.inverse)
      chart.GetCanvas().SelectWhiteBrush();
    else
      chart.GetCanvas().SelectBlackBrush();
    chart.DrawDot(settings_computer.polar.glide_polar_task.GetMC(),
                  h, Layout::Scale(2));
  }
}