C#几何算法:凸包(ConvexHull)——Graham扫描法

本文作者分享了一个修复后的Graham算法的代码,用于计算点集的凸包,适用于CAD环境。代码经过测试,提供给读者直接使用,有问题可私信作者咨询。
摘要由CSDN通过智能技术生成

前言

        距离上一次发文又不知道是什么时候了,之前发了关于四叉树算法的代码及应用,但有部分读者反馈说代码无法使用,在此我也做一个说明哈,之前教程里面的代码确实有点BUG,后来实际使用过程中本人也对该部分BUG进行了一个修复,同时也将其运用至了很多实战项目中。如果各位想要完整可用的代码,可以直接私信我,笔者看见消息一定会及时进行一个反馈。

       今天的文章不以教学为主,主要是附上完整的代码以及使用方法,做任何算法之前一定得把算法本身看懂了才行,如果各位不想看的,也可以照搬下面的代码,代码是笔者自己写的,也通过了大量数据的测试,确保无误之后才进行发布的,下面就是在CAD中测试的结果:

代码

        关于凸包的算法有很多,这里采用的是一个很经典的算法,Graham扫描法,具体实现过程参照博文数学:凸包算法详解 - 爱国呐 - 博客园 (cnblogs.com)

主代码


using LeaderGeometry.Objects;
using LeaderSpatialServices.Entity;
using System.Collections.Generic;
using System.Linq;

namespace LeaderGeometry.Operations
{
    public class ConvexHull
    {
        delegate bool HavePoint(List<IPoint> Points, IPoint Point);//声明委托,判断集合内是否包含点坐标
        /// <summary>
        /// 原始点集
        /// </summary>
        private List<IPoint> Coordinates;
        /// <summary>
        /// 实例化
        /// </summary>
        /// <param name="Coordinates">传入点集合</param>
        public ConvexHull(List<IPoint> Coordinates)
        {
            this.Coordinates = Coordinates;

        }
        /// <summary>
        /// 将点集排序
        /// </summary>
        /// <returns></returns>
        private List<IPoint> SortCoordinates()
        {
           
            List<IPoint> SortPoints = new List<IPoint>();
            //先将原始点集按照y坐标进行排序
            Coordinates = Coordinates.AsEnumerable().OrderBy(pt => pt.Y).ToList();//(默认升序,从小到大排)
            //以Y坐标最小值的点为起点
            IPoint ptStart = Coordinates[0];
            SortPoints.Add(ptStart);//将起点添加至结果

            //按照剩余点与起点连线沿水平方向角角坐标从小到大排序(夹角坐标一致时,离起点最近的排在最前)
            Dictionary<int, double[]> keyValuePairs = new Dictionary<int, double[]>();//新建字典用于记录每个点的水平方向的夹角与点距
       
            for (int i = 1; i < Coordinates.Count; i++)
            {
                //获取点
                IPoint pt = Coordinates[i];
                double degree = ptStart.VectorTo(pt).Degree;//计算水平方向夹角
                double dist = ptStart.DistanceTo(pt);//求当前点到起点的距离
                keyValuePairs.Add(i, new double[] { degree, dist });
            }
            keyValuePairs = keyValuePairs.AsEnumerable().OrderBy(r => r.Value[1]).OrderBy(r => r.Value[0]).ToDictionary(r => r.Key, r => r.Value);//对结果进行排序

            List<int> SortIndex = keyValuePairs.Keys.ToList();//获取排列好的序号
            keyValuePairs.Clear();//清理字典对象


            //根据最新序号将点集重新组合
            foreach (var item in SortIndex)
            {
                SortPoints.Add(Coordinates[item]);
            }
            SortIndex.Clear();//清理列表
            //最后返回结果
            return SortPoints;
        }
        /// <summary>
        /// 通过Graham算法搜索凸包
        /// </summary>
        /// <returns></returns>
        public List<IPoint> GrahamScan()
        {
            //定义委托以便于查找集合是否包含点坐标
            HavePoint havePoint = delegate (List<IPoint> Points, IPoint Point) {
                var q = from pt in Points where pt.X == Point.X && pt.Y == Point.Y && pt.Z == Point.Z select pt;
                return q.Count() > 0;

            };
            List<IPoint> convexHull = new List<IPoint>();//用于存放最终的凸包点集
            Coordinates = SortCoordinates();//将点集进行排序(第一个点与最后一个点必为凸包点)
            convexHull.Add(Coordinates[0]);
            convexHull.Add(Coordinates[1]);

            IPoint ptEnd = Coordinates[Coordinates.Count - 1];//获取最后一个点
            
            IPoint ptHull;//记录当前的顶点

            for (int i = 2; i < Coordinates.Count - 1; i++)
            {
                ptHull = Coordinates[i];

                if (!havePoint(convexHull, ptHull)) convexHull.Add(ptHull);//将当前点暂时作为凸点(可能存在共点的情况,需要剔除)

                IPoint hullStart;
                IPoint hullEnd;
                IPoint NextPoint = Coordinates[i + 1];

                ILine seg;

                hullStart = convexHull[convexHull.Count - 2];
                hullEnd = convexHull[convexHull.Count - 1];
                seg = new ILine(hullStart, hullEnd);
                double sides;
                sides = NextPoint.GetSides(seg);
                while (sides < 0)
                {
                    convexHull.RemoveAt(convexHull.Count - 1);
                    hullStart = convexHull[convexHull.Count - 2];
                    hullEnd = convexHull[convexHull.Count - 1];
                    seg = new ILine(hullStart, hullEnd);
                    sides = NextPoint.GetSides(seg);
                }
            }
            convexHull.Add(ptEnd);//加入最后一个点
            return convexHull;
        }
    }
}

辅助类1

/// <summary>
    /// 点对象
    /// </summary>
    public class IPoint
    {
        public double X
        {
            get
            {
                return _x;
            }
            set
            {
                _x = value;
            }
        }
        private double _x;
        public double Y
        {
            get
            {
                return _y;
            }
            set
            {
                _y = value;
            }
        }
        private double _y;

        public double Z
        {
            get
            {
                return _z;
            }
            set
            {
                _z = value;
            }
        }
        private double _z;

        public IExtent Extent {

            get { 
                IExtent Ext=new IExtent();
                Ext.MinPoint=new IPoint(X-0.5,Y-0.5,0);
                Ext.MaxPoint = new IPoint(X + 0.5, Y + 0.5, 0);
                return Ext;
            }
        }
        /// <summary>
        /// 实例化
        /// </summary>
        /// <param name="X"></param>
        /// <param name="Y"></param>
        /// <param name="Z"></param>
        public IPoint(double X, double Y, double Z)
        {
            this.X = X;
            this.Y = Y;
            this.Z = Z;          
        }
        /// <summary>
        /// 两点距离
        /// </summary>
        /// <returns></returns>
        public double DistanceTo(IPoint SecondPoint)
        {
            return this.VectorTo(SecondPoint).Norm;
        }


        /// <summary>
        /// 获取到另一点的向量
        /// </summary>
        /// <returns></returns>
        public Vector VectorTo(IPoint SecondPoint)
        {
            return new Vector(SecondPoint.X - this.X, SecondPoint.Y - this.Y, SecondPoint.Z - this.Z);
        }

        public double GetSides(ILine refLine)
        {
            IPoint ptStart = refLine.StartPoint;
            IPoint ptEnd = refLine.EndPoint;
            

            return (ptStart.Y - ptEnd.Y) * this.X + (ptEnd.X - ptStart.X) * this.Y + ptStart.X * ptEnd.Y - ptEnd.X * ptStart.Y;
        }


    }

辅助类2

/// <summary>
    /// 向量
    /// </summary>
    public class Vector
    {
        /// <summary>
        /// 向量X方向增量
        /// </summary>
        public double X { get; set; }
        /// <summary>
        /// 向量Y方向的增量
        /// </summary>
        public double Y { get; set; }
        /// <summary>
        /// 向量Z方向的增量
        /// </summary>
        public double Z { get; set; }
        /// <summary>
        /// 向量的模长
        /// </summary>
        public double Norm { get { return GetNormOfVector(); } }
        /// <summary>
        /// 向量角度
        /// </summary>
        public double Degree { get { return GetDegree(); } }

        public Vector(double X, double Y, double Z = 0)
        {
            this.X = X;
            this.Y = Y;
            this.Z = Z;
        }
       
        /// <summary>
        /// 求向量的模长
        /// </summary>
        /// <returns></returns>
        private double GetNormOfVector()
        {
            return Math.Sqrt(this.X * this.X + this.Y*this.Y + this.Z * this.Z);
        }
        /// <summary>
        /// 获取向量与水平方向的夹角
        /// </summary>
        /// <returns></returns>
        private double GetDegree()
        {
            return IMath.RadiansToDegree(Math.Atan2(Y, X));
        }
        

    }

辅助类3

 /// <summary>
    /// 线段
    /// </summary>
    public class ILine : IEntity
    {
        /// <summary>
        /// 起点
        /// </summary>
        public IPoint StartPoint { get { return _startPoint; } set { _startPoint = value; } }
        private IPoint _startPoint;
        /// <summary>
        /// 终点
        /// </summary>
        public IPoint EndPoint { get { return _endPoint; } set { _endPoint = value; } }
        private IPoint _endPoint;

       
        /// <summary>
        /// 实例化
        /// </summary>
        /// <param name="start"></param>
        /// <param name="end"></param>
        public ILine(IPoint start, IPoint end)
        {
            _startPoint = start;
            _endPoint = end;

        }
       
       
    }

结语

        代码复制到类文件编译了就可以用,不限平台,有疑问可以私聊我,困了,晚安!

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值