2021-02-26 14:10:36 +01:00
|
|
|
// 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.
|
|
|
|
|
|
|
|
|
|
#pragma once
|
|
|
|
|
|
2022-04-21 21:25:54 +02:00
|
|
|
#include "ranged_representation.h"
|
2023-05-25 12:47:10 +02:00
|
|
|
#include <mp-units/bits/fmt_hacks.h>
|
2023-09-13 08:55:06 +02:00
|
|
|
#include <mp-units/compare.h>
|
2023-05-25 12:47:10 +02:00
|
|
|
#include <mp-units/format.h>
|
|
|
|
|
#include <mp-units/math.h>
|
|
|
|
|
#include <mp-units/quantity.h>
|
|
|
|
|
#include <mp-units/quantity_point.h>
|
|
|
|
|
#include <mp-units/systems/isq/space_and_time.h>
|
|
|
|
|
#include <mp-units/systems/si/units.h>
|
2022-12-23 19:24:56 +01:00
|
|
|
#include <compare>
|
2021-03-30 13:21:05 +02:00
|
|
|
#include <limits>
|
2022-04-22 13:07:01 +02:00
|
|
|
#include <numbers>
|
2021-03-30 13:21:05 +02:00
|
|
|
#include <ostream>
|
|
|
|
|
|
2021-02-26 14:10:36 +01:00
|
|
|
namespace geographic {
|
|
|
|
|
|
2023-12-03 16:15:38 +01:00
|
|
|
inline constexpr struct mean_sea_level : mp_units::absolute_point_origin<mean_sea_level, mp_units::isq::altitude> {
|
2023-05-24 22:39:42 +02:00
|
|
|
} mean_sea_level;
|
2023-05-15 12:56:11 +02:00
|
|
|
|
|
|
|
|
using msl_altitude = mp_units::quantity_point<mp_units::isq::altitude[mp_units::si::metre], mean_sea_level>;
|
|
|
|
|
|
|
|
|
|
// text output
|
|
|
|
|
template<class CharT, class Traits>
|
|
|
|
|
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os, const msl_altitude& a)
|
|
|
|
|
{
|
2023-08-23 14:52:09 +02:00
|
|
|
return os << a - mean_sea_level << " AMSL";
|
2023-05-15 12:56:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace geographic
|
|
|
|
|
|
|
|
|
|
template<>
|
2023-06-21 18:05:21 +02:00
|
|
|
struct MP_UNITS_STD_FMT::formatter<geographic::msl_altitude> : formatter<geographic::msl_altitude::quantity_type> {
|
2023-05-15 12:56:11 +02:00
|
|
|
template<typename FormatContext>
|
|
|
|
|
auto format(const geographic::msl_altitude& a, FormatContext& ctx)
|
|
|
|
|
{
|
2023-08-23 14:52:09 +02:00
|
|
|
formatter<geographic::msl_altitude::quantity_type>::format(a - geographic::mean_sea_level, ctx);
|
2023-06-21 18:05:21 +02:00
|
|
|
return MP_UNITS_STD_FMT::format_to(ctx.out(), " AMSL");
|
2023-05-15 12:56:11 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
namespace geographic {
|
|
|
|
|
|
2023-12-03 16:15:38 +01:00
|
|
|
inline constexpr struct equator : mp_units::absolute_point_origin<equator, mp_units::isq::angular_measure> {
|
2023-09-04 11:20:00 +02:00
|
|
|
} equator;
|
2023-12-03 16:15:38 +01:00
|
|
|
inline constexpr struct prime_meridian : mp_units::absolute_point_origin<prime_meridian, mp_units::isq::angular_measure> {
|
2023-09-04 11:20:00 +02:00
|
|
|
} prime_meridian;
|
|
|
|
|
|
|
|
|
|
|
2022-04-21 21:25:54 +02:00
|
|
|
template<typename T = double>
|
2023-09-04 11:20:00 +02:00
|
|
|
using latitude = mp_units::quantity_point<mp_units::si::degree, equator, ranged_representation<T, -90, 90>>;
|
2021-02-26 14:10:36 +01:00
|
|
|
|
2022-04-21 21:25:54 +02:00
|
|
|
template<typename T = double>
|
2023-09-04 11:20:00 +02:00
|
|
|
using longitude = mp_units::quantity_point<mp_units::si::degree, prime_meridian, ranged_representation<T, -180, 180>>;
|
2021-02-26 14:10:36 +01:00
|
|
|
|
2022-04-21 21:25:54 +02:00
|
|
|
template<class CharT, class Traits, typename T>
|
|
|
|
|
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os, const latitude<T>& lat)
|
2021-02-26 14:10:36 +01:00
|
|
|
{
|
2023-10-15 09:39:18 +02:00
|
|
|
const auto& q = lat.quantity_ref_from(geographic::equator);
|
|
|
|
|
if (is_gteq_zero(q))
|
|
|
|
|
return os << q << " N";
|
2021-02-26 14:10:36 +01:00
|
|
|
else
|
2023-10-15 09:39:18 +02:00
|
|
|
return os << -q << " S";
|
2021-02-26 14:10:36 +01:00
|
|
|
}
|
|
|
|
|
|
2022-04-21 21:25:54 +02:00
|
|
|
template<class CharT, class Traits, typename T>
|
|
|
|
|
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os, const longitude<T>& lon)
|
2021-02-26 14:10:36 +01:00
|
|
|
{
|
2023-10-15 09:39:18 +02:00
|
|
|
const auto& q = lon.quantity_ref_from(geographic::prime_meridian);
|
|
|
|
|
if (is_gteq_zero(q))
|
|
|
|
|
return os << q << " E";
|
2021-02-26 14:10:36 +01:00
|
|
|
else
|
2023-10-15 09:39:18 +02:00
|
|
|
return os << -q << " W";
|
2021-02-26 14:10:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline namespace literals {
|
|
|
|
|
|
2023-09-04 11:20:00 +02:00
|
|
|
constexpr latitude<long double> operator"" _N(long double v)
|
|
|
|
|
{
|
|
|
|
|
return equator + ranged_representation<long double, -90, 90>{v} * mp_units::si::degree;
|
|
|
|
|
}
|
2023-05-16 14:07:29 +02:00
|
|
|
constexpr latitude<long double> operator"" _S(long double v)
|
|
|
|
|
{
|
2023-09-04 11:20:00 +02:00
|
|
|
return equator - ranged_representation<long double, -90, 90>{v} * mp_units::si::degree;
|
2023-05-16 14:07:29 +02:00
|
|
|
}
|
|
|
|
|
constexpr longitude<long double> operator"" _E(long double v)
|
|
|
|
|
{
|
2023-09-04 11:20:00 +02:00
|
|
|
return prime_meridian + ranged_representation<long double, -180, 180>{v} * mp_units::si::degree;
|
2023-05-16 14:07:29 +02:00
|
|
|
}
|
|
|
|
|
constexpr longitude<long double> operator"" _W(long double v)
|
|
|
|
|
{
|
2023-09-04 11:20:00 +02:00
|
|
|
return prime_meridian - ranged_representation<long double, -180, 180>{v} * mp_units::si::degree;
|
2023-05-16 14:07:29 +02:00
|
|
|
}
|
2021-02-26 14:10:36 +01:00
|
|
|
|
|
|
|
|
} // namespace literals
|
|
|
|
|
|
|
|
|
|
} // namespace geographic
|
|
|
|
|
|
2022-04-21 21:25:54 +02:00
|
|
|
template<typename T>
|
|
|
|
|
class std::numeric_limits<geographic::latitude<T>> : public numeric_limits<T> {
|
|
|
|
|
static constexpr auto min() noexcept { return geographic::latitude<T>(-90); }
|
|
|
|
|
static constexpr auto lowest() noexcept { return geographic::latitude<T>(-90); }
|
|
|
|
|
static constexpr auto max() noexcept { return geographic::latitude<T>(90); }
|
2021-02-26 14:10:36 +01:00
|
|
|
};
|
|
|
|
|
|
2022-04-21 21:25:54 +02:00
|
|
|
template<typename T>
|
|
|
|
|
class std::numeric_limits<geographic::longitude<T>> : public numeric_limits<T> {
|
|
|
|
|
static constexpr auto min() noexcept { return geographic::longitude<T>(-180); }
|
|
|
|
|
static constexpr auto lowest() noexcept { return geographic::longitude<T>(-180); }
|
|
|
|
|
static constexpr auto max() noexcept { return geographic::longitude<T>(180); }
|
2021-02-26 14:10:36 +01:00
|
|
|
};
|
|
|
|
|
|
2022-04-21 21:25:54 +02:00
|
|
|
template<typename T>
|
2023-09-04 11:20:00 +02:00
|
|
|
struct MP_UNITS_STD_FMT::formatter<geographic::latitude<T>> :
|
|
|
|
|
formatter<typename geographic::latitude<T>::quantity_type> {
|
2021-02-26 14:10:36 +01:00
|
|
|
template<typename FormatContext>
|
2022-04-21 21:25:54 +02:00
|
|
|
auto format(geographic::latitude<T> lat, FormatContext& ctx)
|
2021-02-26 14:10:36 +01:00
|
|
|
{
|
2023-10-15 09:39:18 +02:00
|
|
|
const auto& q = lat.quantity_ref_from(geographic::equator);
|
|
|
|
|
formatter<typename geographic::latitude<T>::quantity_type>::format(is_gteq_zero(q) ? q : -q, ctx);
|
|
|
|
|
MP_UNITS_STD_FMT::format_to(ctx.out(), "{}", is_gteq_zero(q) ? " N" : "S");
|
2023-09-04 11:20:00 +02:00
|
|
|
return ctx.out();
|
2021-02-26 14:10:36 +01:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2022-04-21 21:25:54 +02:00
|
|
|
template<typename T>
|
2023-09-04 11:20:00 +02:00
|
|
|
struct MP_UNITS_STD_FMT::formatter<geographic::longitude<T>> :
|
|
|
|
|
formatter<typename geographic::longitude<T>::quantity_type> {
|
2021-02-26 14:10:36 +01:00
|
|
|
template<typename FormatContext>
|
2022-04-21 21:25:54 +02:00
|
|
|
auto format(geographic::longitude<T> lon, FormatContext& ctx)
|
2021-02-26 14:10:36 +01:00
|
|
|
{
|
2023-10-15 09:39:18 +02:00
|
|
|
const auto& q = lon.quantity_ref_from(geographic::prime_meridian);
|
|
|
|
|
formatter<typename geographic::longitude<T>::quantity_type>::format(is_gteq_zero(q) ? q : -q, ctx);
|
|
|
|
|
MP_UNITS_STD_FMT::format_to(ctx.out(), "{}", is_gteq_zero(q) ? " E" : " W");
|
2023-09-04 11:20:00 +02:00
|
|
|
return ctx.out();
|
2021-02-26 14:10:36 +01:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
namespace geographic {
|
|
|
|
|
|
2022-12-29 20:18:48 +01:00
|
|
|
using distance = mp_units::quantity<mp_units::isq::distance[mp_units::si::kilo<mp_units::si::metre>]>;
|
2021-02-26 14:10:36 +01:00
|
|
|
|
2022-04-21 21:25:54 +02:00
|
|
|
template<typename T>
|
2021-02-26 14:10:36 +01:00
|
|
|
struct position {
|
2022-04-21 21:25:54 +02:00
|
|
|
latitude<T> lat;
|
|
|
|
|
longitude<T> lon;
|
2021-02-26 14:10:36 +01:00
|
|
|
};
|
|
|
|
|
|
2022-04-21 21:25:54 +02:00
|
|
|
template<typename T>
|
|
|
|
|
distance spherical_distance(position<T> from, position<T> to)
|
|
|
|
|
{
|
2022-12-29 20:18:48 +01:00
|
|
|
using namespace mp_units;
|
2023-09-13 11:57:22 +02:00
|
|
|
constexpr quantity earth_radius = 6'371 * isq::radius[si::kilo<si::metre>];
|
2022-04-21 21:25:54 +02:00
|
|
|
|
2023-04-07 22:54:47 +02:00
|
|
|
using isq::sin, isq::cos, isq::asin, isq::acos;
|
2022-04-21 21:25:54 +02:00
|
|
|
|
2023-09-13 11:57:22 +02:00
|
|
|
const quantity from_lat = from.lat.quantity_from(equator);
|
|
|
|
|
const quantity from_lon = from.lon.quantity_from(prime_meridian);
|
|
|
|
|
const quantity to_lat = to.lat.quantity_from(equator);
|
|
|
|
|
const quantity to_lon = to.lon.quantity_from(prime_meridian);
|
2023-09-04 11:20:00 +02:00
|
|
|
|
2022-04-21 21:25:54 +02:00
|
|
|
// https://en.wikipedia.org/wiki/Great-circle_distance#Formulae
|
|
|
|
|
if constexpr (sizeof(T) >= 8) {
|
|
|
|
|
// spherical law of cosines
|
2023-09-13 11:57:22 +02:00
|
|
|
const quantity central_angle =
|
|
|
|
|
acos(sin(from_lat) * sin(to_lat) + cos(from_lat) * cos(to_lat) * cos(to_lon - from_lon));
|
2023-09-04 11:20:00 +02:00
|
|
|
// const auto central_angle = 2 * asin(sqrt(0.5 - cos(to_lat - from_lat) / 2 + cos(from_lat) * cos(to_lat) * (1
|
|
|
|
|
// - cos(lon2_rad - from_lon)) / 2));
|
2022-12-23 19:24:56 +01:00
|
|
|
|
2023-02-14 12:58:54 +01:00
|
|
|
return quantity_cast<isq::distance>(earth_radius * central_angle);
|
2022-04-21 21:25:54 +02:00
|
|
|
} else {
|
|
|
|
|
// the haversine formula
|
2023-09-13 11:57:22 +02:00
|
|
|
const quantity sin_lat = sin((to_lat - from_lat) / 2);
|
|
|
|
|
const quantity sin_lon = sin((to_lon - from_lon) / 2);
|
|
|
|
|
const quantity central_angle = 2 * asin(sqrt(sin_lat * sin_lat + cos(from_lat) * cos(to_lat) * sin_lon * sin_lon));
|
2022-12-23 19:24:56 +01:00
|
|
|
|
2023-02-14 12:58:54 +01:00
|
|
|
return quantity_cast<isq::distance>(earth_radius * central_angle);
|
2022-04-21 21:25:54 +02:00
|
|
|
}
|
|
|
|
|
}
|
2021-02-26 14:10:36 +01:00
|
|
|
|
|
|
|
|
} // namespace geographic
|