#pragma once #include #include namespace roro::gnss { // Angles are degrees from true north, clockwise, 0 up to 360. float wrap360(float degrees); // How far to turn from `from` to `to`: -180 up to 180, positive clockwise. float angleDiff(float to, float from); // The initial great-circle bearing from the first point to the second. double bearingDegrees(double lat1, double lon1, double lat2, double lon2); // One of the 16 points: "N", "NNE", "NE"... const char* cardinal(float degrees); // "340 m", "1.2 km", "12 km". std::string formatDistance(double meters); // Where the sun is in the sky, from the time and the place (the low-precision formulas of the // Astronomical Almanac: within a hundredth of a degree until 2050, far finer than a hand can aim). struct SunPosition { float azimuthDeg; float elevationDeg; // above the horizon; refraction is left out }; SunPosition sunPosition(int64_t utcSeconds, double latitude, double longitude); // The Compass's heading (issue #16): where the top of the device points. There is no // magnetometer, so north comes from outside (the course over ground while walking, or the sun // when the user aims at it) and the gyroscope follows the turns in between. Host-tested. class HeadingFilter { public: enum class Source : uint8_t { None, Course, Sun }; // Below a walking pace the receiver's course is noise. static constexpr float kMinSpeedKmh = 3.0f; // Each course reading pulls the heading this much of the way: a few seconds to settle, // without jumping at every reading. static constexpr float kCourseGain = 0.3f; // The gyroscope's own offset is learnt while the device lies still: turning slower than // this for kStillSeconds counts as still. static constexpr float kStillDps = 2.0f; static constexpr float kStillSeconds = 1.0f; static constexpr float kBiasSeconds = 4.0f; // How fast the measured "up" follows the accelerometer. static constexpr float kUpSeconds = 0.3f; // One reading of the IMU: degrees a second and g, on the sensor's axes, `dt` seconds after // the one before. Only the turn around the vertical counts, however the device is held. void motion(const float gyro[3], const float accel[3], float dt); // The receiver's course over ground, once a second: used when moving fast enough. void course(float courseDeg, float speedKmh); // The top of the device is aimed at the sun, which is at this azimuth. void sun(float azimuthDeg); void forget(); // the device was not watched for a while: north has to come again bool known() const { return source_ != Source::None; } float heading() const { return heading_; } Source source() const { return source_; } // Seconds the gyroscope has carried the heading alone since north was last given. float heldSeconds() const { return held_; } // "Up" along the sensor's x and y axes, -1 to 1: both 0 when the device lies flat. float tiltX() const { return up_[0]; } float tiltY() const { return up_[1]; } bool still() const { return stillFor_ >= kStillSeconds; } private: float heading_ = 0, held_ = 0, stillFor_ = 0; float up_[3] = {0, 0, 1}, bias_[3] = {0, 0, 0}; bool any_ = false; Source source_ = Source::None; }; } // namespace roro::gnss