使用Python按顺时针角度排序二维坐标列表?

使用Python按顺时针角度排序二维坐标列表?,python,list,sorting,coordinates,angle,Python,List,Sorting,Coordinates,Angle,我有一组具有x和y坐标的点,可以在下图中看到。9个点的坐标存储在如下列表中: L = [[5,2], [4,1], [3.5,1], [1,2], [2,1], [3,1], [3,3], [4,3] , [2,3]] L = [[2,3], [3,3], [4,3], [5,2], [4,1], [3.5,1], [3,1], [2,1], [1,2]] 其思想是从原点顺时针对点进行排序。在这种情况下,原点是有颜色的点,并且有一个箭头指示排序的方向。不要担心创建方法来确定原点,因为它已经解

我有一组具有x和y坐标的点,可以在下图中看到。9个点的坐标存储在如下列表中:

L = [[5,2], [4,1], [3.5,1], [1,2], [2,1], [3,1], [3,3], [4,3] , [2,3]]
L = [[2,3], [3,3], [4,3], [5,2], [4,1], [3.5,1], [3,1], [2,1], [1,2]]
其思想是从原点顺时针对点进行排序。在这种情况下,原点是有颜色的点,并且有一个箭头指示排序的方向。不要担心创建方法来确定原点,因为它已经解决了

因此,在排序后,列表
L
应如下所示:

L = [[5,2], [4,1], [3.5,1], [1,2], [2,1], [3,1], [3,3], [4,3] , [2,3]]
L = [[2,3], [3,3], [4,3], [5,2], [4,1], [3.5,1], [3,1], [2,1], [1,2]]
请注意,x和y坐标没有更改。更改的是存储顺序

您是否知道用python语言解决这个问题的算法、脚本或方法


用一点三角学就没那么难了。也许你知道,但是两个(标准化)向量之间的角度是
acos(vec1*vec2)
。但是,这仅计算投影角度,但可以使用
atan2
计算方向感知角度

这意味着一个函数计算它,然后将它用作排序的
键是一个好方法:

import math

pts = [[2,3], [5,2],[4,1],[3.5,1],[1,2],[2,1],[3,1],[3,3],[4,3]]
origin = [2, 3]
refvec = [0, 1]

def clockwiseangle_and_distance(point):
    # Vector between point and the origin: v = p - o
    vector = [point[0]-origin[0], point[1]-origin[1]]
    # Length of vector: ||v||
    lenvector = math.hypot(vector[0], vector[1])
    # If length is zero there is no angle
    if lenvector == 0:
        return -math.pi, 0
    # Normalize vector: v/||v||
    normalized = [vector[0]/lenvector, vector[1]/lenvector]
    dotprod  = normalized[0]*refvec[0] + normalized[1]*refvec[1]     # x1*x2 + y1*y2
    diffprod = refvec[1]*normalized[0] - refvec[0]*normalized[1]     # x1*y2 - y1*x2
    angle = math.atan2(diffprod, dotprod)
    # Negative angles represent counter-clockwise angles so we need to subtract them 
    # from 2*pi (360 degrees)
    if angle < 0:
        return 2*math.pi+angle, lenvector
    # I return first the angle because that's the primary sorting criterium
    # but if two vectors have the same angle then the shorter distance should come first.
    return angle, lenvector
原点周围有一个矩形网格,这也能正常工作:

>>> origin = [2,3]
>>> refvec = [0, 1]
>>> pts = [[1,4],[2,4],[3,4],[1,3],[2,3],[3,3],[1,2],[2,2],[3,2]]
>>> sorted(pts, key=clockwiseangle_and_distance)
[[2, 3], [2, 4], [3, 4], [3, 3], [3, 2], [2, 2], [1, 2], [1, 3], [1, 4]]
即使更改了参考向量:

>>> origin = [2,3]
>>> refvec = [1,0]  # to the right instead of pointing up
>>> pts = [[1,4],[2,4],[3,4],[1,3],[2,3],[3,3],[1,2],[2,2],[3,2]]
>>> sorted(pts, key=clockwiseangle_and_distance)
[[2, 3], [3, 3], [3, 2], [2, 2], [1, 2], [1, 3], [1, 4], [2, 4], [3, 4]]

感谢
@Scott Mermelstein
提供了更好的函数名,感谢
@f5r5e5d
提供了
atan2
建议。

这应该能够说明问题,并提供了一个可视化工具

但并不是每次都能为相同距离的一组点找到正确的入口点

import random
import pylab
import cmath
from itertools import groupby 


pts = [(random.randrange(-5,5), random.randrange(-5,5)) for _ in range(10)]

# for this problem complex numbers are just too good to pass up

z_pts = [ i[0] + 1j*i[1] for i in pts if i != (0, 0)]

z_pts.sort(key = lambda x: abs(x))

gpts = [[*g] for _, g in groupby(z_pts, key = lambda x: abs(x) ) ]
print(*gpts, sep='\n')

spts = [1j/2]

for e in gpts:
    if len(e) > 1:
        se = sorted(e, key = lambda x: cmath.phase(-x / spts[-1]))
        spts += se
    else:
        spts += e

print(spts)

def XsYs(zs):
    xs = [z.real for z in zs]
    ys = [z.imag for z in zs]
    return xs, ys

def SpiralSeg(a, b):
    '''
    construct a clockwise spiral segment connecting
    ordered points a, b specified as complex numbers

    Inputs
        a, b complex numbers
    Output
        list of complex numbers
    '''
    seg = [a]
    if a == 0 or a == b:
        return seg
    # rotation interpolation with complex numbers!
    rot = ( b / a ) ** ( 1 / 30 ) 
    # impose cw rotation direction constraint
    if cmath.phase( b / a ) > 0: # add a halfway point to force long way around
        plr = cmath.polar( b / a )
        plr = (plr[0]**(1/2), plr[1] / 2 - 1 * cmath.pi ) # the rotor/2
        a_b = cmath.rect(*plr) * a   # rotate the start point halfway round   
        return SpiralSeg(a, a_b) + (SpiralSeg(a_b, b))

    for _ in range(30):
        a *= rot 
        seg.append(a)
    return seg  

segs = [SpiralSeg(a, b) for a, b in zip(spts, spts[1:])]

pylab.axes().set_aspect('equal', 'datalim')

pylab.scatter(*XsYs(z_pts))
for seg in segs:
   pylab.plot(*XsYs(seg))

[(1-2j), (-2-1j)]
[(2-3j)]
[(1+4j)]
[(3+3j)]
[(-3-4j), (3-4j), (4-3j)]
[(1-5j)]
[(-4-4j)]
[0.5j, (-2-1j), (1-2j), (2-3j), (1+4j), (3+3j), (-3-4j), (3-4j), (4-3j), (1-5j), (-4-4j)]


按角度排序是不够的
我们应该按极角和距原点的距离按字典顺序对点进行排序

我们按极角排序,如果是平局,我们按距离原点的距离排序

这个问题是不适定的。对于任意一组点,您对如何对点进行排序的描述是不确定的。例如:对于每个点和更新的原点pt,检查单位向量是否与上一个循环保持不变,以及大小是否是所有选项中的最小值。。。直到你用完维持初始单位向量的pts,然后移动到下一个。。。i、 向量向右,然后向下,然后向左,你有一个给定的r和θ。按r排序,然后按负θ排序。当然,当然,你会说,“我没有r和θ,我有x和y。”不要这么长方形……起初我有一条评论说“你应该对
lenvector
angle
进行排序”,但现在我读得更仔细了,我明白了
angle
函数就是这么做的。我可以建议您对函数进行重命名吗?我几乎可以称之为“直角坐标到极坐标”,但在这个问题上,你们的系统比极坐标更有用。不过,排序函数的一些类似名称有助于避免混淆。角度计算的问题:所有角度计算最多为mod 2*pi;acos包裹在pi处,约为0对称;atan2更好,但仍在2级*pi@f5r5e5d你说得对,我改变了答案。但是,
atan2
应足以表示
0
360
之间的所有角度(或
0
2*math.pi
).但是没有理由期望任意大的集合数据点可以通过不相交的分段螺旋轨迹连接,当按atan2排序mod 2*pi时-你真的需要保留一个角度历史记录,通过添加2的倍数来包装超过2*pi的角度*pi@f5r5e5d你有没有我建议的功能失败的例子?我已经测试了好几个案例,但我不知道螺旋跟踪是什么意思?我想他希望他们按顺时针方向的角度排序(或者如果那个角度相等,那么首先是最短的长度)。