diff --git a/example/glide_computer/glide_computer.cpp b/example/glide_computer/glide_computer.cpp index fe7b7d62..b378114d 100644 --- a/example/glide_computer/glide_computer.cpp +++ b/example/glide_computer/glide_computer.cpp @@ -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; diff --git a/example/glide_computer/include/geographic.h b/example/glide_computer/include/geographic.h index 70d2f6c9..094c2520 100644 --- a/example/glide_computer/include/geographic.h +++ b/example/glide_computer/include/geographic.h @@ -23,26 +23,22 @@ #pragma once #include "ranged_representation.h" -#include #include -#include -#include -#include +#include +#include +#include +#include #include #include #include -// IWYU pragma: begin_exports -#include -// IWYU pragma: end_exports - namespace geographic { template -using latitude = units::angle>; +using latitude = units::quantity>; template -using longitude = units::angle>; +using longitude = units::quantity>; template std::basic_ostream& operator<<(std::basic_ostream& os, const latitude& lat) @@ -129,8 +125,7 @@ struct STD_FMT::formatter> : formatter { namespace geographic { -struct horizontal_kind : units::kind {}; -using distance = units::quantity_kind; +using distance = units::quantity]>; template struct position { @@ -141,8 +136,8 @@ struct position { template distance spherical_distance(position from, position to) { - using namespace units::isq::si; - constexpr length earth_radius(6371); + using namespace units; + constexpr auto earth_radius = 6371 * isq::radius[si::kilo]; constexpr auto p = std::numbers::pi_v / 180; const auto lat1_rad = from.lat.number() * p; @@ -159,13 +154,19 @@ distance spherical_distance(position from, position 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(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(earth_radius * central_angle); + return earth_radius.number() * central_angle * isq::distance[earth_radius.unit]; } } diff --git a/example/glide_computer/include/glide_computer.h b/example/glide_computer/include/glide_computer.h index d127943b..0f9d8358 100644 --- a/example/glide_computer/include/glide_computer.h +++ b/example/glide_computer/include/glide_computer.h @@ -22,17 +22,12 @@ #pragma once -// IWYU pragma: begin_exports #include "geographic.h" -#include -#include -#include -#include -// IWYU pragma: end_exports - #include #include +#include #include // IWYU pragma: keep +#include #include #include #include @@ -56,60 +51,43 @@ // - no ground obstacles (i.e. mountains) to pass // - flight path exactly on a shortest possible line to destination -template -struct STD_FMT::formatter : formatter { - template - auto format(const QK& v, FormatContext& ctx) - { - return formatter::format(v.common(), ctx); - } -}; - namespace glide_computer { -template -constexpr units::Dimensionless auto operator/(const QK1& lhs, const QK2& rhs) - requires(!units::QuantityKindRelatedTo) && requires { lhs.common() / rhs.common(); } -{ - return lhs.common() / rhs.common(); -} - -// kinds -using horizontal_kind = geographic::horizontal_kind; -struct vertical_kind : units::kind {}; -struct vertical_point_kind : units::point_kind {}; -struct velocity_kind : units::derived_kind {}; -struct rate_of_climb_kind : units::derived_kind {}; - // https://en.wikipedia.org/wiki/Flight_planning#Units_of_measurement +inline constexpr struct mean_sea_level : units::absolute_point_origin { +} mean_sea_level; +QUANTITY_SPEC(rate_of_climb_speed, units::isq::height / units::isq::time); + // length -using distance = units::quantity_kind; -using height = units::quantity_kind; -using altitude = units::quantity_point_kind; +using distance = units::quantity]>; +using height = units::quantity; +using altitude = units::quantity_point; // time -using duration = units::isq::si::time; -using timestamp = units::quantity_point, units::isq::si::second>; +using duration = units::quantity; +using timestamp = + units::quantity_point{}>; // speed -using velocity = units::quantity_kind; -using rate_of_climb = units::quantity_kind; +using velocity = units::quantity / units::si::hour]>; +using rate_of_climb = units::quantity; // text output template std::basic_ostream& operator<<(std::basic_ostream& os, const altitude& a) { - return os << a.relative().common() << " AMSL"; + return os << a.absolute() << " AMSL"; } } // namespace glide_computer template<> -struct STD_FMT::formatter : formatter> { +struct STD_FMT::formatter : + formatter> { template - auto format(glide_computer::altitude a, FormatContext& ctx) + auto format(const glide_computer::altitude& a, FormatContext& ctx) { - formatter>::format(a.relative().common(), ctx); + formatter>::format(a.absolute(), ctx); return STD_FMT::format_to(ctx.out(), " AMSL"); } }; @@ -127,7 +105,10 @@ struct glider { std::array polar; }; -constexpr units::Dimensionless auto glide_ratio(const glider::polar_point& polar) { return polar.v / -polar.climb; } +constexpr units::weak_quantity_of 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 length_3d(distance dist, height h) +inline units::quantity]> 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(dist), quantity_cast(h)); } distance glide_distance(const flight_point& pos, const glider& g, const task& t, const safety& s, altitude ground_alt); diff --git a/example/glide_computer_example.cpp b/example/glide_computer_example.cpp new file mode 100644 index 00000000..eb24d4a9 --- /dev/null +++ b/example/glide_computer_example.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 + requires(std::same_as, 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(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(isq::asin(1 / ratio))); + } + std::cout << "\n"; + } +} + +template + requires(std::same_as, std::pair>) +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 + requires(std::same_as, 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"; + } +} diff --git a/example/include/ranged_representation.h b/example/include/ranged_representation.h index 3d7b2a88..47b55fe6 100644 --- a/example/include/ranged_representation.h +++ b/example/include/ranged_representation.h @@ -24,6 +24,7 @@ #include "validated_type.h" #include +#include #include #include @@ -42,6 +43,9 @@ public: [[nodiscard]] constexpr ranged_representation operator-() const { return ranged_representation(-this->value()); } }; +template +inline constexpr bool units::is_scalar> = units::is_scalar; + template struct std::common_type> : std::type_identity, Min, Max>> {};