【问题标题】:Sorting points by polar angle按极角排序点
【发布时间】:2021-06-19 12:34:24
【问题描述】:

我在按与 X 轴创建的角度对点进行排序时遇到问题。点看起来像这样

这是我的代码:

public static List<Point> SortPoints(List<Point> points)
        {
            List<Point> result = new List<Point>();
            List<KeyValuePair<Point, double>> valuePairs = new List<KeyValuePair<Point, double>>();
            foreach (var point in points)
            {
                valuePairs.Add(new KeyValuePair<Point, double>(point, Math.Atan2(point.Y, point.X)));
            }
            valuePairs = valuePairs.OrderByDescending(x => x.Value).ToList();
            foreach (var valuepair in valuePairs)
            {
                result.Add(valuepair.Key);
            }
            return result;
        }

它应该在途中对点进行排序,它们会关闭。它适用于大多数点,但不适用于其中一些点。它主要在这些片段上崩溃:

对于这类问题,我的想法是正确的还是我错过了什么?我对编程中的几何还是很陌生。

【问题讨论】:

标签: c# algorithm sorting geometry computational-geometry


【解决方案1】:

C# 翻译

public static List<Point> SortPoints(List<Point> points)
    {
        double[] pnt = new double[points.Count * 2];
        for (int z = 0, ii = 0; z < points.Count; z++, ii += 2)
        {
            pnt[ii] = points[z].X;
            pnt[ii + 1] = points[z].Y;
        }
        int n2 = pnt.Length;
        int n = n2 >> 1;
        int[] idx = new int[n];
        int i, j, k, i0, i1, e, m;
        double a, x0, y0, x1, y1, x, y;
        double[] ang = new double[n];
        int[] flag = new int[n];
        const double deg = Math.PI / 180.0;
        const double thr = 0.02 * deg;
        for (i = 0; i < n; i++) idx[i] = i + i;
        x0 = x1 = pnt[0];
        y0 = y1 = pnt[1];
        for (i = 0; i < n2;)
        {
            x = pnt[i]; i++;
            y = pnt[i]; i++;
            if (x0 > x) x0 = x;
            if (x1 < x) x1 = x;
            if (y0 > y) y0 = y;
            if (y1 < y) y1 = y;
        }
        x = 0.5 * (x0 + x1);
        y = 0.5 * (y0 + y1);
        for (i = 0, j = 0; i < n; i++, j += 2)
        {
            a = Math.Atan2(pnt[j + 1] - y, pnt[j + 0] - x);
            ang[i] = a;
        }
        for (e = 1, j = n; e != 0; j--)                                 // loop until jo swap occurs
            for (e = 0, i = 1; i < j; i++)                              // proces unsorted part of array
                if (ang[idx[i - 1] >> 1] > ang[idx[i] >> 1])              // condition if swap needed
                { e = idx[i - 1]; idx[i - 1] = idx[i]; idx[i] = e; e = 1; }
        for (i = 0; i < n; i++) flag[i] = 0;
        for (e = 0, j = 1, i = 1; i < n; i++)
            if (Math.Abs(ang[idx[i] >> 1] - ang[idx[i - 1] >> 1]) < thr)
            { flag[idx[i] >> 1] = j; flag[idx[i - 1] >> 1] = j; e = 1; }
            else if (e != 0) { e = 0; j++; }
        if (e != 0) j++; m = j;
        x = x0 + (0.3 * (x1 - x0));
        y = 0.5 * (y0 + y1);
        for (i = 0, j = 0; i < n; i++, j += 2)
            if (flag[i] != 0)                                      // only for problematic zones no need to recompute finished parts
            {
                a = Math.Atan2(pnt[j + 1] - y, pnt[j + 0] - x);                 // this might need handling edge cases of atan2
                ang[i] = a;
            }
        for (k = 0; k < n;)
        {
            for (; k < n; k++) if (flag[idx[k] >> 1] != 0)                     // zone start
                {
                    i0 = i1 = k;
                    for (; k < n; k++) if (flag[idx[k] >> 1] != 0) i1 = k;//           // zone end
                        else break;
                    // index (bubble) sort idx[] asc by ang[]
                    if (i0 != i1)
                        for (e = 1, j = i1 - i0 + 1; e > 0; j--)                          // loop until jo swap occurs
                            for (e = 0, i = i0 + 1; i < i0 + j; i++)                       // proces unsorted part of array
                                if (ang[idx[i - 1] >> 1] > ang[idx[i] >> 1])             // condition if swap needed
                                { e = idx[i - 1]; idx[i - 1] = idx[i]; idx[i] = e; e = 1; } // swap and allow to process array again
                                                                                            // different center for atan2 might reverse the ang order
                                                                                            // so test if start or end of zone is closer to the point before it
                    j = i0 - 1; if (j < 0) j = n - 1;                             // 45 deg is never at start or end of data so this should never happen
                    x = pnt[idx[j] + 0] - pnt[idx[i0] + 0];
                    y = pnt[idx[j] + 1] - pnt[idx[i0] + 1];
                    a = (x * x) + (y * y);
                    x = pnt[idx[j] + 0] - pnt[idx[i1] + 0];
                    y = pnt[idx[j] + 1] - pnt[idx[i1] + 1];
                    x = (x * x) + (y * y);
                    // reverse if not in correct order
                    if (x < a) for (; i0 < i1; i0++, i1--)
                        { j = idx[i0]; idx[i0] = idx[i1]; idx[i1] = j; }
                }
        }
        List<Point> result = new List<Point>();
        for (int h = 0; h < pnt.Length - 1; h += 2)
        {
            result.Add(new Point(pnt[h], pnt[h + 1]));
        }
        return result;
    }

【讨论】:

    【解决方案2】:

    好的,我只使用 2 个中心按atan2 角度进行排序(不需要 3 个,因为我可以直接从第一个中心检测问题区域)。这是算法(形状不能自相交并且围绕选定中心的角度必须是单调的!!!):

    1. 为您的数据计算 BBOX (x0,y0,x1,y1)

      这是正确计算 atan2 使用的正确中心位置所必需的

    2. 以 BBOX 中心为中心,通过 atan2 计算每个点的角度

      中心应该在你的形状内,所以 BBOX 的中心是显而易见的选择。然而,正如 Yves Daoust 指出的那样,这不适用于任意凹形,仅适用于角度单调的形状和中心。

    3. 按这个角度对你的点进行排序

    4. 检测有问题的区域

      仅在有问题的区域中,排序后的结果点具有几乎相同的角度,因此只需阈值即可。

    5. 为每个具有不同中心的问题区域计算atan2角度

      中心必须在内部......并且应该从原始中心以任何 90 度角的倍数移动。我选择向x0 移动形状x 大小的20%。变化越大,问题区域的角度差异就越大。

    6. 按新角度对问题区域进行排序

    7. 如果需要,在排序后反转问题区域顺序

      与原始角度相比,偏移的中心可能会导致角度方向反转。因此,排序后,如果您计算区域之前的点与区域第一个点和最后一个点之间的距离,如果区域的最后一个点更近,则意味着您需要反转区域点的顺序。

    此处预览输出:

    这里是 C++/OpenGL/VCL 代码:

    //---------------------------------------------------------------------------
    #include <vcl.h>
    #include <math.h>
    #pragma hdrstop
    #include "Unit1.h"
    #include "gl_simple.h"
    #include "data.h"
    //---------------------------------------------------------------------------
    #pragma package(smart_init)
    #pragma resource "*.dfm"
    TForm1 *Form1;
    //---------------------------------------------------------------------------
    const int n2=sizeof(pnt)/sizeof(pnt[0]);    // size of pnt[]
    const int n=n2>>1;                          // point in pnt[]
    int idx[n];                                 // index sort
    int ix=1055;                                // just debug X cursor on mouse wheel
    //---------------------------------------------------------------------------
    void compute()
        {
        int i,j,k,i0,i1,e,m;
        float a,x0,y0,x1,y1,x,y;
        float ang[n];   // atan2 angles per point
        int flag[n];    // 0 or problem zone ix per point
        const float deg=M_PI/180.0;
        const float thr=0.02*deg;
        // shuffle input data for debug as its already ordered
        for (i=0;i<n2;)
            {
            j=Random(n2)&0xFFFFFFFE;
            a=pnt[i]; pnt[i]=pnt[j]; pnt[j]=a; i++; j++;
            a=pnt[i]; pnt[i]=pnt[j]; pnt[j]=a; i++; j++;
            }
        // init index sort table
        for (i=0;i<n;i++) idx[i]=i+i;
        // compute BBOX of data
        x0=x1=pnt[0];
        y0=y1=pnt[1];
        for (i=0;i<n2;)
            {
            x=pnt[i]; i++;
            y=pnt[i]; i++;
            if (x0>x) x0=x;
            if (x1<x) x1=x;
            if (y0>y) y0=y;
            if (y1<y) y1=y;
            }
        // compute atan2 for center set to center of BBOX
        x=0.5*(x0+x1);
        y=0.5*(y0+y1);
        for (i=0,j=0;i<n;i++,j+=2)
            {
            a=atan2(pnt[j+1]-y,pnt[j+0]-x);                 // this might need handling edge cases of atan2
            ang[i]=a;
            }
        // index (bubble) sort idx[] asc by ang[]
        for (e=1,j=n;e;j--)                                 // loop until no swap occurs
         for (e=0,i=1;i<j;i++)                              // process unsorted part of array
          if (ang[idx[i-1]>>1]>ang[idx[i]>>1])              // condition if swap needed
          { e=idx[i-1]; idx[i-1]=idx[i]; idx[i]=e; e=1; }   // swap and allow to process array again
        // detect/label problematic zones m = number of zones +1
        for (i=0;i<n;i++) flag[i]=0;
        for (e=0,j=1,i=1;i<n;i++)
         if (fabs(ang[idx[i]>>1]-ang[idx[i-1]>>1])<thr)
          { flag[idx[i]>>1]=j; flag[idx[i-1]>>1]=j; e=1; }
          else if (e){ e=0; j++; }
        if (e) j++; m=j;
        // compute atan2 for center shifted toward x0
        // so it still inside but not too close to (0,0)
        // so there is some ang diference on problematic zones
        x=x0+(0.3*(x1-x0));
        y=0.5*(y0+y1);
        for (i=0,j=0;i<n;i++,j+=2)
         if (flag[i])                                       // only for problematic zones no need to recompute finished parts
            {
            a=atan2(pnt[j+1]-y,pnt[j+0]-x);                 // this might need handling edge cases of atan2
            ang[i]=a;
            }
        // loop through problematic zones
        for (k=0;k<n;)
            {
            for (;k<n;k++) if (flag[idx[k]>>1])                     // zone start
                {
                i0=i1=k;
                for (;k<n;k++) if (flag[idx[k]>>1]) i1=k;           // zone end
                  else break;
                // index (bubble) sort idx[] asc by ang[]
                if (i0!=i1)
                 for (e=1,j=i1-i0+1;e;j--)                          // loop until no swap occurs
                  for (e=0,i=i0+1;i<i0+j;i++)                       // process unsorted part of array
                   if (ang[idx[i-1]>>1]>ang[idx[i]>>1])             // condition if swap needed
                    { e=idx[i-1]; idx[i-1]=idx[i]; idx[i]=e; e=1; } // swap and allow to process array again
                // different center for atan2 might reverse the ang order
                // so test if start or end of zone is closer to the point before it
                j=i0-1; if (j<0) j=n-1;                             // 45 deg is never at start or end of data so this should never happen
                x=pnt[idx[j]+0]-pnt[idx[i0]+0];
                y=pnt[idx[j]+1]-pnt[idx[i0]+1];
                a=(x*x)+(y*y);
                x=pnt[idx[j]+0]-pnt[idx[i1]+0];
                y=pnt[idx[j]+1]-pnt[idx[i1]+1];
                x=(x*x)+(y*y);
                // reverse if not in correct order
                if (x<a) for (;i0<i1;i0++,i1--)
                 { j=idx[i0]; idx[i0]=idx[i1]; idx[i1]=j; }
                }
            }
        }
    //---------------------------------------------------------------------------
    void gl_draw()
        {
        int i,j;
        float a,da=1.0/float(n-1),x,y,r;
        glClear(GL_COLOR_BUFFER_BIT);
        glDisable(GL_DEPTH_TEST);
        glDisable(GL_TEXTURE_2D);
    
        // set view to 2D
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glScalef(1.0/120.0,1.0/120.0,1.0);
    
        // render points from list
        glBegin(GL_POINTS);
    //  glBegin(GL_LINE_STRIP);
    //  glBegin(GL_TRIANGLE_FAN);
        glColor3f(0.0,0.0,0.0);
        glVertex2f(0.0,0.0);
        for (a=0.0,i=0;i<n;i++,a+=da)
            {
            glColor3f(a,a,a);
            glVertex2fv(pnt+idx[i]);
            }
        glEnd();
    
        // render debug index (on mouse wheel)
        x=pnt[idx[ix]+0];
        y=pnt[idx[ix]+1];
        r=5.0;
        glBegin(GL_LINES);
        glColor3f(0.0,1.0,0.0);
        glVertex2f(x-r,y-r);
        glVertex2f(x+r,y+r);
        glVertex2f(x-r,y+r);
        glVertex2f(x+r,y-r);
        glEnd();
    
        glFinish();
        SwapBuffers(hdc);
        Form1->Caption=ix;
        }
    //---------------------------------------------------------------------------
    __fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
        {
        // Init of program
        gl_init(Handle);    // init OpenGL
        compute();
        }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::FormDestroy(TObject *Sender)
        {
        // Exit of program
        gl_exit();
        }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::FormPaint(TObject *Sender)
        {
        // repaint
        gl_draw();
        }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::FormResize(TObject *Sender)
        {
        // resize
        gl_resize(ClientWidth,ClientHeight);
        }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::FormMouseWheel(TObject *Sender, TShiftState Shift, int WheelDelta, TPoint &MousePos, bool &Handled)
        {
        Handled=true;
        int dix=1; if (Shift.Contains(ssShift)) dix=10;
        if (WheelDelta>0){ ix+=dix; if (ix>=n) ix=  0; }
        if (WheelDelta<0){ ix-=dix; if (ix< 0) ix=n-1; }
        gl_draw();
        }
    //---------------------------------------------------------------------------
    

    忽略 VCL 和 OpenGL 的东西。这里唯一重要的是函数compute(),它的工作原理如上所述。就在它上面的全局变量。但是我使用了索引排序,所以点的顺序根本没有改变,而是idx[i] 在输入数据中保存i-th 点的索引。我想尽可能简单,所以没有动态分配、容器或有趣的东西……我用你的数据作为表单的输入:

    float pnt[]=
        {
        -98.622,0.4532042,
        -98.622,1.64291,
        -98.612097,3.0877569,
        ...
        -98.618994,-3.2649391,
        -98.6260115,-1.9205891
        };
    

    我用于 OpenGL 的 gl_simple.h 可以在这里找到:

    我通过使用光标(图像上的绿色十字)和鼠标滚轮在整个形状中寻找来回跳跃来测试这一点......在以前的版本中,我将flag[] 数组作为全局数组并为问题区域,所以我可以直接调试它们,而不是一次又一次地寻找整个形状......我也使用了冒泡排序......如果你有大量数据,请使用快速排序......但是你提供的这些数据是立即计算的我的旧电脑,所以我没有费心添加递归。

    对于任意凹形非自相交形状,您需要使用不同的方法,例如连通分量分析:

    1. 为每个点计算其 2 个最近邻点

      这非常慢,应该通过使用点的空间排序来降低复杂性来加快速度。

      如果采样非常不均匀,两个最近的点不应位于或靠近同一方向!!!所以他们的单位方向之间的点应该尽可能远离+1.0。

    2. 选择任意起点并将其添加到输出中

    3. 选择其中一个尚未使用的邻居并将其添加到输出

    4. 设置所选点与其前一个点之间的链接。

    5. 循环 #4 直到您再次到达起点

    【讨论】:

    • 这个“e”条件在这里是什么意思for (e=1,j=n;e;j--)? int 如何成为布尔值?
    • @maciejpuzianowski 它的冒泡排序结束条件...如果数据已经排序,则无需迭代(最后一次没有交换)...是的ints 可以是boolean ...所以条件(e) 表示:e==0falsee!=0true .. 这在C/C++ 语言中很常见,因此您可以将其重写为(e!=0),其结果与(e)
    • 我已将您宝贵的代码翻译成 c#,但是我在 if (ang[idx[i-1]&gt;&gt;1]&gt;ang[idx[i]&gt;&gt;1]) // condition if swap needed { e=idx[i-1]; idx[i-1]=idx[i]; idx[i]=e; e=1; } // swap and allow to process array again 中得到了一个 IndexOutOfRangeException,如果。我会发布我的翻译。
    • @maciejpuzianowski 我的输入点是一维数组,持有 { x0,y0, x1,y1, x2,y2, ...} 所以第一个点在索引 0,下一个在索引 2,下一个在索引 4,下一个在索引 6 ......所以一开始我喂idx[] = { 0,2,4,6,8,... } 现在当你想访问第 i 个点时使用 pnt[idx[i]] 但是所有其他表都不是 2D 向量,而只是标量,因此索引必须除以 2:ang[idx[i]&gt;&gt;1] , flag[idx[i]&gt;&gt;1] ...现在如果你有索引超出范围可能有 2 个常见原因:1. 您的语言索引从 1 而不是 0 开始
    • 我发现了这个错误,我只是输错了一个变量。你的代码工作得很好。非常感谢您的帮助。你是老板。
    猜你喜欢
    • 2019-06-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多