🧄 射线法判断一个点是否在多边形内部

563 阅读4分钟

原文地址: alvis.org.cn/posts/3ad2e…

前言 📝

射线法即从P点引出一条射线,看这条射线和多边形相交的次数。假如P点在多边形外,那么P点射线和多边形相交情况一定是:穿入 -> …(中间可能有N次穿入穿出)… ->穿出,即穿入穿出的次数是偶数次,交点是偶数个。假如P点在多边形内,那么P点射线和多边形相交的情况一定是:穿出 -> …(后面可能有N次穿入穿出)…,总之最后一定是穿出,交点是奇数个。是不是不明白,懵逼的你和我一起看一下图。

本网站的图片截图于:blog.csdn.net/qq_23447231…

1. 一般情况

1.1 P点在多边形外

image.png

结论: 要么一个相交点也没有,要么是双数个

1.2 P点在多边形内

image.png

结论:相交点总共是1个或者奇数个

2. 特殊情况(不考虑)

因为所有方向的射线都是一样的,所以为了分析方便,我们使用从P点开始的一条水平线作为射线来分析。 然而还有特殊情况:

2.1 P点在多边形顶点

image.png

2.2 P点和多边形的交点是顶点

image.png

2.3 P点在多边形线段上

image.png

2.4 P点的射线刚好经过多边形的一条边

image.png

2.5 P点的射线刚好经过多边形一条边,并且P点在这条边上

image.png

3. 编程思路

如果我们采用左射线的方法,那是不是直接相反,当交叉数量是奇数的时候,说明我们的点在多边形内。反之则在外面。 为什么要采用左射线的方法呢,我感觉容易理解一点。我们先看一下思路,我们采用左射线的情况思路: 我们先一条线一条线的判断,比如说我们的(x1,y1),(x2,y2)组成的一条线,如果说我们的P点不在两个点之间,则不可能会有交点。继续判断下一条边。如果说在中间的情况下,P点的左射线可能会和我们的线段有交点。然后我们可以利用几何方法得到P点的水平直线与该边交点的x坐标。然后判断交点的X坐标是在P点的左侧还是右侧。左侧则相交数量+1,右侧则继续判断下一条线段。

function IsPtInPoly(aLat, aLon, pointList) {
  /* 
  :param aLon: double 经度 
  :param aLat: double 纬度 
  :param pointList: list [{latitude: 22.22, longitude: 113.113}...] 多边形点的顺序需根据顺时针或逆时针,不能乱 
  */
  var iSum = 0  
  // 坐标点的个数
  var iCount = pointList.length

  // 两点一线,三点一面。如果小于三个点直接返回false
  if(iCount < 3) {
      return false 
  }
  //  待判断的点(x, y) 为已知值
  var y = aLat
  var x = aLon
  for(var i = 0; i < iCount; i++) {
      var y1 = pointList[i].latitude  
      var x1 = pointList[i].longitude
      
      if(i == iCount - 1) {
	      // 最后一圈,这是一个闭环的图形,最后收尾的一定是起点坐标
          var y2 = pointList[0].latitude
          var x2 = pointList[0].longitude
      } else {
	      // 当前坐标的下一个相邻的坐标 
          var y2 = pointList[i + 1].latitude  
          var x2 = pointList[i + 1].longitude
      }
      
      // 当前边的 2 个端点分别为 已知值(x1, y1), (x2, y2)
      // ((y >= y1) && (y < y2)成立说明y2在y1上面,p点的纵坐标(y)在y2和y1之间,或者在y1上
      // ((y >= y2) && (y < y1))成立说明y2在y1下面,p点的纵坐标(y)在y2和y1之间,或者在y2上
      // 主要目的是查看当前的p点纵坐标是否在某个相邻的两个点之间
      if (((y >= y1) && (y < y2)) || ((y >= y2) && (y < y1))) {
          //  y 界于 y1 和 y2 之间
          //  假设过待判断点(x, y)的水平直线和当前边的交点为(x_intersect, y_intersect),有y_intersect = y(具体看下图)
          // 则有(2个相似三角形,公用顶角,宽/宽 = 高/高):|x1 - x2| / |x1 - x_intersect| = |y1 - y2| / |y1 - y|
          if (Math.abs(y1 - y2) > 0) {
              var x_intersect = x1 - ((x1 - x2) * (y1 - y)) / (y1 - y2);  
              // 判断焦点是否在P点的左边
              if(x_intersect < x) {
                  iSum += 1 
              }
          }
      } 
  }
  if(iSum % 2 != 0) {
      return true  
  }else {
      return false 
  }  
}
module.exports = {
  IsPtInPoly: IsPtInPoly,
};

image.png