飞道的博客

C# 求取圆心/球心坐标 ∈ C# 编程笔记

912人阅读  评论(0)

一、求圆心坐标

1.算法原理


如上图所示,已知圆上两点P1, P2 点坐标和圆半径,求圆心点O 的坐标。
1.先来个简单

所以:三点唯一确定一个圆。
但是如果点数不止三个,有多个点呢,这个时候就要平差了。
2.平差法

可用上面的 简单求解法 来求初值(最开始迭代的近似值)。

2.代码实现

		/*此函数用简单方法求圆心坐标
         * 输入:R 半径, p1 p2 已知的圆上两点
         * 输出:center 俩圆心点数组
         * 有个弊端:p1,p2 的x 不能相等
         */
        public static Point[] YSx(double R, Point p1, Point p2)
        {
            Point[] center = new Point[2];
            double x1 = p1.x, y1 = p1.y, x2 = p2.x, y2 = p2.y;
            double C1 = (x2 * x2 - x1 * x1 + y2 * y2 - y1 * y1) / (2 * (x2 - x1)),
                C2 = (y2 - y1) / (x2 - x1),
                A = 1 + C2 * C2,
                B = 2 * ((x1 - C1) * C2 - y1),
                C = (x1 - C1) * (x1 - C1) + y1 * y1 - R * R;
            double y = (-B + Math.Sqrt(B * B - 4 * A * C)) / (2 * A);
            center[0] = new Point(C1 - C2 * y, y);
            y = (-B - Math.Sqrt(B * B - 4 * A * C)) / (2 * A);
            center[1] = new Point(C1 - C2 * y, y);
            return center;
        }
        /*此函数用简单方法求圆心坐标
         * 输入:R 半径, p1 p2 已知的圆上两点
         * 输出:center 俩圆心点数组
         * 有个弊端:p1,p2 的y 不能相等
         */
        public static Point[] YSy(double R, Point p1, Point p2)
        {
            Point[] center = new Point[2];
            double x1 = p1.x, y1 = p1.y, x2 = p2.x, y2 = p2.y;
            double C1 = (x2 * x2 - x1 * x1 + y2 * y2 - y1 * y1) / (2 * (y2 - y1)),
                C2 = (x2 - x1) / (y2 - y1),
                A = 1 + C2 * C2,
                B = 2 * ((y1 - C1) * C2 - x1),
                C = (y1 - C1) * (y1 - C1) + x1 * x1 - R * R;
            double x = (-B + Math.Sqrt(B * B - 4 * A * C)) / (2 * A);
            center[0] = new Point(x, C1 - C2 * x);
            x = (-B - Math.Sqrt(B * B - 4 * A * C)) / (2 * A);
            center[1] = new Point(x, C1 - C2 * x);
            return center;
        }
        /*此函数用简单方法求圆心坐标
         * 输入:R 半径, p1 p2 已知的圆上两点
         * 输出:center 俩圆心点数组
         * 解决了上述两弊端
         */
        public static Point[] YS(double R, Point p1, Point p2)
        {
            if ((p1.x - p2.x) != 0) return YSx(R, p1, p2);
            else return YSy(R, p1, p2);
        }
        /*此函数用平差方法求圆心坐标
         * 输入:R 半径, pt 已知点数组
         * 输出:center 圆心坐标
         */
        public static Point YPC(double R, Point[] pt)
        {
            int n = pt.Length, r = n - 2;
            Point center = new Point();
            Point[] c1 = YS(R, pt[0], pt[1]);
            Point[] c2 = YS(R, pt[0], pt[n - 1]);
            Point[] dp = new Point[4];
            dp[0] = c1[1] - c2[1];
            dp[1] = c1[0] - c2[0];
            dp[2] = c1[0] - c2[1];
            dp[3] = c1[1] - c2[0];
            int index = Point.find_Close0(dp);
            switch (index)
            {//找到初值(近似值)
                case 0:
                    center = (c1[1] + c2[1]) / 2;
                    break;
                case 1:
                    center = (c1[0] + c2[0]) / 2;
                    break;
                case 2:
                    center = (c1[0] + c2[1]) / 2;
                    break;
                case 3:
                    center = (c1[1] + c2[0]) / 2;
                    break;
            }

            Matrix B = new Matrix(n, 2),
                L = new Matrix(n, 1),
                X = new Matrix(2, 1),
                Zero = new Matrix(2, 1);

            double x0 = 0, y0 = 0;
            do
            {
                x0 = center.x;
                y0 = center.y;
                for (int i = 0; i < n; i++)
                {
                    double x = pt[i].x, y = pt[i].y;
                    B[i, 0] = 2 * (x0 - x);
                    B[i, 1] = 2 * (y0 - y);
                    L[i, 0] = 2 * x0 * x + 2 * y * y0 - pt[i].Dis2To0() - center.Dis2To0() + R * R;
                }
                Matrix Nbb = B.Transpose() * B;
                Nbb.InvertGaussJordan();
                X = Nbb * B.Transpose() * L;
                center.x += X[0, 0];
                center.y += X[1, 0];
                X.Eps = 1e-6;
            } while (!X.Equals(Zero));
            Matrix V = B * X - L;
            double cgma2 = (V.Transpose() * V)[0, 0] / r;//单位权中误差的平方。
            return center;
        }
        /*此函数再已知哪个是起点(逆时针)的情况下 求圆心坐标
         */
        public static Point Ycenter(double R, Point p1, Point p2)
        {
            Point E = (p1 + p2) / 2;
            Point[] p = new Point[3];
            p[0] = p1;
            p[1] = p2;
            double s1 = p1.Dis(p2) / 2,
                s3 = Math.Sqrt(R * R - s1 * s1),
                s2 = R - s3;
            Angle a = p1.getAn(p2),
                b = new Angle(Math.PI / 2),
                c = a - b;//-
            p[2] = new Point(E, s2, c);//至此,求出第三个点
            return YPC(R, p);
        }

3.调用实例

			double R = 10.1;
            Point[] pt = new Point[4];
            pt[0] = new Point(1, 13.03);
            pt[1] = new Point(2, -5.8125);
            pt[2] = new Point(3, 13.679);
            pt[3] = new Point(4, -6.24);
            GetCenter.YPC(R, pt).show();
===============================================
X 5.101,   Y 3.800,   Z 0.000,   Name 0

二、求球心坐标

1.算法原理

球坐标系如下图(图片来源于百度百科):

首先来想一下,几个点 + 一个半径可以确定一个球?直接想不好想,那么可以以已知点为球心,R 为半径画球面。(为了偷个懒,我从GNSS课件上盗了几个图)

得出的结论为:四个点唯一确定一个球心。

如下图所示,已知四点坐标及半径,求球心坐标(图中坐标系只是辅助看图,不一定是实际坐标系)。

类似于上面推导圆心坐标的方式,我们也可以先用三个点来求出两个球心这种简单方法。当有多于四个(含四)点时,用平差的方法。
1.简单解法

2.平差法

2.代码实现

2020/6/12
此代码有bug,目前找不出来,有时间来补坑。

		/*此函数用简单方法求球心坐标
         * 输入:R 半径, p1 p2 p3已知的上三点
         * 输出:center 俩圆心点数组
         */
        public static Point[] QS(double R, Point p1, Point p2, Point p3)
        {
            Point[] center = new Point[2];
            double x1 = p1.x, y1 = p1.y, z1 = p1.z,
                x2 = p2.x, y2 = p2.y, z2 = p2.z,
                x3 = p3.x, y3 = p3.y, z3=p3.z;
            double C12 = p1.Dis2To0() - p2.Dis2To0(),
                C23 = p2.Dis2To0() - p3.Dis2To0(),
                x21 = x2 - x1, y21 = y2 - y1, z21 = z2 - z1,
                x32 = x3 - x2, y32 = y3 - y2, z32 = z3 - z2,
                C1=(C23*x21-C12*x32) / (2*(x32*z21-x21*z32)),
                C2=-(x32*y21-x21*y32) / (x32*z21-x21*z32),

                D1=-(C12+2*C1*z21)/(2*(y21+z21*C2)),
                D2=-x21/(y21+z21*C2),
                A = 1 + D2 * D2 + C2 * C2 * D2 * D2,
                B = -2 * x1 - 2 * y1 * D2 - 2 * z1 * C2 * D2 + 2 * D1 * D2 + 2 * (C1 + C2 * D1) * C2 * D2,
                C = p1.Dis2To0() - 2 * y1 * D1 - 2 * z1 * (C1 + C2 * D1) + D1 * D1 + (C1 + C2 * D1) * (C1 + C2 * D1);
            double x = (-B + Math.Sqrt(B * B - 4 * A * C)) / (2 * A),
                y = D1 + D2 * x,
                z = C1 + C2 * y;
            center[0] = new Point(x, y, z);
            x = (-B - Math.Sqrt(B * B - 4 * A * C)) / (2 * A);
            y = D1 + D2 * x;
            z = C1 + C2 * y;
            center[1] = new Point(x, y, z);
            return center;
        }
        /*此函数用平差方法求球心坐标
         * 输入:R 半径, pt 已知点数组
         * 输出:center 球心坐标
         */
        public static Point QPC(double R, Point[] pt)
        {
            int n = pt.Length, r = n - 2;
            Point center = new Point();
            Point[] c1 = QS(R, pt[0], pt[1], pt[2]);
            Point[] c2 = QS(R, pt[0], pt[n-2],pt[n - 1]);
            Point[] dp = new Point[4];
            dp[0] = c1[1] - c2[1];
            dp[1] = c1[0] - c2[0];
            dp[2] = c1[0] - c2[1];
            dp[3] = c1[1] - c2[0];
            int index = Point.find_Close0(dp);
            switch (index)
            {//找到初值(近似值)
                case 0:
                    center = (c1[1] + c2[1]) / 2;
                    break;
                case 1:
                    center = (c1[0] + c2[0]) / 2;
                    break;
                case 2:
                    center = (c1[0] + c2[1]) / 2;
                    break;
                case 3:
                    center = (c1[1] + c2[0]) / 2;
                    break;
            }

            Matrix B = new Matrix(n, 3),
                L = new Matrix(n, 1),
                X = new Matrix(3, 1),
                Zero = new Matrix(3, 1);

            double x0 = 0, y0 = 0, z0=0;
            do
            {
                x0 = center.x;
                y0 = center.y;
                z0 = center.z;
                for (int i = 0; i < n; i++)
                {
                    double x = pt[i].x, y = pt[i].y, z = pt[i].z;
                    B[i, 0] = 2 * (x0 - x);
                    B[i, 1] = 2 * (y0 - y);
                    B[i, 1] = 2 * (z0 - z);
                    L[i, 0] = 2 * x0 * x + 2 * y * y0 + 2 * z * z0 - pt[i].Dis2To0() - center.Dis2To0()+ R * R;
                }
                Matrix Nbb = B.Transpose() * B;
                Nbb.InvertGaussJordan();
                X = Nbb * B.Transpose() * L;
                center.x += X[0, 0];
                center.y += X[1, 0];
                X.Eps = 1e-6;
            } while (!X.Equals(Zero));
            Matrix V = B * X - L;
            double cgma2 = (V.Transpose() * V)[0, 0] / r;//单位权中误差的平方。
            return center;
        }

3.调用实例

			double R = 10.1;
            Point[] pt = new Point[5];
            pt[0] = new Point(1, 2, 11.553);
            pt[1] = new Point(2, 3, -7.079);
            pt[2] = new Point(5, 6, 12.357);
            pt[3] = new Point(7, 8, -6.4866);
            pt[4] = new Point(4, 3, 12.508);
            GetCenter.QPC(R, pt).show();

三、整个源码

	class GetCenter
    {
        /* 辅助函数-求随机数
         * 可选输入:a 下界, b 上界
         */
        public static double getRandom(double a = 0, double b = 100)
        {

            byte[] bytes = new byte[4];
            System.Security.Cryptography.RNGCryptoServiceProvider r = new System.Security.Cryptography.RNGCryptoServiceProvider();
            r.GetBytes(bytes);
            double g = Math.Abs(BitConverter.ToInt32(bytes, 0)) / 100.0;
            double c = b - a;
            return g % c + a;//得到在[a,b)范围内的随机数
        }
        /*此函数用于生成求圆心的实验数据
         * 输入:R 半径 cen 圆心坐标
         */
        public static Point[] YData(double R, Point cen)
        {
            Point[] pt = new Point[10];
            double x = cen.x, y = cen.y;
            for (int i = 0; i < 10; i++)
            {
                double x1 = getRandom(x - R, x + R),
                    dx = x1 - x,
                    dy = Math.Sqrt(R * R - dx * dx),
                    y1 = dy + y;
                if (i % 2 == 0) y1 = y1 - 2 * dy;
                pt[i] = new Point(x1, y1);
            }
            return pt;
        }
        /*此函数用简单方法求圆心坐标
         * 输入:R 半径, p1 p2 已知的圆上两点
         * 输出:center 俩圆心点数组
         * 有个弊端:p1,p2 的x 不能相等
         */
        public static Point[] YSx(double R, Point p1, Point p2)
        {
            Point[] center = new Point[2];
            double x1 = p1.x, y1 = p1.y, x2 = p2.x, y2 = p2.y;
            double C1 = (x2 * x2 - x1 * x1 + y2 * y2 - y1 * y1) / (2 * (x2 - x1)),
                C2 = (y2 - y1) / (x2 - x1),
                A = 1 + C2 * C2,
                B = 2 * ((x1 - C1) * C2 - y1),
                C = (x1 - C1) * (x1 - C1) + y1 * y1 - R * R;
            double y = (-B + Math.Sqrt(B * B - 4 * A * C)) / (2 * A);
            center[0] = new Point(C1 - C2 * y, y);
            y = (-B - Math.Sqrt(B * B - 4 * A * C)) / (2 * A);
            center[1] = new Point(C1 - C2 * y, y);
            return center;
        }
        /*此函数用简单方法求圆心坐标
         * 输入:R 半径, p1 p2 已知的圆上两点
         * 输出:center 俩圆心点数组
         * 有个弊端:p1,p2 的y 不能相等
         */
        public static Point[] YSy(double R, Point p1, Point p2)
        {
            Point[] center = new Point[2];
            double x1 = p1.x, y1 = p1.y, x2 = p2.x, y2 = p2.y;
            double C1 = (x2 * x2 - x1 * x1 + y2 * y2 - y1 * y1) / (2 * (y2 - y1)),
                C2 = (x2 - x1) / (y2 - y1),
                A = 1 + C2 * C2,
                B = 2 * ((y1 - C1) * C2 - x1),
                C = (y1 - C1) * (y1 - C1) + x1 * x1 - R * R;
            double x = (-B + Math.Sqrt(B * B - 4 * A * C)) / (2 * A);
            center[0] = new Point(x, C1 - C2 * x);
            x = (-B - Math.Sqrt(B * B - 4 * A * C)) / (2 * A);
            center[1] = new Point(x, C1 - C2 * x);
            return center;
        }
        /*此函数用简单方法求圆心坐标
         * 输入:R 半径, p1 p2 已知的圆上两点
         * 输出:center 俩圆心点数组
         * 解决了上述两弊端
         */
        public static Point[] YS(double R, Point p1, Point p2)
        {
            if ((p1.x - p2.x) != 0) return YSx(R, p1, p2);
            else return YSy(R, p1, p2);
        }
        /*此函数用平差方法求圆心坐标
         * 输入:R 半径, pt 已知点数组
         * 输出:center 圆心坐标
         */
        public static Point YPC(double R, Point[] pt)
        {
            int n = pt.Length, r = n - 2;
            Point center = new Point();
            Point[] c1 = YS(R, pt[0], pt[1]);
            Point[] c2 = YS(R, pt[0], pt[n - 1]);
            Point[] dp = new Point[4];
            dp[0] = c1[1] - c2[1];
            dp[1] = c1[0] - c2[0];
            dp[2] = c1[0] - c2[1];
            dp[3] = c1[1] - c2[0];
            int index = Point.find_Close0(dp);
            switch (index)
            {//找到初值(近似值)
                case 0:
                    center = (c1[1] + c2[1]) / 2;
                    break;
                case 1:
                    center = (c1[0] + c2[0]) / 2;
                    break;
                case 2:
                    center = (c1[0] + c2[1]) / 2;
                    break;
                case 3:
                    center = (c1[1] + c2[0]) / 2;
                    break;
            }

            Matrix B = new Matrix(n, 2),
                L = new Matrix(n, 1),
                X = new Matrix(2, 1),
                Zero = new Matrix(2, 1);

            double x0 = 0, y0 = 0;
            do
            {
                x0 = center.x;
                y0 = center.y;
                for (int i = 0; i < n; i++)
                {
                    double x = pt[i].x, y = pt[i].y;
                    B[i, 0] = 2 * (x0 - x);
                    B[i, 1] = 2 * (y0 - y);
                    L[i, 0] = 2 * x0 * x + 2 * y * y0 - pt[i].Dis2To0() - center.Dis2To0() + R * R;
                }
                Matrix Nbb = B.Transpose() * B;
                Nbb.InvertGaussJordan();
                X = Nbb * B.Transpose() * L;
                center.x += X[0, 0];
                center.y += X[1, 0];
                X.Eps = 1e-6;
            } while (!X.Equals(Zero));
            Matrix V = B * X - L;
            double cgma2 = (V.Transpose() * V)[0, 0] / r;//单位权中误差的平方。
            return center;
        }
        /*此函数再已知哪个是起点 弧长短的情况下 求圆心坐标
         * 输入:R 半径, p1 起点, p2 终点, k 优弧=1 劣弧=2
         */
        public static Point Ycenter(double R, Point p1, Point p2, int k=1)
        {
            Point E = (p1 + p2) / 2;
            Point[] p = new Point[3];
            p[0] = p1;
            p[1] = p2;
            double s1 = p1.Dis(p2) / 2,
                s3 = Math.Sqrt(R * R - s1 * s1),
                s2 = R - s3;
            if (k == 2) s2 = R + s3;//劣弧的情况下
            Angle a = p1.getAn(p2),
                b = new Angle(Math.PI / 2),
                c = a - b;//-
            p[2] = new Point(E, s2, c);//至此,求出第三个点
            return YPC(R, p);
        }

        /*此函数用于生成求球心的实验数据
         * 输入:R 半径 cen 圆心坐标
         */
        public static Point[] QData(double R, Point cen)
        {
            Point[] pt = new Point[16];
            double x = cen.x, y = cen.y, z = cen.z;
            for (int i = 0; i < 4; i++)
            {
                for (int j = 0; j < 4; j++)
                {
                    double x1 = getRandom(x - R, x + R),
                        dx = x1 - x,
                        R0 = Math.Sqrt(R * R - dx * dx),
                        y1 = getRandom(y - R0, y + R0),
                        dy = y1 - y,
                        dz = Math.Sqrt(R * R - dx * dx - dy * dy),
                        z1 = dz + z;
                    if (j % 2 == 0) z1 = z1 - 2 * dz;
                    int k = j * 4 + i;
                    pt[k] = new Point(x1, y1, z1);
                }
            }
            return pt;
        }
        /*此函数用简单方法求球心坐标
         * 输入:R 半径, p1 p2 p3已知的上三点
         * 输出:center 俩圆心点数组
         */
        public static Point[] QS(double R, Point p1, Point p2, Point p3)
        {
            Point[] center = new Point[2];
            double x1 = p1.x, y1 = p1.y, z1 = p1.z,
                x2 = p2.x, y2 = p2.y, z2 = p2.z,
                x3 = p3.x, y3 = p3.y, z3 = p3.z;
            double C12 = p1.Dis2To0() - p2.Dis2To0(),
                C23 = p2.Dis2To0() - p3.Dis2To0(),
                x21 = x2 - x1, y21 = y2 - y1, z21 = z2 - z1,
                x32 = x3 - x2, y32 = y3 - y2, z32 = z3 - z2,
                C1 = (C23 * x21 - C12 * x32) / (2 * (x32 * z21 - x21 * z32)),
                C2 = -(x32 * y21 - x21 * y32) / (x32 * z21 - x21 * z32),

                D1 = -(C12 + 2 * C1 * z21) / (2 * (y21 + z21 * C2)),
                D2 = -x21 / (y21 + z21 * C2),
                A = 1 + D2 * D2 + C2 * C2 * D2 * D2,
                B = -2 * x1 - 2 * y1 * D2 - 2 * z1 * C2 * D2 + 2 * D1 * D2 + 2 * (C1 + C2 * D1) * C2 * D2,
                C = p1.Dis2To0() - 2 * y1 * D1 - 2 * z1 * (C1 + C2 * D1) + D1 * D1 + (C1 + C2 * D1) * (C1 + C2 * D1);
            double x = (-B + Math.Sqrt(B * B - 4 * A * C)) / (2 * A),
                y = D1 + D2 * x,
                z = C1 + C2 * y;
            center[0] = new Point(x, y, z);
            x = (-B - Math.Sqrt(B * B - 4 * A * C)) / (2 * A);
            y = D1 + D2 * x;
            z = C1 + C2 * y;
            center[1] = new Point(x, y, z);
            return center;
        }
        /*此函数用平差方法求球心坐标
         * 输入:R 半径, pt 已知点数组
         * 输出:center 球心坐标
         */
        public static Point QPC(double R, Point[] pt)
        {
            int n = pt.Length, r = n - 2;
            Point center = new Point();
            Point[] c1 = QS(R, pt[0], pt[1], pt[2]);
            Point[] c2 = QS(R, pt[0], pt[n - 2], pt[n - 1]);
            Point[] dp = new Point[4];
            dp[0] = c1[1] - c2[1];
            dp[1] = c1[0] - c2[0];
            dp[2] = c1[0] - c2[1];
            dp[3] = c1[1] - c2[0];
            int index = Point.find_Close0(dp);
            switch (index)
            {//找到初值(近似值)
                case 0:
                    center = (c1[1] + c2[1]) / 2;
                    break;
                case 1:
                    center = (c1[0] + c2[0]) / 2;
                    break;
                case 2:
                    center = (c1[0] + c2[1]) / 2;
                    break;
                case 3:
                    center = (c1[1] + c2[0]) / 2;
                    break;
            }

            Matrix B = new Matrix(n, 3),
                L = new Matrix(n, 1),
                X = new Matrix(3, 1),
                Zero = new Matrix(3, 1);

            double x0 = 0, y0 = 0, z0 = 0;
            do
            {
                x0 = center.x;
                y0 = center.y;
                z0 = center.z;
                for (int i = 0; i < n; i++)
                {
                    double x = pt[i].x, y = pt[i].y, z = pt[i].z;
                    B[i, 0] = 2 * (x0 - x);
                    B[i, 1] = 2 * (y0 - y);
                    B[i, 1] = 2 * (z0 - z);
                    L[i, 0] = 2 * x0 * x + 2 * y * y0 + 2 * z * z0 - pt[i].Dis2To0() - center.Dis2To0() + R * R;
                }
                Matrix Nbb = B.Transpose() * B;
                Nbb.InvertGaussJordan();
                X = Nbb * B.Transpose() * L;
                center.x += X[0, 0];
                center.y += X[1, 0];
                X.Eps = 1e-6;
            } while (!X.Equals(Zero));
            Matrix V = B * X - L;
            double cgma2 = (V.Transpose() * V)[0, 0] / r;//单位权中误差的平方。
            return center;
        }
    }

其所依附的几个类:

https://blog.csdn.net/Gou_Hailong/article/details/88989274
https://blog.csdn.net/Gou_Hailong/article/details/98451032

调用示例

double R = 10.1;
Point cen = new Point(5.1, 3.8, 2.5);
//GetCenter.YPC(R, GetCenter.YData(R, cen)).show();
//GetCenter.QPC(R, GetCenter.QData(R, cen)).show();

参考/引用 文章

[1] wolves_liu-CSDN博主:https://blog.csdn.net/yaodaoji/article/details/81540883

【注1】其中的代码也许并不完整,您可以作为伪码参看,或者您可以去我主博客逛逛,也许有意外之喜!
【注2】此篇博客是 C# 编程笔记 的子博客。
【注3】由于博主水平有限,程序可能存在漏洞或bug, 如有发现,请尽快与博主联系!


转载:https://blog.csdn.net/Gou_Hailong/article/details/106686355
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场