How to elegantly find if an angle is between a range?

Viewed 682

For example, if my angle is 20 degrees, I can check if it is within [0, 40] like so:

lower = 0
upper = 40
if angle > lower and angle < upper:
        print('done')

However this logic will not work if my range crosses the 360 degree boundary (e.g. lower=350 and upper=40). I know I can handle this with additional if/else block but is there any elegant way?

3 Answers

An easy way to do check if alpha is between lower and upper is to mesure angles modulo 360 from lower. This gives us positive values between 0 and 360.

We just have to check if (alpha - lower) modulo 360 is smaller than (upper - lower) modulo 360, as you can see on the illustrations:

alpha in the range:

alpha in the range

alpha out of the range:

enter image description here

def angle_in_range(alpha, lower, upper):
    return (alpha - lower) % 360 <= (upper - lower) % 360


tests = [(25, 20, 40), (15, 20, 40), (45, 20, 40), (10, 350, 20), (-5, 350, 20), (365, 350, 20), (330, 350, 20)]
for alpha, lower, upper in tests:
    print(f'{alpha} between {lower} and {upper}: {angle_in_range(alpha, lower, upper)}')

# 25 between 20 and 40: True
# 15 between 20 and 40: False
# 45 between 20 and 40: False
# 10 between 350 and 20: True
# -5 between 350 and 20: True
# 365 between 350 and 20: True
# 330 between 350 and 20: False

First, make sure that all angles are modulo 360. Then, if the range appears backwards, reverse the condition.

So for example:

def in_range(lower, upper, angle):
    lower %= 360
    upper %= 360
    angle %= 360
    if lower > upper:
        return angle >= lower or angle <= upper
    return angle > lower and angle < upper

You can compute cross products between the pairs of vectors, and based on their signs find out their relative orientation.

Consider that you have vectors A, B and C and you want to check whether C stays between A and B. In this case if the cross products A x C, C x B and A x B have the same sign, then it does.

In this solution it does not matter which angle is upper and which is lower.

Reference: https://gamedev.stackexchange.com/questions/22392/what-is-a-good-way-to-determine-if-a-vector-is-between-two-other-vectors-in-2d

import math


def is_between(angle, first, second):
    angle = math.radians(angle)
    first = math.radians(first)
    second = math.radians(second)

    return (
        sign(cross_product(first, angle)) ==
        sign(cross_product(angle, second)) ==
        sign(cross_product(first, second))
    )


def cross_product(first, second):
    first_x = math.cos(first)
    first_y = math.sin(first)

    second_x = math.cos(second)
    second_y = math.sin(second)

    return first_x * second_y - first_y * second_x


def sign(x):
    return math.copysign(1, x)

Tests:

import pytest

from angle import is_between


@pytest.mark.parametrize(
    'angle, first, second, expected',
    [
        (20, 0, 40, True),
        (20, 30, 10, True),
        (20, 30, 350, True),
        (-5, 30, 350, True),
        (-5, 30, 350, True),
        (40, 30, 350, False),
        (340, 30, 350, False),
        (-20, 30, -10, False),
        (25, 15, 270, False),
        (25, 15, 90, True),
        (25, 15, 194, True),
        (25, 15, 270, False),
        (90, 0, 179, True),
        (90, 0, 181, False),
        (90, 0, -179, False),
    ],
)
def test_is_between(angle, first, second, expected):
    result = is_between(angle, first, second)
    assert result == expected
Related