【转】【数学】判断一个点是否在一个多边形里面

“判断一个点是否在一个多边形里”,一开始以为是个挺难的问题,但Google了一下之后发现其实蛮简单,所用到的算法叫做“Ray-casting Algorithm”,中文应该叫“光线投射算法”,这是维基百科的描述:[维基百科]

简单地说可以这么判断:从这个点引出一根“射线”,与多边形的任意若干条边相交,累计相交的边的数目,如果是奇数,那么点就在多边形内,否则点就在多边形外。

如图,A点引一条射线,与多边形3条边相交,奇数,所以A点在多边形内,而从B点引一条射线,与多边形的2条边相交,偶数,所以B点在多边形外。

我打算把这个算法用于判断地图上所在的位置是否在一个范围之内,我先用鼠标在地图上绘制出一个多边形区域,然后再用这个方法判断一个坐标是否在这个多边形范围内,我仍然拿五角星做试验品,在高德地图上描出一个五角星:

嗯?怎么五角星居然中间没被镂空?这是怎么回事?经过研究,我发现高德地图的鼠标工具的多边形填充用的是另外一套规则,叫做“None Zero Mode”,判断一个点是否在多边形内的规则就变成了:从这个点引出一根“射线”,与多边形的任意若干条边相交,计数初始化为0,若相交处被多边形的边从左到右切过,计数+1,若相交处被多边形的边从右到左切过,计数-1,最后检查计数,如果是0,点在多边形外,如果非0,点在多边形内。回到五角星的例子,这次要注意多边形线条描绘的方向:

从C点引出一条射线,与这条射线相交的两条多边形的边均是从左向右切过,总计数是2,因此C点在多边形内。用个更形象点的方式描述就是:从C点出发,一直朝一个方向走,遇到两条单行道,都是从自己的左边切至右边的方向,计数+1,计数+1,总计数所以是2。

算法实现起来居然很简单,几行代码即可,真的是几行代码,我用的是C#,大家可以轻轻松松改成别的。

public static class RayCastingAlgorithm {
        public static bool IsWithin(Point pt, IList<Point> polygon, bool noneZeroMode) {
            int ptNum = polygon.Count();
            if (ptNum < 3) {
                return false;
            }
            int j = ptNum - 1;
            bool oddNodes = false;
            int zeroState = 0;
            for (int k = 0; k < ptNum; k++) {
                Point ptK = polygon[k];
                Point ptJ = polygon[j];
                if (((ptK.Y > pt.Y) != (ptJ.Y > pt.Y)) && (pt.X < (ptJ.X - ptK.X) * (pt.Y - ptK.Y) / (ptJ.Y - ptK.Y) + ptK.X)) {
                    oddNodes = !oddNodes;
                    if (ptK.Y > ptJ.Y) {
                        zeroState++;
                    }
                    else {
                        zeroState--;
                    }
                }
                j = k;
            }
            return noneZeroMode?zeroState!=0:oddNodes;
        }
    }

我用WPF写了个demo,如图:

给懒得敲打吗的同学玩玩。(源码:VS2015)

原文地址: https://www.cnblogs.com/guogangj/p/5127527.html

考虑到double值有误差,以及有时在线的边上一点就需要识别为区域内,所以改了下加了个容差值(当然没考虑算法速度的问题,只是判断点是否在线上,有其他好的思路的可以提供下啊)

算法方法:

public static bool IsWithin(Point pt, IList<Point> polygon, bool noneZeroMode, double dTol = 0)
        {
            int ptNum = polygon.Count();
            if (ptNum < 3)
            {
                return false;
            }
            int j = ptNum - 1;
            bool oddNodes = false;
            int zeroState = 0;
            for (int k = 0; k < ptNum; k++)
            {
                Point ptK = polygon[k];
                Point ptJ = polygon[j];
                if (((ptK.Y > pt.Y) != (ptJ.Y > pt.Y)) && (pt.X < (ptJ.X - ptK.X) * (pt.Y - ptK.Y) / (ptJ.Y - ptK.Y) + ptK.X))
                {
                    oddNodes = !oddNodes;
                    if (ptK.Y > ptJ.Y)
                    {
                        zeroState++;
                    }
                    else
                    {
                        zeroState--;
                    }
                }
                j = k;
            }
            bool flag = noneZeroMode ? zeroState != 0 : oddNodes;
            if (flag || dTol.IsAlmostEqualTo(0)) return flag;
            //增加判断点在直线附近容差范围内划分为区域内
            {
                int len = polygon.Count;
                for (int i = 0; i < len; ++i)
                {
                    var p1 = polygon[i];
                    var p2 = i == len - 1 ? polygon[0] : polygon[i + 1];
                    var d = pt.GetDropPoint(p1, p2);
                    if (d.DistanceTo(pt).IsLessThanEqualTo(0, dTol))
                    {
                        if (d.IsInLine(p1, p2, dTol)) return true;
                    }
                }
                return false;
            }
        }

用到的其他一些容差计算的方法:

public static class CommonExpand
    {
        /// <summary>
        /// 判断两个double是否相同
        /// </summary>
        /// <param name="src"></param>
        /// <param name="dst"></param>
        /// <param name="dTol"></param>
        /// <returns></returns>
        public static bool IsAlmostEqualTo(this double src, double dst, double dTol = 0.00328)
        {
            var dis = Math.Abs(src - dst);
            if (dis <= dTol)
                return true;
            else
                return false;
        }

        /// <summary>
        /// 小于
        /// </summary>
        /// <param name="src"></param>
        /// <param name="dst"></param>
        /// <param name="dTol"></param>
        /// <returns></returns>
        public static bool IsLessThan(this double src, double dst, double dTol = 0.00328)
        {
            if (src < dst && !src.IsAlmostEqualTo(dst, dTol))
                return true;
            else
                return false;
        }

        /// <summary>
        /// 小于等于
        /// </summary>
        /// <param name="src"></param>
        /// <param name="dst"></param>
        /// <param name="dTol"></param>
        /// <returns></returns>
        public static bool IsLessThanEqualTo(this double src, double dst, double dTol = 0.00328)
        {
            if (src < dst || src.IsAlmostEqualTo(dst, dTol))
                return true;
            else
                return false;
        }

        /// <summary>
        /// 大于
        /// </summary>
        /// <param name="src"></param>
        /// <param name="dst"></param>
        /// <param name="dTol"></param>
        /// <returns></returns>
        public static bool IsGreaterThan(this double src, double dst, double dTol = 0.00328)
        {
            if (src > dst && !src.IsAlmostEqualTo(dst, dTol))
                return true;
            else
                return false;
        }

        /// <summary>
        /// 大于等于
        /// </summary>
        /// <param name="src"></param>
        /// <param name="dst"></param>
        /// <param name="dTol"></param>
        /// <returns></returns>
        public static bool IsGreaterThanEqualTo(this double src, double dst, double dTol = 0.00328)
        {
            if (src > dst || src.IsAlmostEqualTo(dst, dTol))
                return true;
            else
                return false;
        }
        /// <summary>
        /// 判断两个Point是否相同
        /// </summary>
        /// <param name="src"></param>
        /// <param name="dst"></param>
        /// <param name="dTol"></param>
        /// <returns></returns>
        public static bool IsAlmostEqualTo(this Point src, Point dst, double dTol = 0.00328)
        {
            if (Math.Abs(src.X - dst.X) < dTol && Math.Abs(src.Y - dst.Y) < dTol)
                return true;
            else
                return false;
        }
        /// <summary>
        /// 判断两个向量是否相等
        /// </summary>
        /// <param name="src"></param>
        /// <param name="dst"></param>
        /// <param name="dTol"></param>
        /// <returns></returns>
        public static bool IsAlmostEqualTo(this Vector src, Vector dst, double dTol = 0.00328)
        {
            if (src.X.IsAlmostEqualTo(dst.X) && src.Y.IsAlmostEqualTo(dst.Y))
                return true;
            else
                return false;
        }
        /// <summary>
        /// 判断同一条直线上的点,是否在线段以内
        /// </summary>
        /// <param name="src"></param>
        /// <param name="p1"></param>
        /// <param name="p2"></param>
        /// <param name="deviation"></param>
        /// <returns></returns>
        public static bool IsInLine(this Point src, Point p1, Point p2, double deviation = 0.00328)
        {
            if (src.IsAlmostEqualTo(p1, deviation) || src.IsAlmostEqualTo(p2, deviation)) return true;
            var vec1 = new Vector(p1.X - src.X, p1.Y - src.Y); vec1.Normalize();
            var vec2 = new Vector(p2.X - src.X, p2.Y - src.Y); vec2.Normalize();
            if (vec1.IsAlmostEqualTo(-vec2))
                return true;
            else
                return false;
        }
        /// <summary>
        /// 点到直线的垂足
        /// </summary>
        /// <param name="src"></param>
        /// <param name="p1"></param>
        /// <param name="p2"></param>
        /// <returns></returns>
        public static Point GetDropPoint(this Point src, Point p1, Point p2)
        {
            if (p1.X.IsAlmostEqualTo(p2.X))
            { return new Point(p1.X, src.Y); }
            else if (p1.Y.IsAlmostEqualTo(p2.Y))
            { return new Point(src.X, p1.Y); }
            else
            {
                double k = (p2.Y - p1.Y) / (p2.X - p1.X);
                double b1 = p1.Y - k * p1.X;
                double b2 = src.Y + src.X / k;
                double x = k * (b2 - b1) / (k * k + 1);
                double y = k * x + b1;
                return new Point(x, y);
            }
        }
        /// <summary>
        /// 点到点的距离
        /// </summary>
        /// <param name="src"></param>
        /// <param name="dst"></param>
        /// <returns></returns>
        public static double DistanceTo(this Point src, Point dst)
        {
            return Math.Sqrt(Math.Pow(src.X - dst.X, 2) + Math.Pow(src.Y - dst.Y, 2));
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值