refactor(example): glide computer refactored for V2

This commit is contained in:
Mateusz Pusz
2022-12-23 19:24:56 +01:00
parent 278dbace98
commit fd26f8cdff
5 changed files with 258 additions and 68 deletions

View File

@@ -27,7 +27,7 @@
namespace glide_computer {
using namespace units::isq;
using namespace units;
task::legs task::make_legs(const waypoints& wpts)
{
@@ -52,7 +52,7 @@ altitude terrain_level_alt(const task& t, const flight_point& pos)
{
const task::leg& l = t.get_legs()[pos.leg_idx];
const height alt_diff = l.end().alt - l.begin().alt;
return l.begin().alt + alt_diff * ((pos.dist - t.get_leg_dist_offset(pos.leg_idx)) / l.get_length()).common();
return l.begin().alt + alt_diff * ((pos.dist - t.get_leg_dist_offset(pos.leg_idx)) / l.get_length());
}
// Returns `x` of the intersection of a glide line and a terrain line.
@@ -61,7 +61,7 @@ altitude terrain_level_alt(const task& t, const flight_point& pos)
distance glide_distance(const flight_point& pos, const glider& g, const task& t, const safety& s, altitude ground_alt)
{
const auto dist_to_finish = t.get_length() - pos.dist;
return distance((ground_alt + s.min_agl_height - pos.alt).common() /
return distance((ground_alt + s.min_agl_height - pos.alt) /
((ground_alt - t.get_finish().alt) / dist_to_finish - 1 / glide_ratio(g.polar[0])));
}
@@ -85,7 +85,7 @@ flight_point takeoff(timestamp start_ts, const task& t) { return {start_ts, t.ge
flight_point tow(timestamp start_ts, const flight_point& pos, const aircraft_tow& at)
{
const duration d = (at.height_agl / at.performance).common();
const duration d = (at.height_agl / at.performance);
const flight_point new_pos{pos.ts + d, pos.alt + at.height_agl, pos.leg_idx, pos.dist};
print("Tow", start_ts, pos, new_pos);
@@ -98,7 +98,7 @@ flight_point circle(timestamp start_ts, const flight_point& pos, const glider& g
const height h_agl = agl(pos.alt, terrain_level_alt(t, pos));
const height circling_height = std::min(w.cloud_base - h_agl, height_to_gain);
const rate_of_climb circling_rate = w.thermal_strength + g.polar[0].climb;
const duration d = (circling_height / circling_rate).common();
const duration d = (circling_height / circling_rate);
const flight_point new_pos{pos.ts + d, pos.alt + circling_height, pos.leg_idx, pos.dist};
height_to_gain -= circling_height;
@@ -114,7 +114,7 @@ flight_point glide(timestamp start_ts, const flight_point& pos, const glider& g,
const auto new_distance = pos.dist + dist;
const auto alt = ground_alt + s.min_agl_height;
const auto l3d = length_3d(dist, pos.alt - alt);
const duration d = l3d / g.polar[0].v.common();
const duration d = l3d / g.polar[0].v;
const flight_point new_pos{pos.ts + d, terrain_level_alt(t, pos) + s.min_agl_height, t.get_leg_index(new_distance),
new_distance};
@@ -126,7 +126,7 @@ flight_point final_glide(timestamp start_ts, const flight_point& pos, const glid
{
const auto dist = t.get_length() - pos.dist;
const auto l3d = length_3d(dist, pos.alt - t.get_finish().alt);
const duration d = l3d / g.polar[0].v.common();
const duration d = l3d / g.polar[0].v;
const flight_point new_pos{pos.ts + d, t.get_finish().alt, t.get_legs().size() - 1, pos.dist + dist};
print("Final Glide", start_ts, pos, new_pos);
@@ -151,7 +151,7 @@ void estimate(timestamp start_ts, const glider& g, const weather& w, const task&
pos = tow(start_ts, pos, at);
// estimate the altitude needed to reach the finish line from this place
const altitude final_glide_alt = t.get_finish().alt + height(t.get_length().common() / glide_ratio(g.polar[0]));
const altitude final_glide_alt = t.get_finish().alt + height(t.get_length() / glide_ratio(g.polar[0]));
// how much height we still need to gain in the thermalls to reach the destination?
height height_to_gain = final_glide_alt - pos.alt;

View File

@@ -23,26 +23,22 @@
#pragma once
#include "ranged_representation.h"
#include <units/bits/external/hacks.h>
#include <units/bits/fmt_hacks.h>
#include <units/generic/angle.h>
#include <units/isq/si/length.h>
#include <units/quantity_kind.h>
#include <units/isq/space_and_time.h>
#include <units/quantity.h>
#include <units/si/units.h>
#include <compare>
#include <limits>
#include <numbers>
#include <ostream>
// IWYU pragma: begin_exports
#include <compare>
// IWYU pragma: end_exports
namespace geographic {
template<typename T = double>
using latitude = units::angle<units::degree, ranged_representation<T, -90, 90>>;
using latitude = units::quantity<units::isq::angular_measure[units::si::degree], ranged_representation<T, -90, 90>>;
template<typename T = double>
using longitude = units::angle<units::degree, ranged_representation<T, -180, 180>>;
using longitude = units::quantity<units::isq::angular_measure[units::si::degree], ranged_representation<T, -180, 180>>;
template<class CharT, class Traits, typename T>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os, const latitude<T>& lat)
@@ -129,8 +125,7 @@ struct STD_FMT::formatter<geographic::longitude<T>> : formatter<T> {
namespace geographic {
struct horizontal_kind : units::kind<horizontal_kind, units::isq::si::dim_length> {};
using distance = units::quantity_kind<horizontal_kind, units::isq::si::kilometre>;
using distance = units::quantity<units::isq::distance[units::si::kilo<units::si::metre>]>;
template<typename T>
struct position {
@@ -141,8 +136,8 @@ struct position {
template<typename T>
distance spherical_distance(position<T> from, position<T> to)
{
using namespace units::isq::si;
constexpr length<kilometre> earth_radius(6371);
using namespace units;
constexpr auto earth_radius = 6371 * isq::radius[si::kilo<si::metre>];
constexpr auto p = std::numbers::pi_v<T> / 180;
const auto lat1_rad = from.lat.number() * p;
@@ -159,13 +154,19 @@ distance spherical_distance(position<T> from, position<T> to)
acos(sin(lat1_rad) * sin(lat2_rad) + cos(lat1_rad) * cos(lat2_rad) * cos(lon2_rad - lon1_rad));
// const auto central_angle = 2 * asin(sqrt(0.5 - cos(lat2_rad - lat1_rad) / 2 + cos(lat1_rad) * cos(lat2_rad) * (1
// - cos(lon2_rad - lon1_rad)) / 2));
return distance(earth_radius * central_angle);
// TODO can we improve the below
// return quantity_cast<isq::distance>(earth_radius * central_angle);
return earth_radius.number() * central_angle * isq::distance[earth_radius.unit];
} else {
// the haversine formula
const auto sin_lat = sin((lat2_rad - lat1_rad) / 2);
const auto sin_lon = sin((lon2_rad - lon1_rad) / 2);
const auto central_angle = 2 * asin(sqrt(sin_lat * sin_lat + cos(lat1_rad) * cos(lat2_rad) * sin_lon * sin_lon));
return distance(earth_radius * central_angle);
// TODO can we improve the below
// return quantity_cast<isq::distance>(earth_radius * central_angle);
return earth_radius.number() * central_angle * isq::distance[earth_radius.unit];
}
}

View File

@@ -22,17 +22,12 @@
#pragma once
// IWYU pragma: begin_exports
#include "geographic.h"
#include <units/isq/si/length.h>
#include <units/isq/si/speed.h>
#include <units/isq/si/time.h>
#include <units/quantity_point_kind.h>
// IWYU pragma: end_exports
#include <units/chrono.h>
#include <units/format.h>
#include <units/isq/space_and_time.h>
#include <units/math.h> // IWYU pragma: keep
#include <units/quantity_point.h>
#include <algorithm>
#include <array>
#include <initializer_list>
@@ -56,60 +51,43 @@
// - no ground obstacles (i.e. mountains) to pass
// - flight path exactly on a shortest possible line to destination
template<units::QuantityKind QK>
struct STD_FMT::formatter<QK> : formatter<typename QK::quantity_type> {
template<typename FormatContext>
auto format(const QK& v, FormatContext& ctx)
{
return formatter<typename QK::quantity_type>::format(v.common(), ctx);
}
};
namespace glide_computer {
template<units::QuantityKind QK1, units::QuantityKind QK2>
constexpr units::Dimensionless auto operator/(const QK1& lhs, const QK2& rhs)
requires(!units::QuantityKindRelatedTo<QK1, QK2>) && requires { lhs.common() / rhs.common(); }
{
return lhs.common() / rhs.common();
}
// kinds
using horizontal_kind = geographic::horizontal_kind;
struct vertical_kind : units::kind<vertical_kind, units::isq::si::dim_length> {};
struct vertical_point_kind : units::point_kind<vertical_point_kind, vertical_kind> {};
struct velocity_kind : units::derived_kind<velocity_kind, units::isq::si::dim_speed, horizontal_kind> {};
struct rate_of_climb_kind : units::derived_kind<rate_of_climb_kind, units::isq::si::dim_speed, vertical_kind> {};
// https://en.wikipedia.org/wiki/Flight_planning#Units_of_measurement
inline constexpr struct mean_sea_level : units::absolute_point_origin<units::isq::height> {
} mean_sea_level;
QUANTITY_SPEC(rate_of_climb_speed, units::isq::height / units::isq::time);
// length
using distance = units::quantity_kind<horizontal_kind, units::isq::si::kilometre>;
using height = units::quantity_kind<vertical_kind, units::isq::si::metre>;
using altitude = units::quantity_point_kind<vertical_point_kind, units::isq::si::metre>;
using distance = units::quantity<units::isq::distance[units::si::kilo<units::si::metre>]>;
using height = units::quantity<units::isq::height[units::si::metre]>;
using altitude = units::quantity_point<units::isq::altitude[units::si::metre], mean_sea_level>;
// time
using duration = units::isq::si::time<units::isq::si::second>;
using timestamp = units::quantity_point<units::clock_origin<std::chrono::system_clock>, units::isq::si::second>;
using duration = units::quantity<units::isq::duration[units::si::second]>;
using timestamp =
units::quantity_point<units::isq::time[units::si::second], units::chrono_point_origin<std::chrono::system_clock>{}>;
// speed
using velocity = units::quantity_kind<velocity_kind, units::isq::si::kilometre_per_hour>;
using rate_of_climb = units::quantity_kind<rate_of_climb_kind, units::isq::si::metre_per_second>;
using velocity = units::quantity<units::isq::speed[units::si::kilo<units::si::metre> / units::si::hour]>;
using rate_of_climb = units::quantity<rate_of_climb_speed[units::si::metre / units::si::second]>;
// text output
template<class CharT, class Traits>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os, const altitude& a)
{
return os << a.relative().common() << " AMSL";
return os << a.absolute() << " AMSL";
}
} // namespace glide_computer
template<>
struct STD_FMT::formatter<glide_computer::altitude> : formatter<units::isq::si::length<units::isq::si::metre>> {
struct STD_FMT::formatter<glide_computer::altitude> :
formatter<units::quantity<units::isq::altitude[units::si::metre]>> {
template<typename FormatContext>
auto format(glide_computer::altitude a, FormatContext& ctx)
auto format(const glide_computer::altitude& a, FormatContext& ctx)
{
formatter<units::isq::si::length<units::isq::si::metre>>::format(a.relative().common(), ctx);
formatter<units::quantity<units::isq::altitude[units::si::metre]>>::format(a.absolute(), ctx);
return STD_FMT::format_to(ctx.out(), " AMSL");
}
};
@@ -127,7 +105,10 @@ struct glider {
std::array<polar_point, 1> polar;
};
constexpr units::Dimensionless auto glide_ratio(const glider::polar_point& polar) { return polar.v / -polar.climb; }
constexpr units::weak_quantity_of<units::dimensionless> auto glide_ratio(const glider::polar_point& polar)
{
return polar.v / -polar.climb;
}
struct weather {
height cloud_base;
@@ -212,9 +193,10 @@ altitude terrain_level_alt(const task& t, const flight_point& pos);
constexpr height agl(altitude glider_alt, altitude terrain_level) { return glider_alt - terrain_level; }
inline units::isq::si::length<units::isq::si::kilometre> length_3d(distance dist, height h)
inline units::quantity<units::isq::length[units::si::kilo<units::si::metre>]> length_3d(distance dist, height h)
{
return hypot(dist.common(), h.common());
// TODO Should we be able to calculate this on quantity of different kinds? What to return?
return hypot(quantity_cast<units::isq::length>(dist), quantity_cast<units::isq::length>(h));
}
distance glide_distance(const flight_point& pos, const glider& g, const task& t, const safety& s, altitude ground_alt);

View File

@@ -0,0 +1,203 @@
// The MIT License (MIT)
//
// Copyright (c) 2018 Mateusz Pusz
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "glide_computer.h"
#include <units/bits/fmt_hacks.h>
#include <units/chrono.h>
#include <units/math.h>
#include <units/si/international/length.h>
#include <units/si/unit_symbols.h>
#include <array>
#include <exception>
#include <iostream>
#include <iterator>
#include <string>
#include <utility>
#include <vector>
namespace {
using namespace glide_computer;
using namespace units;
auto get_gliders()
{
using namespace units::si::unit_symbols;
UNITS_DIAGNOSTIC_PUSH
UNITS_DIAGNOSTIC_IGNORE_MISSING_BRACES
static const std::array gliders = {
glider{"SZD-30 Pirat", {83 * isq::speed[km / h], -0.7389 * rate_of_climb_speed[m / s]}},
glider{"SZD-51 Junior", {80 * isq::speed[km / h], -0.6349 * rate_of_climb_speed[m / s]}},
glider{"SZD-48 Jantar Std 3", {110 * isq::speed[km / h], -0.77355 * rate_of_climb_speed[m / s]}},
glider{"SZD-56 Diana", {110 * isq::speed[km / h], -0.63657 * rate_of_climb_speed[m / s]}}};
UNITS_DIAGNOSTIC_POP
return gliders;
}
auto get_weather_conditions()
{
using namespace units::si::unit_symbols;
static const std::array weather_conditions = {
std::pair{"Good", weather{1900 * isq::height[m], 4.3 * rate_of_climb_speed[m / s]}},
std::pair{"Medium", weather{1550 * isq::height[m], 2.8 * rate_of_climb_speed[m / s]}},
std::pair{"Bad", weather{850 * isq::height[m], 1.8 * rate_of_climb_speed[m / s]}}};
return weather_conditions;
}
auto get_waypoints()
{
using namespace geographic::literals;
using namespace units::si::international::unit_symbols;
static const std::array waypoints = {
waypoint{"EPPR", {54.24772_N, 18.6745_E}, altitude{16. * isq::altitude[ft]}}, // N54°14'51.8" E18°40'28.2"
waypoint{"EPGI", {53.52442_N, 18.84947_E}, altitude{115. * isq::altitude[ft]}} // N53°31'27.9" E18°50'58.1"
};
return waypoints;
}
template<std::ranges::input_range R>
requires(std::same_as<std::ranges::range_value_t<R>, glider>)
void print(const R& gliders)
{
std::cout << "Gliders:\n";
std::cout << "========\n";
for (const auto& g : gliders) {
std::cout << "- Name: " << g.name << "\n";
std::cout << "- Polar:\n";
for (const auto& p : g.polar) {
const auto ratio = quantity_cast<one>(glide_ratio(g.polar[0]));
std::cout << STD_FMT::format(" * {:%.4Q %q} @ {:%.1Q %q} -> {:%.1Q %q} ({:%.1Q %q})\n", p.climb, p.v, ratio,
// TODO is it possible to make ADL work below (we need another set of trig functions
// for strong angle in a different namespace)
quantity_cast<si::degree>(isq::asin(1 / ratio)));
}
std::cout << "\n";
}
}
template<std::ranges::input_range R>
requires(std::same_as<std::ranges::range_value_t<R>, std::pair<const char*, weather>>)
void print(const R& conditions)
{
std::cout << "Weather:\n";
std::cout << "========\n";
for (const auto& c : conditions) {
std::cout << "- " << c.first << "\n";
const auto& w = c.second;
std::cout << " * Cloud base: " << STD_FMT::format("{:%.0Q %q}", w.cloud_base) << " AGL\n";
std::cout << " * Thermals strength: " << STD_FMT::format("{:%.1Q %q}", w.thermal_strength) << "\n";
std::cout << "\n";
}
}
template<std::ranges::input_range R>
requires(std::same_as<std::ranges::range_value_t<R>, waypoint>)
void print(const R& waypoints)
{
std::cout << "Waypoints:\n";
std::cout << "==========\n";
for (const auto& w : waypoints)
std::cout << STD_FMT::format("- {}: {} {}, {:%.1Q %q}\n", w.name, w.pos.lat, w.pos.lon, w.alt);
std::cout << "\n";
}
void print(const task& t)
{
std::cout << "Task:\n";
std::cout << "=====\n";
std::cout << "- Start: " << t.get_start().name << "\n";
std::cout << "- Finish: " << t.get_finish().name << "\n";
std::cout << "- Length: " << STD_FMT::format("{:%.1Q %q}", t.get_length()) << "\n";
std::cout << "- Legs: "
<< "\n";
for (const auto& l : t.get_legs())
std::cout << STD_FMT::format(" * {} -> {} ({:%.1Q %q})\n", l.begin().name, l.end().name, l.get_length());
std::cout << "\n";
}
void print(const safety& s)
{
std::cout << "Safety:\n";
std::cout << "=======\n";
std::cout << "- Min AGL separation: " << STD_FMT::format("{:%.0Q %q}", s.min_agl_height) << "\n";
std::cout << "\n";
}
void print(const aircraft_tow& tow)
{
std::cout << "Tow:\n";
std::cout << "====\n";
std::cout << "- Type: aircraft\n";
std::cout << "- Height: " << STD_FMT::format("{:%.0Q %q}", tow.height_agl) << "\n";
std::cout << "- Performance: " << STD_FMT::format("{:%.1Q %q}", tow.performance) << "\n";
std::cout << "\n";
}
void example()
{
using namespace units::si::unit_symbols;
const safety sfty = {300 * isq::height[m]};
const auto gliders = get_gliders();
const auto waypoints = get_waypoints();
const auto weather_conditions = get_weather_conditions();
const task t = {waypoints[0], waypoints[1], waypoints[0]};
const aircraft_tow tow = {400 * isq::height[m], 1.6 * rate_of_climb_speed[m / s]};
// TODO use C++20 date library when available
// set `start_time` to 11:00 am today
const timestamp start_time(std::chrono::system_clock::now());
print(sfty);
print(gliders);
print(waypoints);
print(weather_conditions);
print(t);
print(tow);
for (const auto& g : gliders) {
for (const auto& c : weather_conditions) {
std::string txt = "Scenario: Glider = " + g.name + ", Weather = " + c.first;
std::cout << txt << "\n";
std::cout << STD_FMT::format("{0:=^{1}}\n\n", "", txt.size());
estimate(start_time, g, c.second, t, sfty, tow);
std::cout << "\n\n";
}
}
}
} // namespace
int main()
{
try {
example();
} catch (const std::exception& ex) {
std::cerr << "Unhandled std exception caught: " << ex.what() << '\n';
} catch (...) {
std::cerr << "Unhandled unknown exception caught\n";
}
}

View File

@@ -24,6 +24,7 @@
#include "validated_type.h"
#include <units/bits/external/hacks.h>
#include <units/customization_points.h>
#include <algorithm>
#include <type_traits>
@@ -42,6 +43,9 @@ public:
[[nodiscard]] constexpr ranged_representation operator-() const { return ranged_representation(-this->value()); }
};
template<typename T, auto Min, auto Max>
inline constexpr bool units::is_scalar<ranged_representation<T, Min, Max>> = units::is_scalar<T>;
template<typename T, auto Min, auto Max>
struct std::common_type<std::intmax_t, ranged_representation<T, Min, Max>> :
std::type_identity<ranged_representation<std::common_type_t<std::intmax_t, T>, Min, Max>> {};