如何确定一个2D点是否在多边形内?

我试图创建一个 2D点内多边形算法,用于击中测试(例如Polygon.contains(p:Point))。对有效技术的建议将不胜感激。

391762 次浏览

简单的解决方案是将多边形划分为三角形,并像在这里所解释的那样对三角形进行测试

如果你的多边形是,可能有更好的方法。把这个多边形看作是无限条线的集合。每一行将空间一分为二。对于每一个点,很容易判断它是在直线的一边还是另一边。如果一个点在所有直线的同一侧,那么它在多边形内。

当我还是迈克尔Stonebraker的研究员时,我就做过一些研究——你知道,就是那个提出安格尔PostgreSQL等的教授。

我们意识到最快的方法是首先做一个边界框,因为它非常快。如果它在边界框之外,它就在外面。否则,你就得做更辛苦的工作……

如果你想要一个伟大的算法,看看开源项目PostgreSQL的源代码的地理工作…

我想指出的是,我们从来没有深入了解过左撇子和右撇子(也可以表达为“内”和“外”的问题……


更新

BKB的链接提供了大量合理的算法。我正在研究地球科学问题,因此需要一个适用于纬度/经度的解决方案,它有一个特殊的手性问题——里面的面积是更小的区域还是更大的区域?答案是,顶点的“方向”很重要——它要么是左手的,要么是右手的,这样你就可以把任何一个区域表示为“在”任何给定的多边形内。因此,我的工作使用了那一页上列举的解决方案三。

此外,我的工作使用单独的函数进行“在线”测试。

...因为有人问:我们发现当垂直的数量超过某个数字时,边界盒测试是最好的——如果有必要,在做更长的测试之前做一个非常快速的测试……边界框是通过简单地将最大的x,最小的x,最大的y和最小的y放在一起,组成一个框的四个点来创建的……

另一个提示是:我们在网格空间中进行了所有更复杂的“调光”计算,都是在平面上的正点上进行的,然后重新投影到“真实”的经度/纬度上,从而避免了在经度180线交叉时和处理极地时可能出现的环绕错误。工作好了!

计算点p与每个多边形顶点之间的有向角和。如果总倾斜角是360度,那么这个点在里面。如果总数为0,则点在外面。

我更喜欢这种方法,因为它更健壮,对数值精度的依赖更小。

计算交集数量的均匀性的方法是有限的,因为你可以在计算交集数量的过程中“击中”一个顶点。

编辑:顺便说一下,这种方法适用于凹凸多边形。

编辑:我最近发现了一个关于这个话题的完整的维基百科的文章

David Segond的答案几乎是标准的一般答案,Richard T的是最常见的优化,尽管还有一些其他的。其他强优化基于不太通用的解决方案。例如,如果你要检查带有许多点的同一个多边形,对多边形进行三角化可以极大地加快速度,因为有许多非常快速的TIN搜索算法。另一种方法是,如果多边形和点在一个低分辨率的有限平面上,比如屏幕显示,您可以将多边形绘制到内存映射的显示缓冲区上,并检查给定像素的颜色,以查看它是否位于多边形中。

像许多优化一样,这些优化是基于特定情况而不是一般情况,并且基于摊销时间而不是单次使用产生效益。

在这个领域工作,我发现约瑟夫·奥鲁克斯的《计算几何》在C' ISBN 0-521-44034-3是一个很大的帮助。

对于图形,我宁愿不使用整数。许多系统使用整数绘制UI(像素毕竟是整数),但macOS,例如,使用浮点数。macOS只知道点,一个点可以转换为一个像素,但根据显示器分辨率,它可能转换为其他东西。在视网膜屏幕上,半点(0.5/0.5)是像素。不过,我从来没有注意到macOS的ui明显比其他ui慢。毕竟,3D api (OpenGL或Direct3D)也可以使用浮点数,现代图形库经常利用GPU加速。

现在你说速度是你最关心的,好吧,让我们追求速度。在运行任何复杂的算法之前,先做一个简单的测试。在多边形周围创建一个轴对准包围框。这是非常简单,快速的,已经可以节省你很多计算。这是怎么做到的呢?遍历多边形的所有点,找到X和Y的最小/最大值。

例如,你有点(9/1), (4/3), (2/7), (8/2), (3/6)。这意味着Xmin是2,Xmax是9,Ymin是1,Ymax是7。矩形外有两条边(2/1)和(9/7)的点不可能在多边形内。

// p is your point, p.x is the x coord, p.y is the y coord
if (p.x < Xmin || p.x > Xmax || p.y < Ymin || p.y > Ymax) {
// Definitely not within the polygon!
}

这是对任意点运行的第一个测试。正如你所看到的,这个测试非常快,但也非常粗糙。要处理边界矩形内的点,我们需要更复杂的算法。有几种计算方法。哪种方法有效还取决于多边形是否有孔或始终是固体。以下是实体的例子(一个凸面,一个凹面):

无孔多边形

这里有一个洞:

有洞的多边形

绿色的中间有个洞!

最简单的算法是雷铸造,它可以处理以上三种情况,并且仍然非常快。该算法的思想非常简单:从多边形外的任何地方绘制一条虚拟光线到你的点,并计算它击中多边形一侧的频率。如果命中次数是偶数,则在多边形外,如果是奇数,则在多边形内。

演示射线如何切割多边形

圈数算法将是一个替代方案,它对非常接近多边形线的点更准确,但它也慢得多。由于有限的浮点精度和舍入问题,光线投射可能会因为太靠近多边形一侧的点而失败,但在现实中这几乎不是问题,因为如果一个点靠近一侧,在视觉上甚至不可能让观看者识别它是否已经在内部或仍然在外部。

还记得上面的边界框吗?只需在边界框外选择一个点,并将其用作射线的起点。例如,点(Xmin - e/p.y)肯定在多边形外。

但是什么是e?好吧,e(实际上是)给边界框一些填充。如我所说,如果我们开始时太靠近多边形线,光线追踪就会失败。因为边界框可能等于多边形(如果多边形是一个轴对齐的矩形,边界框等于多边形本身!),我们需要一些填充来确保安全,就是这样。你应该选择多大的e?不要太大。这取决于你用于绘图的坐标系统比例。如果你的像素步宽是1.0,那么就选择1.0(0.1也可以)

现在我们有了光线的起始坐标和结束坐标,问题就从"点在多边形内吗"“# EYZ1"。因此,我们不能像以前那样只处理多边形点,现在我们需要实际的边。一条边总是由两点来定义。

side 1: (X1/Y1)-(X2/Y2)
side 2: (X2/Y2)-(X3/Y3)
side 3: (X3/Y3)-(X4/Y4)
:

你需要从各个方面测试光线。假设射线是一个矢量,每条边都是一个矢量。光线必须恰好击中每边一次,否则就永远不会。它不可能击中同一侧两次。二维空间中的两条直线总是只相交一次,除非它们是平行的,在这种情况下,它们永远不会相交。然而,由于向量的长度是有限的,两个向量可能不平行,也永远不会相交,因为它们太短而无法相遇。

// Test the ray against all sides
int intersections = 0;
for (side = 0; side < numberOfSides; side++) {
// Test if current side intersects with ray.
// If yes, intersections++;
}
if ((intersections & 1) == 1) {
// Inside of polygon
} else {
// Outside of polygon
}

到目前为止一切顺利,但是如何检验两个向量是否相交呢?下面是一些C代码(未测试),应该可以做到:

#define NO 0
#define YES 1
#define COLLINEAR 2


int areIntersecting(
float v1x1, float v1y1, float v1x2, float v1y2,
float v2x1, float v2y1, float v2x2, float v2y2
) {
float d1, d2;
float a1, a2, b1, b2, c1, c2;


// Convert vector 1 to a line (line 1) of infinite length.
// We want the line in linear equation standard form: A*x + B*y + C = 0
// See: http://en.wikipedia.org/wiki/Linear_equation
a1 = v1y2 - v1y1;
b1 = v1x1 - v1x2;
c1 = (v1x2 * v1y1) - (v1x1 * v1y2);


// Every point (x,y), that solves the equation above, is on the line,
// every point that does not solve it, is not. The equation will have a
// positive result if it is on one side of the line and a negative one
// if is on the other side of it. We insert (x1,y1) and (x2,y2) of vector
// 2 into the equation above.
d1 = (a1 * v2x1) + (b1 * v2y1) + c1;
d2 = (a1 * v2x2) + (b1 * v2y2) + c1;


// If d1 and d2 both have the same sign, they are both on the same side
// of our line 1 and in that case no intersection is possible. Careful,
// 0 is a special case, that's why we don't test ">=" and "<=",
// but "<" and ">".
if (d1 > 0 && d2 > 0) return NO;
if (d1 < 0 && d2 < 0) return NO;


// The fact that vector 2 intersected the infinite line 1 above doesn't
// mean it also intersects the vector 1. Vector 1 is only a subset of that
// infinite line 1, so it may have intersected that line before the vector
// started or after it ended. To know for sure, we have to repeat the
// the same test the other way round. We start by calculating the
// infinite line 2 in linear equation standard form.
a2 = v2y2 - v2y1;
b2 = v2x1 - v2x2;
c2 = (v2x2 * v2y1) - (v2x1 * v2y2);


// Calculate d1 and d2 again, this time using points of vector 1.
d1 = (a2 * v1x1) + (b2 * v1y1) + c2;
d2 = (a2 * v1x2) + (b2 * v1y2) + c2;


// Again, if both have the same sign (and neither one is 0),
// no intersection is possible.
if (d1 > 0 && d2 > 0) return NO;
if (d1 < 0 && d2 < 0) return NO;


// If we get here, only two possibilities are left. Either the two
// vectors intersect in exactly one point or they are collinear, which
// means they intersect in any number of points from zero to infinite.
if ((a1 * b2) - (a2 * b1) == 0.0f) return COLLINEAR;


// If they are not collinear, they must intersect in exactly one point.
return YES;
}

输入值是向量1 (v1x1/v1y1v1x2/v1y2)和向量2 (v2x1/v2y1v2x2/v2y2)的两个端点。有2个向量,4个点,8个坐标。YESNO是清晰的。YES增加了十字路口,NO什么都没有。

共线呢?这意味着两个向量都在同一条无限大的直线上,取决于位置和长度,它们完全不相交,或者它们在无穷多的点上相交。我不太确定如何处理这种情况,无论如何我都不会把它算作交集。因为浮点舍入误差,这种情况在实践中很少见;更好的代码可能不会测试== 0.0f,而是测试像< epsilon这样的东西,其中的是一个相当小的数字。

如果你需要测试更多的点,你当然可以通过在内存中保留多边形边的线性方程标准形式来加快整个过程,这样你就不必每次都重新计算这些点。这将在每次测试中为您节省两次浮点乘法和三次浮点减法,以换取在内存中为每个多边形边存储三个浮点值。这是一个典型的内存与计算时间的权衡。

最后但并非最不重要的是:如果你可能使用3D硬件来解决问题,还有一个有趣的替代方案。让GPU为你做所有的工作。创建一个屏幕外的绘画表面。用黑色填充它。现在让OpenGL或Direct3D绘制你的多边形(甚至是所有的多边形,如果你只是想测试点是否在其中任何一个,但你不关心哪个),并用不同的颜色填充多边形,例如白色。若要检查点是否在多边形内,请从绘图表面获取该点的颜色。这只是一个O(1)内存读取。

当然,这种方法只适用于你的绘图曲面不是很大的情况。如果它不适合GPU内存,这种方法比在CPU上执行要慢。如果它必须是巨大的,你的GPU支持现代着色器,你仍然可以使用GPU通过实现如上所示的射线投射作为GPU着色器,这是绝对可能的。对于大量的多边形或大量的点进行测试,这是值得的,考虑到一些gpu将能够并行测试64到256个点。但是请注意,将数据从CPU传输到GPU再传输回来总是很昂贵的,所以对于仅仅针对几个简单多边形测试几个点,其中的点或多边形都是动态的,并且会频繁变化,GPU方法很少会有回报。

bobobobo引用的Eric Haines文章非常棒。特别有趣的是比较算法性能的表格;角度求和法和其他方法比起来真的很差。同样有趣的是,使用查找网格将多边形进一步细分为“in”和“out”扇区的优化可以使测试非常快,即使是在> 1000条边的多边形上。

不管怎样,现在还为时过早,但我的投票倾向于“交叉”方法,我认为这几乎就是Mecki所描述的。然而,我发现它最简洁的由大卫·伯克描述和编纂。我喜欢它不需要真正的三角函数,它适用于凸和凹,而且随着边数的增加,它的表现也相当不错。

顺便说一下,这是Eric Haines文章中的一个性能表,在随机多边形上进行测试。

                       number of edges per polygon
3       4      10      100    1000
MacMartin               2.9     3.2     5.9     50.6    485
Crossings               3.1     3.4     6.8     60.0    624
Triangle Fan+edge sort  1.1     1.8     6.5     77.6    787
Triangle Fan            1.2     2.1     7.3     85.4    865
Barycentric             2.1     3.8    13.8    160.7   1665
Angle Summation        56.2    70.4   153.6   1403.8  14693


Grid (100x100)          1.5     1.5     1.6      2.1      9.8
Grid (20x20)            1.7     1.7     1.9      5.7     42.2
Bins (100)              1.8     1.9     2.7     15.1    117
Bins (20)               2.1     2.2     3.7     26.3    278

我认为下面这段代码是最好的解决方案(摘自在这里):

int pnpoly(int nvert, float *vertx, float *verty, float testx, float testy)
{
int i, j, c = 0;
for (i = 0, j = nvert-1; i < nvert; j = i++) {
if ( ((verty[i]>testy) != (verty[j]>testy)) &&
(testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) )
c = !c;
}
return c;
}

参数

  • nvert:多边形顶点的数量。是否在末端重复第一个顶点在上面的文章中已经讨论过了。
  • 包含多边形顶点的x坐标和y坐标的数组。
  • testx,暴躁的:测试点的X坐标和y坐标。

它既简短又高效,适用于凸多边形和凹多边形。如前所述,您应该首先检查边界矩形,并单独处理多边形孔。

这背后的想法很简单。作者描述如下:

我从测试点水平运行一条半无限射线(增加x,固定y),并计算它穿过多少条边。在每个十字路口,光线在内部和外部之间切换。这叫做乔丹曲线定理。

当水平射线穿过任意一条边时,变量c从0变为1,从1变为0。基本上它记录了交叉边的数量是偶数还是奇数。0表示偶数,1表示奇数。

我知道这是旧的,但这里是一个在Cocoa实现的光线投射算法,如果有人感兴趣的话。不确定这是最有效的方法,但它可能会帮助别人。

- (BOOL)shape:(NSBezierPath *)path containsPoint:(NSPoint)point
{
NSBezierPath *currentPath = [path bezierPathByFlatteningPath];
BOOL result;
float aggregateX = 0; //I use these to calculate the centroid of the shape
float aggregateY = 0;
NSPoint firstPoint[1];
[currentPath elementAtIndex:0 associatedPoints:firstPoint];
float olderX = firstPoint[0].x;
float olderY = firstPoint[0].y;
NSPoint interPoint;
int noOfIntersections = 0;


for (int n = 0; n < [currentPath elementCount]; n++) {
NSPoint points[1];
[currentPath elementAtIndex:n associatedPoints:points];
aggregateX += points[0].x;
aggregateY += points[0].y;
}


for (int n = 0; n < [currentPath elementCount]; n++) {
NSPoint points[1];


[currentPath elementAtIndex:n associatedPoints:points];
//line equations in Ax + By = C form
float _A_FOO = (aggregateY/[currentPath elementCount]) - point.y;
float _B_FOO = point.x - (aggregateX/[currentPath elementCount]);
float _C_FOO = (_A_FOO * point.x) + (_B_FOO * point.y);


float _A_BAR = olderY - points[0].y;
float _B_BAR = points[0].x - olderX;
float _C_BAR = (_A_BAR * olderX) + (_B_BAR * olderY);


float det = (_A_FOO * _B_BAR) - (_A_BAR * _B_FOO);
if (det != 0) {
//intersection points with the edges
float xIntersectionPoint = ((_B_BAR * _C_FOO) - (_B_FOO * _C_BAR)) / det;
float yIntersectionPoint = ((_A_FOO * _C_BAR) - (_A_BAR * _C_FOO)) / det;
interPoint = NSMakePoint(xIntersectionPoint, yIntersectionPoint);
if (olderX <= points[0].x) {
//doesn't matter in which direction the ray goes, so I send it right-ward.
if ((interPoint.x >= olderX && interPoint.x <= points[0].x) && (interPoint.x > point.x)) {
noOfIntersections++;
}
} else {
if ((interPoint.x >= points[0].x && interPoint.x <= olderX) && (interPoint.x > point.x)) {
noOfIntersections++;
}
}
}
olderX = points[0].x;
olderY = points[0].y;
}
if (noOfIntersections % 2 == 0) {
result = FALSE;
} else {
result = TRUE;
}
return result;
}

net端口:

    static void Main(string[] args)
{


Console.Write("Hola");
List<double> vertx = new List<double>();
List<double> verty = new List<double>();


int i, j, c = 0;


vertx.Add(1);
vertx.Add(2);
vertx.Add(1);
vertx.Add(4);
vertx.Add(4);
vertx.Add(1);


verty.Add(1);
verty.Add(2);
verty.Add(4);
verty.Add(4);
verty.Add(1);
verty.Add(1);


int nvert = 6;  //Vértices del poligono


double testx = 2;
double testy = 5;




for (i = 0, j = nvert - 1; i < nvert; j = i++)
{
if (((verty[i] > testy) != (verty[j] > testy)) &&
(testx < (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + vertx[i]))
c = 1;
}
}

nirg的c#版本的答案在这里:我只分享代码。这可能会节省一些时间。

public static bool IsPointInPolygon(IList<Point> polygon, Point testPoint) {
bool result = false;
int j = polygon.Count() - 1;
for (int i = 0; i < polygon.Count(); i++) {
if (polygon[i].Y < testPoint.Y && polygon[j].Y >= testPoint.Y || polygon[j].Y < testPoint.Y && polygon[i].Y >= testPoint.Y) {
if (polygon[i].X + (testPoint.Y - polygon[i].Y) / (polygon[j].Y - polygon[i].Y) * (polygon[j].X - polygon[i].X) < testPoint.X) {
result = !result;
}
}
j = i;
}
return result;
}

Obj-C版本nirg的答案与样本方法测试点。Nirg的回答对我很有效。

- (BOOL)isPointInPolygon:(NSArray *)vertices point:(CGPoint)test {
NSUInteger nvert = [vertices count];
NSInteger i, j, c = 0;
CGPoint verti, vertj;


for (i = 0, j = nvert-1; i < nvert; j = i++) {
verti = [(NSValue *)[vertices objectAtIndex:i] CGPointValue];
vertj = [(NSValue *)[vertices objectAtIndex:j] CGPointValue];
if (( (verti.y > test.y) != (vertj.y > test.y) ) &&
( test.x < ( vertj.x - verti.x ) * ( test.y - verti.y ) / ( vertj.y - verti.y ) + verti.x) )
c = !c;
}


return (c ? YES : NO);
}


- (void)testPoint {


NSArray *polygonVertices = [NSArray arrayWithObjects:
[NSValue valueWithCGPoint:CGPointMake(13.5, 41.5)],
[NSValue valueWithCGPoint:CGPointMake(42.5, 56.5)],
[NSValue valueWithCGPoint:CGPointMake(39.5, 69.5)],
[NSValue valueWithCGPoint:CGPointMake(42.5, 84.5)],
[NSValue valueWithCGPoint:CGPointMake(13.5, 100.0)],
[NSValue valueWithCGPoint:CGPointMake(6.0, 70.5)],
nil
];


CGPoint tappedPoint = CGPointMake(23.0, 70.0);


if ([self isPointInPolygon:polygonVertices point:tappedPoint]) {
NSLog(@"YES");
} else {
NSLog(@"NO");
}
}

sample polygon

没有什么比归纳定义问题更美好的了。为了完整起见,你在序言中有一个版本,它可能也会澄清雷铸造背后的思想:

基于http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html仿真的简化算法

一些helper谓词:

exor(A,B):- \+A,B;A,\+B.
in_range(Coordinate,CA,CB) :- exor((CA>Coordinate),(CB>Coordinate)).


inside(false).
inside(_,[_|[]]).
inside(X:Y, [X1:Y1,X2:Y2|R]) :- in_range(Y,Y1,Y2), X > ( ((X2-X1)*(Y-Y1))/(Y2-Y1) +      X1),toggle_ray, inside(X:Y, [X2:Y2|R]); inside(X:Y, [X2:Y2|R]).


get_line(_,_,[]).
get_line([XA:YA,XB:YB],[X1:Y1,X2:Y2|R]):- [XA:YA,XB:YB]=[X1:Y1,X2:Y2]; get_line([XA:YA,XB:YB],[X2:Y2|R]).

给定两点a和B的直线(直线(a,B))方程为:

                    (YB-YA)
Y - YA = ------- * (X - XA)
(XB-YB)
重要的是,直线的旋转方向是 设置为顺时针为边界,逆时针为洞。 我们要检查点(X,Y),即被测点是否在左边 我们线的半平面(这是一个品味问题,也可能是 右边,还有边界线的方向也要改变 这种情况下),这是将射线从该点向右(或向左)投射 并确认与直线的交点。我们选择了投射 水平方向的射线(这也是个人口味的问题, 它也可以在垂直方向上做,有类似的限制),所以我们有:

               (XB-XA)
X < ------- * (Y - YA) + XA
(YB-YA)
现在我们需要知道这个点是在的左边(还是右边) 只是线段,不是整个平面,所以我们需要 将搜索限制在此段,但这很容易,因为 要在线段内,直线上只有一个点可以更高 在纵轴上比Y大。因为这是一个更强的限制 需要第一个检查,所以我们只先检查那些线 满足此要求后再检查其位置。约旦河边 曲线定理任何投影到多边形上的射线都必须与点相交 偶数行。做完了,我们把射线扔给 然后每次它与一条线相交时,切换它的状态。 然而,在我们的实现中,我们将检查的长度 袋解满足给定的限制,并决定 对它的所有权。对于多边形中的每一条线都必须这样做
is_left_half_plane(_,[],[],_).
is_left_half_plane(X:Y,[XA:YA,XB:YB], [[X1:Y1,X2:Y2]|R], Test) :- [XA:YA, XB:YB] =  [X1:Y1, X2:Y2], call(Test, X , (((XB - XA) * (Y - YA)) / (YB - YA) + XA));
is_left_half_plane(X:Y, [XA:YA, XB:YB], R, Test).


in_y_range_at_poly(Y,[XA:YA,XB:YB],Polygon) :- get_line([XA:YA,XB:YB],Polygon),  in_range(Y,YA,YB).
all_in_range(Coordinate,Polygon,Lines) :- aggregate(bag(Line),    in_y_range_at_poly(Coordinate,Line,Polygon), Lines).


traverses_ray(X:Y, Lines, Count) :- aggregate(bag(Line), is_left_half_plane(X:Y, Line, Lines, <), IntersectingLines), length(IntersectingLines, Count).


% This is the entry point predicate
inside_poly(X:Y,Polygon,Answer) :- all_in_range(Y,Polygon,Lines), traverses_ray(X:Y, Lines, Count), (1 is mod(Count,2)->Answer=inside;Answer=outside).

下面是由nirg给出的答案的c#版本,它来自这位RPI教授。请注意,使用来自RPI源代码的代码需要归属。

在顶部添加了一个边界框复选。然而,正如James Brown所指出的,主代码几乎和边界框检查本身一样快,所以边界框检查实际上会减慢整体操作,因为您正在检查的大多数点都在边界框内。所以你可以让边界框签出,或者另一种选择是预先计算多边形的边界框,如果它们不经常改变形状的话。

public bool IsPointInPolygon( Point p, Point[] polygon )
{
double minX = polygon[ 0 ].X;
double maxX = polygon[ 0 ].X;
double minY = polygon[ 0 ].Y;
double maxY = polygon[ 0 ].Y;
for ( int i = 1 ; i < polygon.Length ; i++ )
{
Point q = polygon[ i ];
minX = Math.Min( q.X, minX );
maxX = Math.Max( q.X, maxX );
minY = Math.Min( q.Y, minY );
maxY = Math.Max( q.Y, maxY );
}


if ( p.X < minX || p.X > maxX || p.Y < minY || p.Y > maxY )
{
return false;
}


// https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html
bool inside = false;
for ( int i = 0, j = polygon.Length - 1 ; i < polygon.Length ; j = i++ )
{
if ( ( polygon[ i ].Y > p.Y ) != ( polygon[ j ].Y > p.Y ) &&
p.X < ( polygon[ j ].X - polygon[ i ].X ) * ( p.Y - polygon[ i ].Y ) / ( polygon[ j ].Y - polygon[ i ].Y ) + polygon[ i ].X )
{
inside = !inside;
}
}


return inside;
}

真的很喜欢Nirg发布的解决方案,由bobobobo编辑。我只是让它javascript友好,更容易读懂我的使用:

function insidePoly(poly, pointx, pointy) {
var i, j;
var inside = false;
for (i = 0, j = poly.length - 1; i < poly.length; j = i++) {
if(((poly[i].y > pointy) != (poly[j].y > pointy)) && (pointx < (poly[j].x-poly[i].x) * (pointy-poly[i].y) / (poly[j].y-poly[i].y) + poly[i].x) ) inside = !inside;
}
return inside;
}

以下是M. Katz基于Nirg方法的答案的JavaScript变体:

function pointIsInPoly(p, polygon) {
var isInside = false;
var minX = polygon[0].x, maxX = polygon[0].x;
var minY = polygon[0].y, maxY = polygon[0].y;
for (var n = 1; n < polygon.length; n++) {
var q = polygon[n];
minX = Math.min(q.x, minX);
maxX = Math.max(q.x, maxX);
minY = Math.min(q.y, minY);
maxY = Math.max(q.y, maxY);
}


if (p.x < minX || p.x > maxX || p.y < minY || p.y > maxY) {
return false;
}


var i = 0, j = polygon.length - 1;
for (i, j; i < polygon.length; j = i++) {
if ( (polygon[i].y > p.y) != (polygon[j].y > p.y) &&
p.x < (polygon[j].x - polygon[i].x) * (p.y - polygon[i].y) / (polygon[j].y - polygon[i].y) + polygon[i].x ) {
isInside = !isInside;
}
}


return isInside;
}

Java版本:

public class Geocode {
private float latitude;
private float longitude;


public Geocode() {
}


public Geocode(float latitude, float longitude) {
this.latitude = latitude;
this.longitude = longitude;
}


public float getLatitude() {
return latitude;
}


public void setLatitude(float latitude) {
this.latitude = latitude;
}


public float getLongitude() {
return longitude;
}


public void setLongitude(float longitude) {
this.longitude = longitude;
}
}


public class GeoPolygon {
private ArrayList<Geocode> points;


public GeoPolygon() {
this.points = new ArrayList<Geocode>();
}


public GeoPolygon(ArrayList<Geocode> points) {
this.points = points;
}


public GeoPolygon add(Geocode geo) {
points.add(geo);
return this;
}


public boolean inside(Geocode geo) {
int i, j;
boolean c = false;
for (i = 0, j = points.size() - 1; i < points.size(); j = i++) {
if (((points.get(i).getLongitude() > geo.getLongitude()) != (points.get(j).getLongitude() > geo.getLongitude())) &&
(geo.getLatitude() < (points.get(j).getLatitude() - points.get(i).getLatitude()) * (geo.getLongitude() - points.get(i).getLongitude()) / (points.get(j).getLongitude() - points.get(i).getLongitude()) + points.get(i).getLatitude()))
c = !c;
}
return c;
}


}

在C语言的多边形测试中,有一个点没有使用光线投射。它可以用于重叠区域(自我交叉),参见use_holes参数。

/* math lib (defined below) */
static float dot_v2v2(const float a[2], const float b[2]);
static float angle_signed_v2v2(const float v1[2], const float v2[2]);
static void copy_v2_v2(float r[2], const float a[2]);


/* intersection function */
bool isect_point_poly_v2(const float pt[2], const float verts[][2], const unsigned int nr,
const bool use_holes)
{
/* we do the angle rule, define that all added angles should be about zero or (2 * PI) */
float angletot = 0.0;
float fp1[2], fp2[2];
unsigned int i;
const float *p1, *p2;


p1 = verts[nr - 1];


/* first vector */
fp1[0] = p1[0] - pt[0];
fp1[1] = p1[1] - pt[1];


for (i = 0; i < nr; i++) {
p2 = verts[i];


/* second vector */
fp2[0] = p2[0] - pt[0];
fp2[1] = p2[1] - pt[1];


/* dot and angle and cross */
angletot += angle_signed_v2v2(fp1, fp2);


/* circulate */
copy_v2_v2(fp1, fp2);
p1 = p2;
}


angletot = fabsf(angletot);
if (use_holes) {
const float nested = floorf((angletot / (float)(M_PI * 2.0)) + 0.00001f);
angletot -= nested * (float)(M_PI * 2.0);
return (angletot > 4.0f) != ((int)nested % 2);
}
else {
return (angletot > 4.0f);
}
}


/* math lib */


static float dot_v2v2(const float a[2], const float b[2])
{
return a[0] * b[0] + a[1] * b[1];
}


static float angle_signed_v2v2(const float v1[2], const float v2[2])
{
const float perp_dot = (v1[1] * v2[0]) - (v1[0] * v2[1]);
return atan2f(perp_dot, dot_v2v2(v1, v2));
}


static void copy_v2_v2(float r[2], const float a[2])
{
r[0] = a[0];
r[1] = a[1];
}

注意:这是一个不太理想的方法,因为它包含很多对atan2f的调用,但它可能会引起阅读这个线程的开发人员的兴趣(在我的测试中,它比使用线交方法慢23倍)。

这只适用于凸形状,但是Minkowski Portal Refinement和GJK也是测试一个点是否在多边形中的很好的选择。您使用闵可夫斯基减法从多边形中减去点,然后运行这些算法来查看多边形是否包含原点。

另外,有趣的是,你可以用支持函数更隐式地描述你的形状,它以一个方向向量作为输入,并输出沿该向量的最远点。这可以让你描述任何凸形状..弯曲的,由多边形制成的,或混合的您还可以执行一些操作,将简单支持函数的结果组合起来,以生成更复杂的形状。

< p >更多信息: # EYZ0 < / p >

此外,game programming gems 7讨论了如何在3d中做到这一点(:

通过nirg回答的Swift版本:

extension CGPoint {
func isInsidePolygon(vertices: [CGPoint]) -> Bool {
guard !vertices.isEmpty else { return false }
var j = vertices.last!, c = false
for i in vertices {
let a = (i.y > y) != (j.y > y)
let b = (x < (j.x - i.x) * (y - i.y) / (j.y - i.y) + i.x)
if a && b { c = !c }
j = i
}
return c
}
}

对于检测多边形上的命中,我们需要测试两件事:

  1. 如果点在多边形区域内。(可通过Ray-Casting算法实现)
  2. 如果点在多边形边界上(可以用与在折线(线)上检测点相同的算法来完成)。

光线投射算法中处理以下特殊情况:

  1. 射线与多边形的一条边重叠。
  2. 点在多边形的内部,光线穿过多边形的顶点。
  3. 该点在多边形的外部,光线只接触到多边形的一个角。

检查# EYZ0。本文提供了一种简单的解决方法,因此对于上述情况不需要特殊处理。

您可以通过检查将所需点连接到多边形顶点所形成的面积是否与多边形本身的面积相匹配来实现这一点。

或者你可以检查从你的点到每一对连续的多边形顶点到你的检查点的内角之和是否为360,但我有一种感觉,第一种选择更快,因为它不涉及除法,也不计算三角函数的反函数。

我不知道如果你的多边形内部有一个洞会发生什么,但在我看来,主要思想可以适应这种情况

你也可以把问题贴在数学社区里。我打赌他们有一百万种方法

如果你正在寻找一个java脚本库,有一个javascript谷歌maps v3扩展的Polygon类,以检测是否有一个点驻留在它里面。

var polygon = new google.maps.Polygon([], "#000000", 1, 1, "#336699", 0.3);
var isWithinPolygon = polygon.containsLatLng(40, -90);

谷歌扩展Github

当使用 (Qt 4.3+)时,可以使用QPolygon的函数containsPoint

VBA版本:

注意:请记住,如果你的多边形是地图中的一个区域,纬度/经度是Y/X值,而不是X/Y(纬度= Y,经度= X),因为从我的理解来看,这是历史含义,因为经度不是一个测量值。

类模块:CPoint

Private pXValue As Double
Private pYValue As Double


'''''X Value Property'''''


Public Property Get X() As Double
X = pXValue
End Property


Public Property Let X(Value As Double)
pXValue = Value
End Property


'''''Y Value Property'''''


Public Property Get Y() As Double
Y = pYValue
End Property


Public Property Let Y(Value As Double)
pYValue = Value
End Property

模块:

Public Function isPointInPolygon(p As CPoint, polygon() As CPoint) As Boolean


Dim i As Integer
Dim j As Integer
Dim q As Object
Dim minX As Double
Dim maxX As Double
Dim minY As Double
Dim maxY As Double
minX = polygon(0).X
maxX = polygon(0).X
minY = polygon(0).Y
maxY = polygon(0).Y


For i = 1 To UBound(polygon)
Set q = polygon(i)
minX = vbMin(q.X, minX)
maxX = vbMax(q.X, maxX)
minY = vbMin(q.Y, minY)
maxY = vbMax(q.Y, maxY)
Next i


If p.X < minX Or p.X > maxX Or p.Y < minY Or p.Y > maxY Then
isPointInPolygon = False
Exit Function
End If




' SOURCE: http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html


isPointInPolygon = False
i = 0
j = UBound(polygon)


Do While i < UBound(polygon) + 1
If (polygon(i).Y > p.Y) Then
If (polygon(j).Y < p.Y) Then
If p.X < (polygon(j).X - polygon(i).X) * (p.Y - polygon(i).Y) / (polygon(j).Y - polygon(i).Y) + polygon(i).X Then
isPointInPolygon = True
Exit Function
End If
End If
ElseIf (polygon(i).Y < p.Y) Then
If (polygon(j).Y > p.Y) Then
If p.X < (polygon(j).X - polygon(i).X) * (p.Y - polygon(i).Y) / (polygon(j).Y - polygon(i).Y) + polygon(i).X Then
isPointInPolygon = True
Exit Function
End If
End If
End If
j = i
i = i + 1
Loop
End Function


Function vbMax(n1, n2) As Double
vbMax = IIf(n1 > n2, n1, n2)
End Function


Function vbMin(n1, n2) As Double
vbMin = IIf(n1 > n2, n2, n1)
End Function




Sub TestPointInPolygon()


Dim i As Integer
Dim InPolygon As Boolean


'   MARKER Object
Dim p As CPoint
Set p = New CPoint
p.X = <ENTER X VALUE HERE>
p.Y = <ENTER Y VALUE HERE>


'   POLYGON OBJECT
Dim polygon() As CPoint
ReDim polygon(<ENTER VALUE HERE>) 'Amount of vertices in polygon - 1
For i = 0 To <ENTER VALUE HERE> 'Same value as above
Set polygon(i) = New CPoint
polygon(i).X = <ASSIGN X VALUE HERE> 'Source a list of values that can be looped through
polgyon(i).Y = <ASSIGN Y VALUE HERE> 'Source a list of values that can be looped through
Next i


InPolygon = isPointInPolygon(p, polygon)
MsgBox InPolygon


End Sub

答案取决于你用的是简单多边形还是复杂多边形。简单多边形不能有任何线段交点。所以它们可以有洞,但线不能交叉。复杂区域可以有直线交点,所以它们可以有重叠的区域,或者只有一点相交的区域。

对于简单多边形,最好的算法是光线投射(交叉数)算法。对于复杂多边形,该算法不检测重叠区域内的点。所以对于复杂多边形你必须使用圈数算法。

下面是一篇用C实现这两种算法的优秀文章。我试过了,效果不错。

http://geomalgorithms.com/a03-_inclusion.html

这个问题很有趣。我有另一个可行的想法,不同于这篇文章的其他答案。其原理是利用角度之和来判断目标是在内部还是外部。更广为人知的名字是圈数

设x为目标点。让数组[0,1,....N]是该区域的所有点。用一条线将目标点与每一个边界点连接起来。如果目标点在这个区域内。所有角的和是360度。如果不是,角度将小于360度。

参考这张图片,对这个想法有一个基本的了解: # EYZ0 < / p >

我的算法假设顺时针是正方向。这是一个潜在的输入:

[[-122.402015, 48.225216], [-117.032049, 48.999931], [-116.919132, 45.995175], [-124.079107, 46.267259], [-124.717175, 48.377557], [-122.92315, 47.047963], [-122.402015, 48.225216]]

下面是实现这个想法的python代码:

def isInside(self, border, target):
degree = 0
for i in range(len(border) - 1):
a = border[i]
b = border[i + 1]


# calculate distance of vector
A = getDistance(a[0], a[1], b[0], b[1]);
B = getDistance(target[0], target[1], a[0], a[1])
C = getDistance(target[0], target[1], b[0], b[1])


# calculate direction of vector
ta_x = a[0] - target[0]
ta_y = a[1] - target[1]
tb_x = b[0] - target[0]
tb_y = b[1] - target[1]


cross = tb_y * ta_x - tb_x * ta_y
clockwise = cross < 0


# calculate sum of angles
if(clockwise):
degree = degree + math.degrees(math.acos((B * B + C * C - A * A) / (2.0 * B * C)))
else:
degree = degree - math.degrees(math.acos((B * B + C * C - A * A) / (2.0 * B * C)))


if(abs(round(degree) - 360) <= 3):
return True
return False

Scala版本的解决方案由nirg(假设边界矩形预检查是单独完成的):

def inside(p: Point, polygon: Array[Point], bounds: Bounds): Boolean = {


val length = polygon.length


@tailrec
def oddIntersections(i: Int, j: Int, tracker: Boolean): Boolean = {
if (i == length)
tracker
else {
val intersects = (polygon(i).y > p.y) != (polygon(j).y > p.y) && p.x < (polygon(j).x - polygon(i).x) * (p.y - polygon(i).y) / (polygon(j).y - polygon(i).y) + polygon(i).x
oddIntersections(i + 1, i, if (intersects) !tracker else tracker)
}
}


oddIntersections(0, length - 1, tracker = false)
}

我已经做了nirg的 c++ 代码的Python实现:

输入

  • 组成多边形的bounding_points:个节点。
  • bounding_box_positions:候选点到过滤器。(在我从边界框创建的实现中。

    (输入是元组列表,格式为:[(xcord, ycord), ...])

返回

  • 多边形内的所有点。
def polygon_ray_casting(self, bounding_points, bounding_box_positions):
# Arrays containing the x- and y-coordinates of the polygon's vertices.
vertx = [point[0] for point in bounding_points]
verty = [point[1] for point in bounding_points]
# Number of vertices in the polygon
nvert = len(bounding_points)
# Points that are inside
points_inside = []


# For every candidate position within the bounding box
for idx, pos in enumerate(bounding_box_positions):
testx, testy = (pos[0], pos[1])
c = 0
for i in range(0, nvert):
j = i - 1 if i != 0 else nvert - 1
if( ((verty[i] > testy ) != (verty[j] > testy))   and
(testx < (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + vertx[i]) ):
c += 1
# If odd, that means that we are inside the polygon
if c % 2 == 1:
points_inside.append(pos)




return points_inside

同样,这个想法来自在这里

下面是golang版本的@nirg答案(灵感来自于@@m-katz的c#代码)

func isPointInPolygon(polygon []point, testp point) bool {
minX := polygon[0].X
maxX := polygon[0].X
minY := polygon[0].Y
maxY := polygon[0].Y


for _, p := range polygon {
minX = min(p.X, minX)
maxX = max(p.X, maxX)
minY = min(p.Y, minY)
maxY = max(p.Y, maxY)
}


if testp.X < minX || testp.X > maxX || testp.Y < minY || testp.Y > maxY {
return false
}


inside := false
j := len(polygon) - 1
for i := 0; i < len(polygon); i++ {
if (polygon[i].Y > testp.Y) != (polygon[j].Y > testp.Y) && testp.X < (polygon[j].X-polygon[i].X)*(testp.Y-polygon[i].Y)/(polygon[j].Y-polygon[i].Y)+polygon[i].X {
inside = !inside
}
j = i
}


return inside
}

令人惊讶的是之前没有人提出这个问题,但是对于需要数据库的实用主义者来说:MongoDB对Geo查询提供了出色的支持,包括这个查询。

你需要的是:

< p > db.neighborhoods。findOne({geometry: {$geoIntersects: {$geometry: { type: "Point",坐标:["经度","纬度"]}}} }) < / p >

Neighborhoods是存储一个或多个标准GeoJson格式多边形的集合。如果查询返回null,则表示不相交,否则为。

这里有很好的记录: # EYZ0 < / p >

在330个不规则多边形网格中,超过6000个点分类的性能不到一分钟,没有任何优化,包括用各自的多边形更新文档的时间。

这似乎在R中工作(为丑陋道歉,希望看到更好的版本!)。

pnpoly <- function(nvert,vertx,verty,testx,testy){
c <- FALSE
j <- nvert
for (i in 1:nvert){
if( ((verty[i]>testy) != (verty[j]>testy)) &&
(testx < (vertx[j]-vertx[i])*(testy-verty[i])/(verty[j]-verty[i])+vertx[i]))
{c <- !c}
j <- i}
return(c)}

如果你正在使用谷歌Map SDK,想要检查一个点是否在一个多边形内,你可以尝试使用GMSGeometryContainsLocation。效果很好!!它是这样运作的,

if GMSGeometryContainsLocation(point, polygon, true) {
print("Inside this polygon.")
} else {
print("outside this polygon")
}

这里是参考:https://developers.google.com/maps/documentation/ios-sdk/reference/group___geometry_utils#gaba958d3776d49213404af249419d0ffd

为了完整起见,下面是由nirg提供并由Mecki讨论的算法的lua实现:

function pnpoly(area, test)
local inside = false
local tx, ty = table.unpack(test)
local j = #area
for i=1, #area do
local vxi, vyi = table.unpack(area[i])
local vxj, vyj = table.unpack(area[j])
if (vyi > ty) ~= (vyj > ty)
and tx < (vxj - vxi)*(ty - vyi)/(vyj - vyi) + vxi
then
inside = not inside
end
j = i
end
return inside
end

变量area是一个点的表,这些点依次存储为2D表。例子:

> A = \{\{2, 1}, {1, 2}, {15, 3}, {3, 4}, {5, 3}, {4, 1.5}}
> T = {2, 1.1}
> pnpoly(A, T)
true

链接到GitHub的要点。

这可能是来自从本页在这里的C代码的稍微优化的版本。

我的c++版本使用std::vector<std::pair<double, double>>和两个double作为x和y。逻辑应该与原始的C代码完全相同,但我发现我的更容易阅读。我不能为表演说话。

bool point_in_poly(std::vector<std::pair<double, double>>& verts, double point_x, double point_y)
{
bool in_poly = false;
auto num_verts = verts.size();
for (int i = 0, j = num_verts - 1; i < num_verts; j = i++) {
double x1 = verts[i].first;
double y1 = verts[i].second;
double x2 = verts[j].first;
double y2 = verts[j].second;


if (((y1 > point_y) != (y2 > point_y)) &&
(point_x < (x2 - x1) * (point_y - y1) / (y2 - y1) + x1))
in_poly = !in_poly;
}
return in_poly;
}

原始的C代码是

int pnpoly(int nvert, float *vertx, float *verty, float testx, float testy)
{
int i, j, c = 0;
for (i = 0, j = nvert-1; i < nvert; j = i++) {
if ( ((verty[i]>testy) != (verty[j]>testy)) &&
(testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) )
c = !c;
}
return c;
}
这个问题的大多数答案并没有很好地处理所有的极端情况。以下是一些微妙的极端情况: ray casting corner cases 这是一个javascript版本,所有角落的情况都得到了很好的处理
/** Get relationship between a point and a polygon using ray-casting algorithm
* @param \{\{x:number, y:number}} P: point to check
* @param \{\{x:number, y:number}[]} polygon: the polygon
* @returns -1: outside, 0: on edge, 1: inside
*/
function relationPP(P, polygon) {
const between = (p, a, b) => p >= a && p <= b || p <= a && p >= b
let inside = false
for (let i = polygon.length-1, j = 0; j < polygon.length; i = j, j++) {
const A = polygon[i]
const B = polygon[j]
// corner cases
if (P.x == A.x && P.y == A.y || P.x == B.x && P.y == B.y) return 0
if (A.y == B.y && P.y == A.y && between(P.x, A.x, B.x)) return 0


if (between(P.y, A.y, B.y)) { // if P inside the vertical range
// filter out "ray pass vertex" problem by treating the line a little lower
if (P.y == A.y && B.y >= A.y || P.y == B.y && A.y >= B.y) continue
// calc cross product `PA X PB`, P lays on left side of AB if c > 0
const c = (A.x - P.x) * (B.y - P.y) - (B.x - P.x) * (A.y - P.y)
if (c == 0) return 0
if ((A.y < B.y) == (c > 0)) inside = !inside
}
}


return inside? 1 : -1
}
from typing import Iterable


def pnpoly(verts, x, y):
#check if x and/or y is iterable
xit, yit = isinstance(x, Iterable), isinstance(y, Iterable)
#if not iterable, make an iterable of length 1
X = x if xit else (x, )
Y = y if yit else (y, )
#store verts length as a range to juggle j
r = range(len(verts))
#final results if x or y is iterable
results = []
#traverse x and y coordinates
for xp in X:
for yp in Y:
c = 0 #reset c at every new position
for i in r:
j = r[i-1] #set j to position before i
#store a few arguments to shorten the if statement
yneq       = (verts[i][1] > yp) != (verts[j][1] > yp)
xofs, yofs = (verts[j][0] - verts[i][0]), (verts[j][1] - verts[i][1])
#if we have crossed a line, increment c
if (yneq and (xp < xofs * (yp - verts[i][1]) / yofs + verts[i][0])):
c += 1
#if c is odd store the coordinates
if c%2:
results.append((xp, yp))
#return either coordinates or a bool, depending if x or y was an iterable
return results if (xit or yit) else bool(c%2)

这个python版本是通用的。您可以为True/False结果输入单个x和单个y值,也可以为xy使用range来遍历整个点网格。如果使用范围,则返回所有True点的x/y对中的listvertices参数期望x/y对的二维Iterable,例如:[(x1,y1), (x2,y2), ...]

使用示例:

vertices = [(25,25), (75,25), (75,75), (25,75)]
pnpoly(vertices, 50, 50) #True
pnpoly(vertices, range(100), range(100)) #[(25,25), (25,26), (25,27), ...]

实际上,这些都可以。

pnpoly(vertices, 50, range(100)) #check 0 to 99 y at x of 50
pnpoly(vertices, range(100), 50) #check 0 to 99 x at y of 50

我认为这是迄今为止所有答案中最简洁的一个。

例如,我们有一个多边形,它看起来像这样: # EYZ0 < / p >

大多边形顶点的二维坐标为

[[139, 483], [227, 792], [482, 849], [523, 670], [352, 330]]

方框顶点的坐标为

[[248, 518], [336, 510], [341, 614], [250, 620]]

空心三角形顶点的坐标为

[[416, 531], [505, 517], [495, 616]]
假设我们想测试两个点[296, 557][422, 730],如果它们在红色区域内(不包括边缘)。如果我们定位这两个点,它将是这样的: # EYZ0 < / p >

显然,[296, 557]不在读取区域内,而[422, 730]在。

我的解决方案是基于圈数算法。下面是我的4行python代码,只使用numpy:

def detect(points, *polygons):
import numpy as np
endpoint1 = np.r_[tuple(np.roll(p, 1, 0) for p in polygons)][:, None] - points
endpoint2 = np.r_[polygons][:, None] - points
p1, p2 = np.cross(endpoint1, endpoint2), np.einsum('...i,...i', endpoint1, endpoint2)
return ~((p1.sum(0) < 0) ^ (abs(np.arctan2(p1, p2).sum(0)) > np.pi) | ((p1 == 0) & (p2 <= 0)).any(0))

要测试实现:

points = [[296, 557], [422, 730]]
polygon1 = [[139, 483], [227, 792], [482, 849], [523, 670], [352, 330]]
polygon2 = [[248, 518], [336, 510], [341, 614], [250, 620]]
polygon3 = [[416, 531], [505, 517], [495, 616]]


print(detect(points, polygon1, polygon2, polygon3))

输出:

[False  True]

就像David Segonds的回答建议我使用从我的凹多边形绘制算法派生的角度和的方法。它依赖于将围绕该点的子三角形的近似角相加来获得权重。1.0附近的权值表示该点在三角形内部,0.0附近的权值表示在三角形外部,-1.0附近的权值是在多边形内部发生的,但顺序相反(就像领结形四边形的其中一半),而NAN的权值恰好位于边上。它不慢的原因是角度根本不需要精确估计。孔可以通过将它们视为单独的多边形并减去权重来处理。

typedef struct { double x, y; } xy_t;


xy_t sub_xy(xy_t a, xy_t b)
{
a.x -= b.x;
a.y -= b.y;
return a;
}


double calc_sharp_subtriangle_pixel_weight(xy_t p0, xy_t p1)
{
xy_t rot, r0, r1;
double weight;


// Rotate points (unnormalised)
rot = sub_xy(p1, p0);
r0.x = rot.x*p0.y - rot.y*p0.x;
r0.y = rot.x*p0.x + rot.y*p0.y;
r1.y = rot.x*p1.x + rot.y*p1.y;


// Calc weight
weight = subtriangle_angle_approx(r1.y, r0.x) - subtriangle_angle_approx(r0.y, r0.x);


return weight;
}


double calc_sharp_polygon_pixel_weight(xy_t p, xy_t *corner, int corner_count)
{
int i;
xy_t p0, p1;
double weight = 0.;


p0 = sub_xy(corner[corner_count-1], p);
for (i=0; i < corner_count; i++)
{
// Transform corner coordinates
p1 = sub_xy(corner[i], p);


// Calculate weight for each subtriangle
weight += calc_sharp_subtriangle_pixel_weight(p0, p1);
p0 = p1;
}


return weight;
}

因此,对于多边形的每一段,都形成一个子三角形,并计算点,然后旋转每个子三角形以计算其近似角度并添加到权重。

subtriangle_angle_approx(y, x)的调用可以用atan2(y, x) / (2.*pi)代替,但是一个非常粗略的近似值就足够精确了:

double subtriangle_angle_approx(double y, double x)
{
double angle, d;
int obtuse;


if (x == 0.)
return NAN;


obtuse = fabs(y) > fabs(x);
if (obtuse)
swap_double(&y, &x);


// Core of the approximation, a very loosely approximate atan(y/x) / (2.*pi) over ]-1 , 1[
d = y / x;
angle = 0.13185 * d;


if (obtuse)
angle = sign(d)*0.25 - angle;


return angle;
}
这是Rust版本的@nirg答案(Philipp Lenssen javascript版本) 我给出这个答案是因为我从这个网站得到了很多帮助,我翻译javascript版本rust作为一个练习,希望可以帮助一些人,最后一个原因是,在我的工作中,我会把这段代码翻译成一个wasm,以提高我的画布的性能,这是一个开始。我的英语很差……,请原谅我 ' < / p >
pub struct Point {
x: f32,
y: f32,
}
pub fn point_is_in_poly(pt: Point, polygon: &Vec<Point>) -> bool {
let mut is_inside = false;


let max_x = polygon.iter().map(|pt| pt.x).reduce(f32::max).unwrap();
let min_x = polygon.iter().map(|pt| pt.x).reduce(f32::min).unwrap();
let max_y = polygon.iter().map(|pt| pt.y).reduce(f32::max).unwrap();
let min_y = polygon.iter().map(|pt| pt.y).reduce(f32::min).unwrap();


if pt.x < min_x || pt.x > max_x || pt.y < min_y || pt.y > max_y {
return is_inside;
}


let len = polygon.len();
let mut j = len - 1;


for i in 0..len {
let y_i_value = polygon[i].y > pt.y;
let y_j_value = polygon[j].y > pt.y;
let last_check = (polygon[j].x - polygon[i].x) * (pt.y - polygon[i].y)
/ (polygon[j].y - polygon[i].y)
+ polygon[i].x;
if y_i_value != y_j_value && pt.x < last_check {
is_inside = !is_inside;
}
j = i;
}
is_inside
}




let pt = Point {
x: 1266.753,
y: 97.655,
};
let polygon = vec![
Point {
x: 725.278,
y: 203.586,
},
Point {
x: 486.831,
y: 441.931,
},
Point {
x: 905.77,
y: 445.241,
},
Point {
x: 1026.649,
y: 201.931,
},
];
let pt1 = Point {
x: 725.278,
y: 203.586,
};
let pt2 = Point {
x: 872.652,
y: 321.103,
};
println!("{}", point_is_in_poly(pt, &polygon));// false
println!("{}", point_is_in_poly(pt1, &polygon)); // true
println!("{}", point_is_in_poly(pt2, &polygon));// true