#degree #angle #circle #gis #shortest #modular-arithmetic

ring360

Simple wrapper type for 64-bit floats representing degrees around a circle. It has operator overloads for addition and subtraction, calculates the shortest angles and implements the core trigonometric functions.

3 releases

0.2.14 Nov 5, 2024
0.2.13 Aug 11, 2024
0.2.12 Jun 12, 2024
0.2.11 Apr 14, 2024
0.1.0 Feb 25, 2024

#195 in Math

Download history 51/week @ 2024-07-27 118/week @ 2024-08-10 10/week @ 2024-08-17 18/week @ 2024-09-14 13/week @ 2024-09-21 16/week @ 2024-09-28 1/week @ 2024-10-05 110/week @ 2024-11-02 15/week @ 2024-11-09

125 downloads per month
Used in aspect360

GPL-2.0-or-later WITH Bison-exception-2…

19KB
145 lines

mirror crates.io docs.rs

Ring360: Modular Arithmetic around a 360º circle

This crate provides a versatile wrapper struct for representing degrees in a circular range, designed around 64-bit floats. The primary type, Ring360, supports intuitive degree manipulation with + and - operators, and provides directional calculations for the shortest angle between two degrees. This angle calculation returns a positive value for clockwise movement and a negative value for counterclockwise, making it ideal for applications where direction matters. Additionally, an abs() method can be used to get the absolute shortest distance regardless of direction.

Ring360 is implemented as a tuple struct, encapsulating an f64 value with methods for accessing degrees(), rotations(), and value(). For example, Ring360(400.0) would have a raw value of 400.0, representing 40º in degrees with 1 rotation.

Values are kept within a circular 360º range, so negative values are automatically converted to their positive counterparts in the opposite direction (e.g., -60º is represented as 300º). Recognizing that geographic coordinates often use a ±180º range, the crate includes GIS-compatible conversions. The to_360_gis() method converts ±180º to the 360º format, and Ring360::from_gis(lng180: f64) performs the reverse conversion. This way, the crate seamlessly adapts to GIS systems such as QGIS, where angles between 180º and 360º are often expressed in a ±180º format.

These conversions preserve the actual angle (e.g., -60º in ±180º converts directly to 300º), though they may adjust the rotation count. Standard methods like to_360() and degrees() offer consistent handling of degrees within the 360º range.

Add and subtract degree values

/// Add and/or subtract degrees on a circle
/// The degree value represents the longitude, while the intrinsic f64 value
/// represents the angle travelled around the circumference
let longitude_1 = 74.7.to_360(); // or Ring360(74.7);
let longitude_2 = 291.4.to_360();
let longitude_3 = 126.1.to_360();

let result_1 = longitude_1 + longitude_2 + longitude_3;

println!(
  "Degrees: {:.1}º, intrinsic value is {:.1}, rotations: {}",
  result_1.degrees(),
  result_1.value(),
  result_1.rotations()
);
/// Should read: Degrees: 132.2º, intrinsic value is 492.2, rotations: 1

let result_2 = longitude_1 - longitude_2 + longitude_3;

println!("Degree value: {}º", result_2);
/// Should read: Degree value: 269.4º

Use ±180 GIS system, -180º to 180º

/// Declare two degrees on the GIS scale
let gis_lng_1 = 143.32;
let gis_lng_2 = -111.4;

/// Shift ±180 degrees to a 0º to 360º scale
let lng_360_1 = gis_lng_1.to_360_gis(); // or Ring360::rom_gis(74.7);
let lng_360_2 = gis_lng_2.to_360_gis();

let angle_3 = lng_360_1.angle(lng_360_2);

println!("The longitudinal distance between {} and {} is {}", lng_360_1.to_gis(), lng_360_2.to_gis(), angle_3);
/// The longitudinal distance between 143.32 and -111.4 is 105.28

Convert from and to f64

let value_1 = 74.7;
let value_2 = 291.4;

let longitude_1 = value_1.to_360();
let longitude_2 = value_2.to_360();

let result_1_f64 = (longitude_1 + longitude_2).degrees();

println!(
  "{}º +  {}º equals {}º",
  longitude_1,
  longitude_2,
  result_1_f64
);

Multiplication and Division

These are not implemented directly for degrees as Ring360 values, but only with primitive f64 values via the multiply() and divide() methods.

let value_1 = 74.7;
let factor = 4.0;

let result_1 = value_1.to_360().multiply(factor);

println!(
  "{}º multiplied by {} equals {}º",
  value_1,
  factor,
  result_1
);

let result_2 = value_1.to_360().divide(factor);

println!(
  "{}º divided by {} equals {}",
  value_1,
  factor,
  result_2
);

Angular Distance

The angle() and angle_f64() methods calculate the shortest angle in degrees between two longitudes in a circle. Negative values indicate an anticlockwise offset from A to B, e.g. from 340º to 320º would be negative, while 350º to 10º would be positive.

let value_1 = 297.4;
let longitude_1 = value_1.to_360();
let value_2: f64 = 36.2;
let longitude_2 = value_2.to_360();

let result_1 = longitude_1.angle(longitude_2);

let result_2 = longitude_1.angle_f64(value_2);

println!(
  "Both results should be the same: {} == {}",
  result_2,
  result_1
);

Absolute undirectional angles

The angle_abs() and angle_f64_abs() methods calculate the absolute angle in degrees in a clockwise direction between two longitudes in a circle. Values over 180.0 represent more than half a turn. For f64 value you may use .angle_360_abs(other_value: f64).

let value_1 = 270.0;
let longitude_1 = value_1.to_360();
let value_2: f64 = 30.0;
let longitude_2 = value_2.to_360();

let result_1 = longitude_1.angle_abs(longitude_2);
let result_2 = longitude_2.angle_abs(longitude_1);

println!(
  "The angles may be anywhere in the 0 to 359.9999º range: {} and {}",
  result_2,
  result_1
);

Calculate sine, cosine and tangent directly

let value_1 = 45.0;
  let degree_1 = value_1.to_360();

println!(
  "The sine of {}º is {:.12}",
  degree_1.degrees(),
  degree_1.sin()
);
// Should print: The sine of 45º is 0.707106781187

let value_2 = 60.0;
let degree_2 = value_2.to_360();

println!(
  "The cosine of {}º is {:.1}",
  degree_2.degrees(),
  degree_2.cos()
);
// Should print: The cosine of 60º is 0.5

Instance Methods

  • degrees()-> f64 Degree value from 0º to 360º.
  • to_f64() -> f64 Alias for degrees() and the default display value
  • to_gis() -> f64 Convert the internal 0-360º scale to the -180º to +180º GIS scale
  • rotations() -> i64 Number of rotations required to reach the current raw value, e.g. 730.0 would require 2 rotations with a degree value of 10.0.
  • progress() -> f64 the instrinsic value expressed as decimal fractions of progress around a circle, i.e. 180º translates to 0.5 and 450º to 1.25
  • value() -> f64 Raw f64 value
  • as_tuple() -> (f64, i64) Return a tuple with degrees as f64 and rotations as i64
  • multiply(multiple: f64) -> Self Multiply a Ring360 value by a normal f64 value
  • divide(divisor: f64) -> Self Divide a Ring360 by a normal f64 value
  • angle_f64(other_value: f64) -> f64 Calculate the shortest distance in degrees between a Ring360 value and a 64-bit float representing a degree. A positive value represents clockwise movement between the first and second longitude
  • angle(other_value: Ring360) -> f64 Angular distance between to Ring360 values
  • to_radians() -> f64 convert to radians for interoperability
  • sin() -> f64 sine (with implicit radian conversion, e.g. 30.to_360().sin() yields 0.5)
  • cos() -> f64 cosine
  • tan() -> f64 tangent
  • asin() -> f64 inverse sine
  • acos() -> f64 inverse cosine
  • atan() -> f64 inverse tangent

Static methods

  • half_turn() -> f64 Half of the 360º base, i.e. 180.0

Constants

  • BASE:f64 = 360.0

Traits

ToRing360

This is implemented only for f64 with the following methods:

  • to_360() -> Ring360 converts any 64-bit float representing a degree in the 360º system to Ring360 value;
  • to_360_gis() -> Ring360 converts any 64-bit float representing a degree in the ±180º system and normalises on the 0-360º scale.
  • mod_360() -> f64 A convenience method for % 360.0, but treating negative values as positive degrees in reverse, e.g. (-20.0).mod_360 equals 340º
  • angle_360(other_value: f64) -> f64: Calculates the shortest angle between two f64 values as degrees around a circle.
  • angle_360_abs(other_value: f64) -> f64: Calculates the absolute unidirectional angle between two f64 values as degrees around a circle.

Dev Notes

This is crate is in development, but implements all core features.

Version notes

0.2.11

  • angle_abs() -> f64 Returns the absolute undirectional angle, with related angle_f64_abs() and angle_360_abs() methods

0.2.8

  • progress() -> f64 Returns the rotations as decimal progress around a circle

0.2.6

The following methods have been added to Ring360

  • to_radians() for interoperability with other maths functions

The core trigonometric methods, sin(), cos(), tan(), asin(), acos() and atan() are calculated from degrees without explicitly converting to radians first:

  • .sin() -> f64 calculates sine
  • .cos() -> f64 calculates cosine
  • .tan() -> f64 calculates tangent
  • .asin() -> f64 calculates inverse sine (arcsine, asin)
  • .acos() -> f64 calculates inverse cosine (arccoine, acos)
  • .atan() -> f64 calculates inverse tangent (arctan, atan)

0.2.5

The following deprecated methods were removed from version 0.2.5:

  • degree() => use degrees() instead.
  • turns() => use rotations() instead.

0.2.10

  • the deprecated Ring360::from_180(-90.0) constructor and Ring360.to_180() method have been removed. Use Ring360::from_gis(-90.0) and Ring360.to_360_gis() instead. NB: This does not affect the calculated 360º degree value. It only affects only the calculated number of rotations, where -30 would convert to 330º with both to_360() and to_360_gis(), but have -1 rotations with the default cast method, but 0 rotations with the f64.to_360_gis().

0.2.12

  • Simplified the logic in ring360's angle_f64() method and added a new static method minus_half_turn() (-180).

No runtime deps