This is a guide on creating a Python class for representing and performing calculations with angles, supporting a variety of unit types, and providing operators for arithmetic, comparison, and approximate equality.
Abstract
The article is about the creation of the Angle class in Python, which is designed to simplify the process of dealing with angles in programming. This class supports multiple units such as degrees, minutes, seconds, radians, gradians, turns, hour angles, points, and quadrants. All angle values are stored as seconds for calculations and comparisons. Arithmetic and comparison operators are implemented to enable performing calculations and comparisons on angles using different units. The article also discusses the implementation of the approximately equal function to handle any inaccuracies caused due to using Pi in calculating radian values. The str method is also implemented to display the value and unit of the angle in a user-friendly format. Finally, the article provides several examples demonstrating the functionalities of the Angle class.
Bullet points
The article is about a Python class called Angle that makes dealing with angle measurements in code easier and more flexible.
The Angle class represents angles using seven different unit types, including degrees, minutes, seconds, radians, gradians, turns, hour angles, points, and quadrants.
All values are stored as seconds (arcseconds) to make calculations and comparisons across different instances of the class easier and more accurate, regardless of the unit of measurement used.
The class provides a value property, which converts the angle value to or from the chosen unit using the unit's functions.
The Angle class implements arithmetic dunder methods using the angle's stored value in seconds, performing calculations regardless of the chosen unit and returning a result in the first angle's unit.
Comparison operators are provided to enable comparing angles from different instances of the Angle class, regardless of the units used.
A function called approximate_equal is provided for comparing angle values that should be equal but may vary slightly due to rounding or calculation inaccuracies.
The angle class uses a MappingProxyType to store data for each unit, consisting of the unit name and three methods for converting to and from seconds, and generating a human-readable representation.
Creating an Angle Class in Python
In this project I will write a Python class to represent angles in various units, and provide a selection of arithmetic and comparison operators.
Overview
There is a surprising number of different units used to measure angles. Dealing with them in code can be fiddly for a number of reasons so I have written a class to make the process easier.
The class records not only the angle itself but also the unit you wish to use, for which there is a choice of no less than seven. Whichever unit you choose, the angle value itself is stored in seconds. A second (strictly second of arc or arcsecond) is one sixtieth of a minute (minute of arc or arcminute), which is one sixtieth of a degree. Yes, degrees are sliced up in the same way as hours. The reason for the value being stored in arcseconds is to simplify calculations and comparisons using different instances of the Angle class which may use different units.
In addition to a value and unit the class also provides a selection of arithmetic and comparison operators to simplify using instances in expressions. Due to roundings and slight inaccuracies caused by using Pi to calculate radian values, different angles which are intended to be equal might be slightly different, messing up our comparison operators. I have therefore provided a function to test for approximate equality which uses the rather obscure isclose method provided by Python’s math module.
Finally, the __str__ dunder method is implemented to provide a standard and human-friendly string representation of the value and unit.
Now let’s take a brief look at the seven units of angle provide by the Angle class.
Degrees, minutes and seconds — 360 in a circle, each degree is divided into 60 arcminutes, which are divided into 60 arcseconds
Radians — see below
Gradians — 400 in a circle, therefore 100 in a right angle
Turn — 1 in a circle
Hour Angle — 24 in a circle
Point — one unit of the 32 points of a compass
Quadrant — 4 in a circle
Radians
The diagram below illustrates the radian. If you draw a line from the centre of a circle to the edge, another the same length round the circle, and a third back to the centre then the angle where the lines meet at the centre is 1 radian. There are π radians in 180 degrees, and the number of degrees in 1 radian is 180 / π, or approximately 57.29577951. As π is irrational it is impossible to calculate this with absolute accuracy.
The angle.py file contains the Angle class, and the angledemo.py contains various functions to show the class in action.
The Code
This is the full code listing for the Angle class.
units
This consists of a MappingProxyType (basically an immutable dictionary) with the unit names as keys and inner dictionaries as values. As well as a name the inner dictionaries contain three functions to convert the value to and from seconds, and provide a human readable representation.
The logic associated with each unit is therefore kept together in one neat package, and the whole is easily extensible for other units. You might consider this “design pattern” to be ingenious, or on the other hand you might find it freakish and bizarre . . . !
__init__
This is pretty straightforward, just setting the variables for the value and unit. Note though that the value is converted to seconds using the method associated with the relevant unit.
value Property
As values are stored as seconds the value getter converts seconds to the actual unit before returning it. Similarly, the setter converts units to seconds.
__str__
Here we convert the value from seconds, and then call the unit’s tostring method.
approx_equal
As I mentioned above small inaccuracies can creep into the seconds value used to store all values. This method allows the values of two instances of the Angle class to be compared for equality even if they are slightly different. It uses the math.isclose function provided for us by Python.
The isclosefunction can take optional rel_tol (relative tolerance) or abs_tol (absolute tolerance) arguments although here I use the default, rel_tol=1e-09, which the Python documentation states “assures that the two values are the same within about 9 decimal digits”.
It’s worth taking a minute to read the math.isclose function’s documentation.
These implement the various arithmetic dunder methods. The actual arithmetic is carried out on the seconds values so that we can carry out calculations with angles of different units. Then the value in the unit of the first angle is calculated, and a new Angle instance returned, again with the unit of the first angle.
Comparison Operators
These are all straightforward one-line implementations, again using seconds to enable angles of different units to be compared.
Let’s Try It Out . . .
The Angle class is now complete so we need some bits of code to try it out.
The main Function
This just has six calls to functions demoing the various aspects of the Angle class. We can comment/uncomment them and run one at a time.
The create_and_output Function
For each of the seven units an Angle instance is created, and the unit name, seconds value, unit value and string value are printed.
For the degreeminutesecond unit a tuple is passed to __init__, the other units just needing a single value.
Run the code like this:
python3 angledemo.py
This is the output of the create_and_output function.
The set_value Function
This is to demonstrate that value can be set, but the new value needs to be in the Angle instance’s unit, in this case a tuple of degrees, minutes and seconds.
Uncomment set_value() in main and run the program. This is the output.
The arithmetic Function
Firstly we create two Angle objects, one with a unit of degreeminutesecond and the other with quadrant. This is to show that it is possible to mix up units.
Next five new Angle objects are created, each the result of a calculation using the first two angles. There are two multiplications, dms * 2 which uses __mul__, and 3 * dms which uses __rmul__.
We then print the original two angles followed by the various results. Uncomment arithmetic() in main and run the program again.
The comparisons Function
Three Angle objects are created and then used with the six comparison operators within print functions. As with the arithmetic function the angles use different units just to show that we can.
This is the output from the comparisons Function.
The approx_equal Function
Firstly I created two Angle objects, the first for 360 degrees and the second for 2π radians, the two being equivalent. If you sneak at the output below you’ll see that the equality operator returns True as both values resolve to the same value in seconds.
However, dividing each by 3 introduces a tiny difference between the seconds values so the == operator returns False, but approx_equal returns True as the two values are within the tolerances.
The convert_units Function
Changing the unit of an Angle object is easy — just do it. As the value is stored as seconds changing the unit has no effect on it, but in future output will of course be shown in the new unit. In the convert_units function we start of with a degreeminutesecond angle, then convert it to quadrant, and finally to gradian. As you can see from the output below the value is retained and output correctly.
I hope you found this article interesting and useful. Even if you have no need for an Angle class a few of the language features such as arithmetic and comparison operators, and the math.isclose function, might be useful for other projects.
This article was previously published on CodeDrome