判断点是否在多边形中

本文介绍了一种用于判断点是否位于多边形内部的高效算法,包括理论原理和C#实现代码,利用射线法计算交点数量来判断,并通过外包矩形预筛选提高效率。
一、判断点是否在多边形中(理论)

  判断点P是否在多边形中是计算几何中一个非常基本但是十分重要的算法。以点P为端点,向左方作射线L,由于多边形是有界的,所以射线L的左端一定在多边形外,考虑沿着L从无穷远处开始自左向右移动,遇到和多边形的第一个交点的时候,进入到了多边形的内部,遇到第二个交点的时候,离开了多边形,……所以很容易看出当L和多边形的交点数目C是奇数的时候,P在多边形内,是偶数的话P在多边形外。

  但是有些特殊情况要加以考虑。如图下图(a)(b)(c)(d)所示。在图(a)中,L和多边形的顶点相交,这时候交点只能计算一个;在图(b)中,L和多边形顶点的交点不应被计算;在图(c)和(d) 中,L和多边形的一条边重合,这条边应该被忽略不计。如果L和多边形的一条边重合,这条边应该被忽略不计。

    判断点是否在多边形中 - kenchell - kenchell

  为了统一起见,我们在计算射线L和多边形的交点的时候,1。对于多边形的水平边不作考虑;2。对于多边形的顶点和L相交的情况,如果该顶点是其所属的边上纵坐标较大的顶点,则计数,否则忽略;3。对于P在多边形边上的情形,直接可判断P属于多边行。由此得出算法的伪代码如下:

    count ← 0;
    以P为端点,作从右向左的射线L;
    for 多边形的每条边s
     do if P在边s上
          then return true;
        if s不是水平的
          then if s的一个端点在L上
                 if 该端点是s两端点中纵坐标较大的端点
                   then count ← count+1
               else if s和L相交
                 then count ← count+1;
    if count mod 2 = 1
      then return true;
    else return false;



  其中做射线L的方法是:设P'的纵坐标和P相同,横坐标为正无穷大(很大的一个正数),则P和P'就确定了射线L。

  判断点是否在多边形中的这个算法的时间复杂度为O(n)。

  另外还有一种算法是用带符号的三角形面积之和与多边形面积进行比较,这种算法由于使用浮点数运算所以会带来一定误差,不推荐大家使用。

 

二、判断点是否在多边形中(操作)

为加快判别速度,首先计算多边形的外包矩形,判断点是否落在外包矩形内,只有满足落在外包矩形内的条件的点,才进入下一步“射线法”的计算。

下面是C#实现的代码

 

/// <summary>
        /// 功能:判断点是否在多边形内
        /// 方法:求解通过该点的水平线与多边形各边的交点
        /// 结论:单边交点为奇数,成立!
        /// 参数: 返回1表示肯定在多边形内;-1肯定不在多边形内;0表示在多边形的边上;
        /// Point p 指定的某个点
        /// Point[] ptPolygon 多边形的各个顶点坐标(首末点可以不一致)
        /// int nCount 多边形定点的个数
        /// </summary>
        /// <param name="p"></param>
        /// <param name="ptPolygon"></param>
        /// <returns></returns>
        public static int PtInPolygon(Point p, Point[] ptPolygon)
        {
            int nCount = ptPolygon.Length;
            bool isBeside = false;// 记录是否在多边形的边上

            #region 矩形外区域
            double maxx;
            double maxy;
            double minx;
            double miny;
            if (nCount > 0)
            {
                maxx = ptPolygon[0].X;
                minx = ptPolygon[0].X;
                maxy = ptPolygon[0].Y;
                miny = ptPolygon[0].Y;

                for (int j = 1; j < nCount; j++)
                {
                    if (ptPolygon[j].X >= maxx)
                        maxx = ptPolygon[j].X;
                    else if (ptPolygon[j].X <= minx)
                        minx = ptPolygon[j].X;

                    if (ptPolygon[j].Y >= maxy)
                        maxy = ptPolygon[j].Y;
                    else if (ptPolygon[j].Y <= miny)
                        miny = ptPolygon[j].Y;
                }

                if ((p.X > maxx) || (p.X < minx) || (p.Y > maxy) || (p.Y < miny))
                    return -1;
            }


            #endregion

            #region 射线法
            int nCross = 0;

            for (int i = 0; i < nCount; i++)
            {
                Point p1 = ptPolygon[i];
                Point p2 = ptPolygon[(i + 1) % nCount];

                // 求解 y=p.y 与 p1p2 的交点

                if (p1.Y == p2.Y) // p1p2 与 y=p0.y平行
                {
                    if (p.Y == p1.Y && p.X >= min(p1.X, p2.X) && p.X <= max(p1.X, p2.X))
                    {
                        isBeside = true;
                        continue;
                    }
                }

                if (p.Y < min(p1.Y, p2.Y) || p.Y > max(p1.Y, p2.Y)) // 交点在p1p2延长线上
                    continue;


                // 求交点的 X 坐标 --------------------------------------------------------------
                double x = (double)(p.Y - p1.Y) * (double)(p2.X - p1.X) / (double)(p2.Y - p1.Y) + p1.X;

                if (x > p.X)
                    nCross++; // 只统计单边交点
                else if (x == p.X)
                    isBeside = true;
            }

            if (isBeside)
                return 0;//多边形边上
            else if (nCross % 2 == 1)// 单边交点为偶数,点在多边形之外 ---
                return 1;//多边形内

            return -1;//多边形外
            #endregion
        }

        private static int min(int x, int y)
        {
            if (x > y)
                return y;
            else
                return x;
        }
        private static int max(int x, int y)
        {
            if (x > y)
                return x;
            else
                return y;
        }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值