zoukankan      html  css  js  c++  java
  • 【转】【数学】判断一个点是否在一个多边形里面

    “判断一个点是否在一个多边形里”,一开始以为是个挺难的问题,但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));
            }
        }
  • 相关阅读:
    正经学C#_循环[do while,while,for]:[c#入门经典]
    Vs 控件错位 右侧资源管理器文件夹点击也不管用,显示异常
    asp.net core 获取当前请求的url
    在实体对象中访问导航属性里的属性值出现异常“There is already an open DataReader associated with this Command which must be
    用orchard core和asp.net core 3.0 快速搭建博客,解决iis 部署https无法登录后台问题
    System.Data.Entity.Core.EntityCommandExecution The data reader is incompatible with the specified
    初探Java设计模式3:行为型模式(策略,观察者等)
    MySQL教程77-CROSS JOIN 交叉连接
    MySQL教程76-HAVING 过滤分组
    MySQL教程75-使用GROUP BY分组查询
  • 原文地址:https://www.cnblogs.com/mqxs/p/15576069.html
Copyright © 2011-2022 走看看