2

I'm trying to make a program to get azimuth value from two points using this code:

import math


def azimuth(x1, x2, y1, y2):
    temp = (y2 - y1) / (x2 - x1)
    res = math.degrees(abs(math.atan(temp)))
    if x2 > x1 and y2 > y1:    #Q1
        res = 90 - res
    elif x2 < x1 and y2 > y1:  #Q2
        res = (90 - res) * -1
    elif x2 < x1 and y2 < y1:  #Q3
        res = (90 + res) * -1
    elif x2 > x1 and y2 < y1:  #Q4
        res += 90
    else:
        raise ValueError('No point difference.')
    return res

I've been able to get azimuth value with range (-180)-180. Next, I need to split it into two groups of azimuth value equaly based on range of value. The goal is to get two group which get the closest azimuth value.

The problem is that pairs of points on quadrant III ((-180)-(-90)) and quadrant IV (90-180) logically can be consider as close (e.g:(-179) and 179). About how to split it equally, I've been thinking of using sorted list of azimuth and using index idx = int(math.ceil((len(lst)-1)/2)) as the median to split the list. This is how I do it on code:

lst = [-60, -50, -40, -30, -20, -10, 10, 20, 30, 40, 50, 60]
def split_list(lst):
    lst.sort()
    idx = int(math.ceil((len(lst)-1)/2))
    left = []
    right = []
    for i in lst:
        if i < lst[idx]:
            left.append(i)
        else:
            right.append(i)
    print(left)
    print(right)
    return left, right

split_list(lst)

The code above will return list [-60, -50, -40, -30, -20, -10] and [10, 20, 30, 40, 50, 60] as the result.

Is there any idea of how to do this? With the current condition, I don't have any idea of how to make quadrant III and IV considered as close.

Kela
  • 61
  • 4
  • Do I understand correctly that you have a set of coordinates that you want to split into two sets with minimal radial distance among the coordinates? Should the number of elements in the sets be equal, or the sum of the residuals be minimal? – M.G.Poirot Mar 15 '22 at 14:07
  • Yes, I have a set of coordinates that wanted to be split into two sets with minimal radial distance among the coordinates. The number of elements in the sets tried to be equal as possible. – Kela Mar 16 '22 at 03:20
  • Two observations: 1) isn't this what `atan2` is for? 2) Is this question actually about the math at all? I don't think the code is relevant to the *problem you describe*, since you are only asking "how do I bin the values together?" which would be the same problem no matter how you actually *got* the values. Please read [ask] and https://stackoverflow.com/help/minimal-reproducible-example, and try to ask the *actual question you have*. – Karl Knechtel Mar 16 '22 at 04:17
  • "I need to split it into two groups of azimuth value equaly based on range of value." **What does this actually mean?** "Based on" **how**? Please show an example of the input azimuth values, how that input should be split, and explain the underlying logic. Make sure it's complex enough to illustrate why it isn't an easy problem, but no more complex than necessary. – Karl Knechtel Mar 16 '22 at 04:18
  • I just done editing the question explaination. First of all, yes `atan2` seem better to use for this case. The point of my question is about how to split it into two equally list, which every azimuth on the list can be consider as near. With using function `split_list(list)`, it won't let the nearest pairs of points between QIII and QIV (e.g: (-179) and 179) became member of only one splited list. – Kela Mar 16 '22 at 05:45
  • I've updated my answer, with an example of how to split azimuths, taking into account that 359° is only 1° away from 0°. – Eric Duminil Mar 16 '22 at 12:47

2 Answers2

1

You might be interested in math.atan2(y, x).

The point of atan2() is that the signs of both inputs are known to it, so it can compute the correct quadrant for the angle.

Which means that you can remove all the quadrant logic from your code, and basically replace the whole function with math.degrees(math.atan2(y2 - y1, x2 - x1)). It also avoids a division by 0 if x1 == x2.

If you have two azimuths, you can calculate the minimum rotation between both with:

def shortest_rotation(start_angle, end_angle):
    return (end_angle - start_angle + 180) % 360 - 180

You can split the list of azimuths depending on the sign of the output.

Just for fun, here's a small script to split azimuths left and right of split_at_azimuth, and display them on a polar plot:

def shortest_rotation(start_angle, end_angle):
    return (end_angle - start_angle + 180) % 360 - 180

azimuths = list(range(0, 370, 10))
split_at_azimuth = 60

left, right = [], []
for azimuth in azimuths:
    (left, right)[shortest_rotation(azimuth, split_at_azimuth) >= 0].append(azimuth)

import numpy as np
import matplotlib.pyplot as plt

t1 = [np.pi * a / 180 for a in right]
t2 = [np.pi * a / 180 for a in left]

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
ax.plot(t1, [1 for _ in t1], '+')
ax.plot(t2, [1 for _ in t2], '+')
ax.set_rmax(2)
ax.set_rticks([])
ax.grid(True)

ax.set_title("Left and right azimuth", va='bottom')
plt.show()

enter image description here

Eric Duminil
  • 52,989
  • 9
  • 71
  • 124
0

Your problem can be reduced to finding a distance between two phase points. I say phase because phase is wrapped (in your case % 360). Thus, you need to compare the distance both ways: clockwise and counter-clockwise. The simplest might be:

dist = min(abs(A2-A1), abs(360+A1-A2))  # requiring A1 <= A2

If the clockwise distance is the smallest, then the first term in min() will be your solution. If counter-clockwise, then the second term in min() will be your solution. To do grouping, you'd then calculate the distances between desired points. Note that arrays don't need to be sorted for this to work, just that A1 <= A2.

Andrej Prsa
  • 551
  • 3
  • 14