基于sign(x)函数的点在多边形内外判别算法
引用论文:
孙爱玲,赵光华,赵敏华,常璐.基于sign(x)函数的点在多边形内外判别算法及应用[J].计算机工程与科学,2017,39(04):785-790
一、名词定义
多边形的一般定义为:
给定平面上一系列共面的点V,V2,…,Vn,将这些点依次用线段连接,这些点就是多边形的顶点,线段就是多边形的边。
若多边形的不相邻的边对不相交,则称该多边形为简单多边形。
简单多边形把平面分为两个不同的区域:内部区域(有界的)和外部区域(无界的)。
这样,平面中点与多边形的位置关系有:点在多边形内、点在多边形外、点在多边形边上或顶点上
在几何中:
二维空间仅指的是一个平面,上面的每一个点可以由两个数构成的坐标(x, y)表示;
三维空间则是立体的,点的位置由(x ,y,z)三个坐标决定。
将二维空间看作xoy平面,三维空间看作o'x'y'z’立体空间,那么二维平面上的任意点可以看作是三维空间o'x'y'z’在c'o'y′平面上的点Q',这样二维平面上点的坐标(x,y)扩展 为空间坐标而成为(x, y,0)。
sign(x)定义:
二、算法思路
将xoy 平面上的多边形的顶点从某一起始点开始,沿同一方向(如:逆时针方向)进行编号,如图
所示,点Vi、V+1分别是多边形某条边的两个顶点,点Q是对应平面上的一点。现在要判别点Q与多边形的位置关系。
我们可以这样做:将平面xoy 上的点Q(x,y)、多边形的顶点Vi(xi,yi)和Vj(xj,yj)看作是三维空间oxyz在xoy平面上的点Q’、Vi’及 Vj’,如图
所示,其平面坐标扩展为空间坐标而成为(x,y,0)、(xi,yi ,0)及(xj,yj,0)。
于是得出:
向量:
Q
′
V
i
′
=
(
(
x
i
−
x
)
,
(
y
i
−
y
)
,
0
)
向量:Q'Vi' = ((xi - x), (yi - y), 0)
向量:Q′Vi′=((xi−x),(yi−y),0)
向量:
Q
′
V
j
′
=
(
(
x
j
−
x
)
,
(
y
j
−
y
)
,
0
)
向量:Q'Vj' = ((xj - x), (yj - y), 0)
向量:Q′Vj′=((xj−x),(yj−y),0)
则两向量的积为:
得到了向量的乘积后代入sign(x)函数:
分类讨论:
(1)如果sign(x) = 0,则点Q、Vi、Vj三点共线,点Q可能位于线段 V-Vi 的延长线上,也可能位于线段** V-Vi** 上(包括端点)。但是,当该点同时满足以下条件时:
待判断点位于多边形上(边界或顶点)。所以,在算法中按式(6)进行判断。
(2)如果sign(z)≠0,过Q点作一条射线,该射线可能与多边形的某几条边有交点(但不需要求出交点坐标,也就不存在坐标的计算误差),只需进行Q点与其相交边对应顶点坐标值的计算。
为计算方便,作一条水平射线,如图
所示,存在交点的边的顶点坐标与待判断点的坐标关系存在两种情况,分别为 yi < y < yj 或 yi > y > yj
在这两种情况下,作如下的计算:
若 K≠0 时,点Q位于多边形S内部;
若 K=0 时,点Q位于多边形S外部。
三、自然语言表述
四、算法实现代码:
点的数据模型
//点 数据模型
public class Point {
public double x;
public double y;
public Point() {
}
public Point(double x, double y) {
this.x = x;
this.y = y;
}
@Override
public String toString() {
return "Point{" +
"x=" + x +
", y=" + y +
'}';
}
}
具体算法
import Point;
import java.util.List;
/**
* 检查点是否在图形内部
* @孙爱玲,赵光华,赵敏华,常璐.基于sign(x)函数的点在多边形内外判别算法及应用[J].计算机工程与科学,2017,39(04):785-790
* @author dingN
* @date 2022/07/11
*/
public class CheckIsInside {
static int K = 0; // 判断所用累加值
/**
* 检查 某点是否在图形内部
* @param contourPoints 轮廓点, 依次连线
* @param specific 待测点
* @return boolean
*/
public static boolean CheckInside(
List<Point> contourPoints,
Point specific
) {
K = 0;
int listLength = contourPoints.size();
for (int i = 0; i < listLength; i++) {
Point firstPoint = contourPoints.get(i);
Point secondPoint = contourPoints.get((i + 1) % listLength);
double difference = calculateVectorDifference(firstPoint, secondPoint, specific);
int signX = sign(difference);
if (signX == 0 && whetherOnEdge(firstPoint, secondPoint, specific)) {
return false; // 这里认为如果待测点在某边上,也同样是不在图形内
} else {
AddK(firstPoint, secondPoint, specific, signX);
}
}
if (K == 0) return false; // K=0, 点在多边形外部
return true; // k != 0, 点在多边形内部
}
/**
* 计算向量差
*
* @param pointI point i
* @param pointIAddOne point i + 1
* @param specific 待测点
* @return double
*/
public static double calculateVectorDifference(
Point pointI,
Point pointIAddOne,
Point specific
) {
return (pointI.x - specific.x) * (pointIAddOne.y - specific.y) - (pointIAddOne.x - specific.x) * (pointI.y - specific.y);
}
/**
* 标志函数 sign(x)
* @param x x
* @return int
*/
public static int sign(
double x
) {
if (x == 0)
return 0;
else if (x > 0)
return 1;
else
return -1;
}
/**
* 待测点是否在边上
*
* @param pointI point i
* @param pointIAddOne point i + 1
* @param specific 待测点
* @return boolean
*/
public static boolean whetherOnEdge(
Point pointI,
Point pointIAddOne,
Point specific
) {
if ( // 在边上
specific.x <= Math.max(pointI.x, pointIAddOne.x) && specific.x >= Math.min(pointI.x, pointIAddOne.x)
&&
specific.y <= Math.max(pointI.y, pointIAddOne.y) && specific.y >= Math.min(pointI.y, pointIAddOne.y)
) {
System.out.println("待测点位于边" + pointI + " " + pointIAddOne + "上");
return true;
}
System.out.println("待测点不位于边" + pointI + " " + pointIAddOne + "上");
return false;
}
/**
* addK
* @param pointI point i
* @param pointIAddOne point i + 1
* @param specific 待测点
* @param sign 标志函数的结果
*/
public static void AddK(
Point pointI,
Point pointIAddOne,
Point specific,
int sign
) {
if ((pointIAddOne.y < specific.y && specific.y < pointI.y) || (pointI.y < specific.y && specific.y < pointIAddOne.y)) {
K += sign;
}
}
}
测试代码
这里仅测试了 点在凸多边形内部、点不在凹多边形内部 两种情况
import Point;
import CheckIsInside;
import org.junit.Assert;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
public class checkTest {
@Test
public void testCheckInside() {
List<Point> contourPoints = new ArrayList<>();
contourPoints.add( new Point(0, 0));
contourPoints.add( new Point(2, 0));
contourPoints.add( new Point(2, 2));
contourPoints.add( new Point(1, 3));
contourPoints.add( new Point(0, 2));
Point specific = new Point(1, 1);
Assert.assertEquals(true, CheckIsInside.CheckInside(contourPoints, specific));
}
@Test
public void testCheckInsideTwo() {
List<Point> contourPoints = new ArrayList<>();
contourPoints.add( new Point(0, 0));
contourPoints.add( new Point(2, 0));
contourPoints.add( new Point(2, 2));
contourPoints.add( new Point(1, 1));
contourPoints.add( new Point(0, 2));
Point specific = new Point(1, 2);
Assert.assertEquals(false, CheckIsInside.CheckInside(contourPoints, specific));
}
}