js8call/JS8_Main/Geodesic.h
2026-01-18 19:19:15 -06:00

159 lines
4.9 KiB
C++

#include <QString>
#include <QStringView>
#include <cmath>
#include <utility>
namespace Geodesic {
// Azimuth class, describes an azimuth in degrees. Created via
// interpolation of Maidenhead grid coordinates, and as such
// will be invalid if interpolation failed, typically due to
// bad coordinates.
class Azimuth {
// Data members
float m_value = NAN;
// Constructors
Azimuth() = default;
Azimuth(float const value) : m_value{value} {}
// Allow construction only by Vector.
friend class Vector;
public:
// Allow copying, moving, and assignment by anyone.
Azimuth(Azimuth const &) = default;
Azimuth &operator=(Azimuth const &) = default;
Azimuth(Azimuth &&) noexcept = default;
Azimuth &operator=(Azimuth &&) noexcept = default;
// Inline Accessors
auto isValid() const { return !std::isnan(m_value); }
// Conversion operators; return validity and value. These
// are all we need to implement an ordering relation, but
// we do so elsewhere.
explicit operator bool() const noexcept { return isValid(); }
operator float() const noexcept { return m_value; }
// Return as a directional arrow and cardinal direction, if
// valid, an empty string if invalid.
QStringView compass() const;
// String conversion, to the nearest whole degree; always
// succeeds, returning an empty string if invalid. Caller
// must specify if they want units or just a bare value.
QString toString(bool units) const;
};
// Distance class, describes a distance in kilometers. Created via
// interpolation of Maidenhead grid coordinates, and as such will
// be invalid if interpolation failed, typically due to bad coordinates.
//
// May additionally be defined as 'close', meaning that either of the
// grids provided was only 4 characters and the computed distance was
// short, so we know it's close, but not just how close. In this case,
// the value will be a non-zero minimum constant, and string conversion
// will prepend a '<'.
//
// While distances are stored internally only in kilometers, caller may
// request string conversion in terms of statute miles.
class Distance {
// Value, in kilometers, that we consider to be the close limit, so
// if we're informed that we should consider the value for closeness,
// i.e., one of the grids that gave rise to use is of at best square
// magnitude, we'd consider any value at or under the limit to be of
// only the limit in terms of precision.
static constexpr float CLOSE = 120.0f;
// Data members
float m_value = NAN;
// Constructors
Distance() = default;
Distance(float const value, bool const close)
: m_value{close && CLOSE > value ? INFINITY : value} {}
// Allow construction only by Vector.
friend class Vector;
public:
// Allow copying, moving, and assignment by anyone.
Distance(Distance const &) = default;
Distance &operator=(Distance const &) = default;
Distance(Distance &&) noexcept = default;
Distance &operator=(Distance &&) noexcept = default;
// Inline Accessors
auto isValid() const { return !std::isnan(m_value); }
auto isClose() const { return std::isinf(m_value); }
// Conversion operators; return validity and value. These
// are all we need to implement an ordering relation, but
// we do so elsewhere.
explicit operator bool() const noexcept { return isValid(); }
operator float() const noexcept { return isClose() ? CLOSE : m_value; }
// String conversion, to the nearest whole kilometer or mile,
// always succeeds, returning an empty string if invalid. Caller
// must specify if they want units or just the bare value.
QString toString(bool miles, bool units) const;
};
// Vector class, aggregate of azimuth and distance from an
// origin grid to a remote grid.
class Vector {
// Data members
Azimuth m_azimuth;
Distance m_distance;
// Constructors; disallow creation without going through
// the vector() function.
Vector() = default;
Vector(std::pair<float, float> const &azdist, bool const square)
: m_azimuth{azdist.first}, m_distance{azdist.second, square} {}
friend Vector vector(QStringView, QStringView);
public:
// Allow copying, moving, and assignment by anyone.
Vector(Vector const &) = default;
Vector &operator=(Vector const &) = default;
Vector(Vector &&) noexcept = default;
Vector &operator=(Vector &&) noexcept = default;
// Inline accessors
Azimuth const &azimuth() const { return m_azimuth; }
Distance const &distance() const { return m_distance; }
};
// Creation method; manages a cache, returning cached data
// if possible. This gets called just a lot; performing the
// calculations needed to make a vector are not cheap, so
// we want to reuse results as much as possible.
Vector vector(QStringView origin, QStringView remote);
} // namespace Geodesic