我正在尝试在多边形算法中创建一个快速的 2D点,以用于点击测试(例如Polygon.contains(p:Point)
)。对于有效技术的建议将不胜感激。
我正在尝试在多边形算法中创建一个快速的 2D点,以用于点击测试(例如Polygon.contains(p:Point)
)。对于有效技术的建议将不胜感激。
Answers:
对于图形,我宁愿不要整数。许多系统使用整数进行UI绘制(像素毕竟是ints),但是macOS例如对所有内容都使用float。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!
}
这是任何时候都可以运行的第一个测试。如您所见,此测试非常快,但也很粗糙。要处理边界矩形内的点,我们需要一种更复杂的算法。有几种方法可以计算出来。哪种方法有效还取决于以下事实:多边形是否可以具有孔或始终是实体。以下是实心示例(一个凸,一个凹)的示例:
这是一个有洞的人:
绿色的中间有一个洞!
可以处理上述所有三种情况并且仍然非常快的最简单算法称为ray cast。该算法的想法非常简单:从多边形外部的任意位置绘制一条虚拟射线到您的点,并计算其撞击多边形一侧的频率。如果命中数是偶数,则在多边形之外,如果是奇数,则在多边形内。
该绕数的算法将是一个另类,它是点非常接近多边形线条更准确,但它也慢得多。由于有限的浮点精度和舍入问题,射线投射可能会由于太靠近多边形边而失败,但实际上这并不是问题,好像点位于边附近,通常在视觉上甚至不可能查看器以识别它是否已经在内部或仍在外部。
您还有上方的边界框,记得吗?只需在边界框外选择一个点,然后将其用作射线的起点即可。例如,该点(Xmin - e/p.y)
肯定在多边形之外。
但是什么e
呢?好吧,e
(实际上是epsilon)给边界框添加了一些填充。如我所说,如果我们开始太靠近多边形线,光线跟踪将失败。由于边界框可能等于多边形(如果多边形是轴对齐的矩形,则边界框等于多边形本身!),我们需要一些填充以使其安全,仅此而已。您应该选择e
多少?不太大 这取决于您用于绘图的坐标系比例。如果您的像素步长为1.0,则只需选择1.0(但0.1也可以)
现在我们有了射线及其开始和结束坐标,问题从“ 多边形内的点 ”变为“ 射线与多边形边相交的频率 ”。因此,我们不能像以前那样仅使用多边形点,现在我们需要实际的边。边总是由两点定义。
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(和)和向量2(和)的两个端点。因此,您有2个向量,4个点,8个坐标。而且很清楚。增加十字路口,什么也不做。v1x1/v1y1
v1x2/v1y2
v2x1/v2y1
v2x2/v2y2
YES
NO
YES
NO
COLLINEAR呢?这意味着两个向量都位于同一条无限线上,具体取决于位置和长度,它们根本不相交,或者它们以无数个点相交。我不太确定如何处理这种情况,无论如何我都不会将其视为交叉路口。嗯,由于浮点舍入错误,这种情况在实践中还是很少见的。更好的代码可能不会测试,== 0.0f
而是会测试< epsilon
,其中epsilon是一个很小的数字。
如果需要测试更多的点,则可以通过将多边形边的线性方程标准形式保留在内存中来一定程度地加快整个过程,因此不必每次都重新计算它们。这样可以在每个测试中节省两个浮点乘法和三个浮点减法,以换取在内存中每个多边形边存储三个浮点值。这是典型的内存与计算时间之间的折衷。
最后但并非最不重要的一点:如果您可以使用3D硬件解决问题,则有一个有趣的替代方法。只需让GPU为您完成所有工作即可。创建不在屏幕上的绘画表面。将其完全填充为黑色。现在,让OpenGL或Direct3D绘制多边形(如果您只是想测试点是否在其中任何一个,但您不在乎哪个,则可以绘制所有多边形),并用不同的多边形填充颜色,例如白色。要检查点是否在多边形内,请从绘图表面获取该点的颜色。这只是一个O(1)内存提取。
当然,仅当您的绘图表面不必很大时,此方法才可用。如果无法容纳到GPU内存中,则此方法比在CPU上执行该方法要慢。如果必须很大并且您的GPU支持现代着色器,您仍然可以通过将上面所示的光线投射实现为GPU着色器来使用GPU,这是绝对可能的。对于要测试的大量多边形或大量点,这会有所作为,考虑到某些GPU能够并行测试64至256点。但是请注意,将数据从CPU传输到GPU并回传总是很昂贵的,因此,仅针对几个简单的多边形测试几个点(点或多边形是动态的并且会经常变化),GPU的方法很少会付出代价关。
我认为以下代码是最好的解决方案(从此处获取):
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;
}
它既短又有效,并且适用于凸多边形和凹多边形。如之前建议的那样,您应该先检查边界矩形,然后分别处理多边形孔。
这背后的想法很简单。作者对此进行了如下描述:
我从测试点水平发出半无限的光线(增加x,固定y),并计算它穿过多少条边。在每个交叉处,射线在内部和外部之间切换。这称为乔丹曲线定理。
每当水平射线穿过任何边缘时,变量c都会从0切换为1,从1切换为0。因此,基本上,它一直在跟踪交叉的边数是偶数还是奇数。0表示偶数,1表示奇数。
verty[i]
并且verty[j]
位于的任一侧testy
,因此它们永远不相等。
这是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;
}
这是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;
}
计算点p与每个多边形顶点之间的角度定向和。如果总定向角度为360度,则该点在内部。如果总数为0,则该点位于外部。
我更喜欢此方法,因为它更健壮且对数值精度的依赖性较小。
计算交叉点数量均匀度的方法是有限的,因为您可以在计算交叉点数量时“击中”顶点。
编辑:顺便说一下,此方法适用于凹凸多边形。
编辑:我最近发现了一个整体 有关该主题 Wikipedia文章。
这个问题真有趣。我还有另一个可行的想法,与本帖子的其他答案不同。这个想法是使用角度之和来确定目标是在内部还是外部。俗称绕组数。
令x为目标点。令数组[0,1,.... n]为该区域的所有点。用一条线将目标点与每个边界点连接起来。如果目标点在此区域内。所有角度的总和将为360度。如果不是,则角度将小于360。
我的算法假设顺时针为正方向。这是一个潜在的输入:
[[-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
埃里克·海恩斯(Eric Haines)的文章bobobobo引用确实很棒。表格比较算法性能特别有趣。角度求和方法与其他方法相比确实很差。同样有趣的是,使用查找网格将多边形进一步细分为“入”和“出”扇区的优化,即使在边数大于1000的多边形上,也可以使测试变得非常快。
无论如何,这是早期,但我的投票投给了“穿越”方法,这是梅基对我的描述。但是我发现它最简洁地由David Bourke描述和编纂。我喜欢不需要真正的三角函数,它适用于凸面和凹面,并且随着边数的增加,它的表现还算不错。
顺便说一下,这是埃里克·海恩斯(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
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
}
}
就像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;
}
当我还是Michael Stonebraker的研究员时,我为此做了一些工作-您知道,是提出Ingres,PostgreSQL等的教授。
我们意识到最快的方法是首先制作一个边界框,因为它超级快。如果它在边界框之外,那么它在外面。否则,您会做得更辛苦...
如果您想使用出色的算法,请查看开源项目PostgreSQL源代码中的地理信息...
我想指出的是,我们从来没有对右撇子和左撇子(也可以表示为“内部”与“外部”问题表达任何见解...
更新
BKB的链接提供了大量合理的算法。我当时正在研究地球科学问题,因此需要一种可以在纬度/经度上使用的解决方案,并且该解决方案具有特殊的惯用性问题-是较小的区域还是较大的区域?答案是顶点的“方向”很重要-它是左撇子还是右撇子,通过这种方式,您可以将任一区域表示为任何给定多边形的“内部”。因此,我的工作使用了该页面上列举的解决方案三。
另外,我的工作使用单独的功能进行“在线”测试。
...由于有人问:我们发现,当折点数超过一定数量时,边界框测试是最好的-如果需要,在进行更长的测试之前先做一个非常快速的测试...通过简单地将边界框最大x,最小x,最大y和最小y并将它们放在一起以构成一个盒子的四个点...
接下来的提示:我们在网格空间中的所有正点上都在网格空间中进行了所有更复杂的“调光”计算,然后将其重新投影回“真实”经度/纬度,从而避免了可能的误差。绕过一条经线180和处理极区时。很棒!
David Segond的答案几乎是标准的一般答案,Richard T的答案是最常见的优化方法,尽管还有其他一些优化方法。其他强大的优化则基于不太通用的解决方案。例如,如果您要检查具有很多点的同一个多边形,则对多边形进行三角剖分可以极大地加快处理速度,因为有许多非常快速的TIN搜索算法。另一个是如果多边形和点在低分辨率的有限平面上(例如屏幕显示),则可以将多边形以给定的颜色绘制到内存映射的显示缓冲区上,并检查给定像素的颜色以查看其是否位于在多边形中。
像许多优化一样,这些优化基于特定情况而不是一般情况,并且收益基于摊销时间而不是一次性使用。
在该领域工作,我发现Joeseph O'Rourkes的C语言ISBN 0-521-44034-3中的“计算几何”是一个很大的帮助。
在平凡的解决办法是将多边形到三角形和命中测试三角形的解释这里
如果您的多边形是CONVEX,则可能会有更好的方法。将多边形视为无限线的集合。每行将空间分成两部分。对于每个点,很容易说出它是在线的一侧还是另一侧。如果点在所有线的同一侧,则该点在面内。
我意识到这很旧,但是这里是可可中实现的光线投射算法,以防万一有人感兴趣。不确定这是做事的最有效方法,但可能会帮助某人。
- (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;
}
nirg答案的Obj-C版本,带有用于测试点的样本方法。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");
}
}
CGPathContainsPoint()
是您的朋友。
CGPathContainsPoint()
没有什么比对问题的归纳定义更美了。为了完整起见,您在序言中有一个版本,该版本可能还会阐明ray cast背后的观点:
基于http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html中简化算法的仿真
一些辅助谓词:
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]).
给定2个点A和B(Line(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#版本在这里:我只分享代码。这样可以节省一些时间。
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;
}
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;
}
}
.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;
}
}
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
输入项
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
再次,这个想法是从这里
令人惊讶的是没有人提过这个,但是对于要求数据库的实用主义者来说:MongoDB对包括此在内的Geo查询提供了出色的支持。
您正在寻找的是:
db.neighborhoods.findOne({几何:{$ geoIntersects:{$ geometry:{类型:“点”,坐标:[“经度”,“纬度”]}}}})
Neighborhoods
是以标准GeoJson格式存储一个或多个多边形的集合。如果查询返回null,则不相交。
很好地记录在这里:https : //docs.mongodb.com/manual/tutorial/geospatial-tutorial/
在330个不规则多边形网格中分类的6,000多个点的性能不到一分钟,根本没有优化,包括用各自的多边形更新文档的时间。
这是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倍)。
在射线投射算法中处理以下特殊情况:
确定点是否在复杂多边形内的检查。本文为解决这些问题提供了一种简便的方法,因此对于上述情况,无需特殊处理。
如果您正在寻找Java脚本库,则可以使用Polygon类的javascript google maps v3扩展来检测点是否位于其中。
var polygon = new google.maps.Polygon([], "#000000", 1, 1, "#336699", 0.3);
var isWithinPolygon = polygon.containsLatLng(40, -90);
使用时 t(Qt 4.3+),可以使用QPolygon的功能containsPoint
答案取决于您是否具有简单或复杂的多边形。简单的多边形不得有任何线段相交。因此它们可以有孔,但线不能交叉。复杂区域可以具有线相交-因此它们可以具有重叠区域,也可以只有一个点相互接触的区域。
对于简单的多边形,最好的算法是射线投射(交叉数)算法。对于复杂的多边形,此算法不会检测重叠区域内的点。因此,对于复杂的多边形,您必须使用绕组数算法。
这是一篇很好的文章,介绍了两种算法的C实现。我尝试了它们,它们运行良好。
nirg的Scala解决方案版本(假定边界矩形预检查是单独完成的):
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答案的golang版本(受@@ 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
}