적중 테스트 (예 :)에 사용하기 위해 다각형 알고리즘 내에 빠른 2D 포인트 를 만들려고합니다 Polygon.contains(p:Point)
. 효과적인 기술에 대한 제안을 부탁드립니다.
적중 테스트 (예 :)에 사용하기 위해 다각형 알고리즘 내에 빠른 2D 포인트 를 만들려고합니다 Polygon.contains(p:Point)
. 효과적인 기술에 대한 제안을 부탁드립니다.
답변:
그래픽의 경우 정수를 선호하지 않습니다. 많은 시스템이 UI 페인팅을 위해 정수를 사용하지만 (픽셀은 정수), 예를 들어 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!
}
이것은 어느 시점에서나 실행되는 첫 번째 테스트입니다. 보시다시피이 테스트는 매우 빠르지 만 매우 거칠습니다. 경계 사각형 내에있는 점을 처리하려면보다 정교한 알고리즘이 필요합니다. 이를 계산하는 방법에는 몇 가지가 있습니다. 어떤 방법이 작동하는지는 다각형에 구멍이 있거나 항상 단색인지에 따라 다릅니다. 다음은 단단한 것들 (하나의 볼록, 하나의 오목)의 예입니다.
그리고 여기에 구멍이 하나 있습니다 :
녹색은 중간에 구멍이 있습니다!
위의 세 가지 경우를 모두 처리 할 수 있고 여전히 매우 빠른 가장 쉬운 알고리즘은 레이 캐스팅 입니다. 알고리즘의 아이디어는 매우 간단합니다. 다각형 외부에서 점까지 가상 광선을 그리고 다각형 측면에 얼마나 자주 충돌하는지 계산합니다. 적중 수가 짝수이면 다각형 외부에 있고 홀수이면 내부에 있습니다.
권수 알고리즘은 매우 가까운 다각형 라인에있는 점에 대한 더 정확하지만 훨씬 낮은 속도도의 대안이 될 것입니다. 부동 소수점 정밀도 및 반올림 문제로 인해 다각형쪽에 너무 가까운 점에 대해서는 광선 주조가 실패 할 수 있지만 실제로는 점이 측면에 가까운 것처럼 보이는 것처럼 거의 문제가되지 않습니다. 뷰어가 이미 내부 또는 외부에 있는지 인식합니다.
여전히 위의 경계 상자가 있습니다. 기억하십니까? 경계 상자 외부의 점을 선택하여 광선의 시작점으로 사용하십시오. 예를 들어 점 (Xmin - e/p.y)
은 다각형 외부에 있어야합니다.
그러나 무엇 e
입니까? 글쎄, e
(실제로 엡실론) 경계 상자에 패딩을 제공합니다 . 내가 말했듯이, 다각형 선에 너무 가까이 시작하면 광선 추적이 실패합니다. 경계 상자가 다각형과 같을 수 있으므로 (다각형이 축 정렬 된 사각형 인 경우 경계 상자가 다각형 자체와 동일합니다!)이를 안전하게하려면 패딩이 필요합니다. 얼마나 크게 선택해야 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)
:
모든면에서 광선을 테스트해야합니다. 광선을 벡터로, 모든면을 벡터로 간주하십시오. 광선은 각면을 정확히 한 번만 또는 전혀 치지 않아야합니다. 같은 쪽을 두 번 칠 수 없습니다. 2D 공간의 두 선은 평행하지 않으면 항상 정확히 한 번 교차합니다.이 경우 절대 교차하지 않습니다. 그러나 벡터의 길이는 제한되어 있기 때문에 두 벡터는 평행하지 않으며 서로 만나기에는 너무 짧기 때문에 여전히 교차하지 않을 수 있습니다.
// 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이 다소 작은 곳 과 같은 것을 대신합니다 .
더 많은 수의 포인트를 테스트 해야하는 경우 다각형 측의 선형 방정식 표준 형태를 메모리에 유지하여 모든 것을 약간 가속화 할 수 있으므로 매번 다시 계산할 필요는 없습니다. 이렇게하면 다각형 면당 3 개의 부동 소수점 값을 메모리에 저장하는 대신 모든 테스트에서 2 개의 부동 소수점 곱셈과 3 개의 부동 소수점 빼기를 저장할 수 있습니다. 일반적인 메모리와 계산 시간의 균형을 유지합니다.
마지막으로, 3D 하드웨어를 사용하여 문제를 해결할 수 있다면 흥미로운 대안이 있습니다. GPU가 모든 작업을 수행하도록하십시오. 화면이 아닌 그림 표면을 만듭니다. 검은 색으로 완전히 채우십시오. 이제 OpenGL 또는 Direct3D를 사용하여 다각형 (또는 점이 다각형 내에 있는지 여부 만 테스트하려는 경우 모든 다각형)을 페인트하고 다각형을 다른 색으로 채 웁니다 색상 (예 : 흰색) 점이 다각형 내에 있는지 확인하려면 그리기 표면에서이 점의 색을 가져옵니다. 이것은 단지 O (1) 메모리 반입입니다.
물론이 방법은 도면 표면이 크지 않아도 사용할 수 있습니다. GPU 메모리에 맞지 않으면이 방법은 CPU에서 수행하는 것보다 느립니다. 거대해야하고 GPU가 최신 셰이더를 지원하는 경우 위에 표시된 레이 캐스팅을 GPU 셰이더로 구현하여 GPU를 계속 사용할 수 있습니다. 더 많은 수의 폴리곤 또는 많은 수의 테스트 포인트가 있다면, 이것은 일부 GPU가 64-256 포인트를 병렬로 테스트 할 수 있다는 점을 고려할 것입니다. 그러나 CPU에서 GPU로 데이터를 다시 전송하는 것은 항상 비용이 많이 들기 때문에 점 또는 다각형이 동적이며 자주 변경되는 몇 가지 간단한 다각형에 대해 몇 개의 점을 테스트하기 만하면 GPU 접근 방식이 거의 지불하지 않습니다. 떨어져서.
다음 코드 조각이 가장 좋은 해결책이라고 생각합니다 ( here ).
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
이므로 서로 같지 않습니다.
여기의 C # 버전입니다 nirg에 의해 주어진 대답 에서 온다, 이 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의 접근 방식을 기반으로 한 M. Katz의 답변에 대한 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]]
다음은 아이디어를 구현하는 파이썬 코드입니다.
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
에릭 헤인즈 기사 bobobobo 인용은 정말 우수합니다. 알고리즘의 성능을 비교하는 테이블이 특히 흥미 롭습니다. 각도 합산 방법은 다른 방법에 비해 실제로 나쁩니다. 또한 룩업 그리드를 사용하여 폴리곤을 "in"및 "out"섹터로 세분화하는 것과 같은 최적화는>면이 1000 개 이상인 폴리곤에서도 테스트를 매우 빠르게 수행 할 수 있습니다.
어쨌든, 초창기이지만 투표는 "교차"방법으로갑니다. 이것은 Mecki가 생각하는 것과 거의 같습니다. 그러나 나는 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가 편집 한 솔루션과 정말 같습니다. 방금 자바 스크립트에 친숙하고 사용하기에 조금 더 읽기 쉽습니다.
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의 링크는 적절한 수의 합리적인 알고리즘을 제공했습니다. 나는 지구 과학 문제를 연구하고 있었기 때문에 위도 / 경도로 작동하는 솔루션이 필요했고, 그것은 손의 특이한 문제가 있습니다-작은 영역이나 넓은 영역의 내부 영역입니까? 답은 정점의 "방향"이 중요하다는 것입니다. 왼손잡이 또는 오른 손잡이이며이 방법으로 어느 한 영역을 주어진 다각형의 "내부"로 표시 할 수 있습니다. 따라서 필자의 작업은 해당 페이지에 열거 된 솔루션 3을 사용했습니다.
또한 제 작업은 "온라인"테스트를 위해 별도의 기능을 사용했습니다.
... 누군가가 물었을 때 : 정점 수가 몇 개를 넘어 서면 경계 상자 테스트가 가장 좋다는 것을 알았습니다. 필요한 경우 더 긴 테스트를 수행하기 전에 매우 빠른 테스트를 수행하십시오 ... 경계 상자는 단순히 가장 큰 x, 가장 작은 x, 가장 큰 y 및 가장 작은 y를 모아서 상자의 네 점을 만들기 위해 ...
다음과 같은 사람들을위한 또 다른 팁 : 우리는 그리드 공간에서 더 정교하고 "조도 희미하게"계산 한 모든 것을 평면의 양의 지점에서 모두 수행 한 다음 "실제"경도 / 위도로 다시 투영하여 가능한 오류를 피했습니다. 경도의 선 180을 넘었을 때와 극지방을 다룰 때 잘 했어!
David Segond의 대답은 표준 일반 답변과 거의 비슷하며 Richard T 's가 가장 일반적인 최적화이지만 다른 것들도 있습니다. 다른 강력한 최적화는 덜 일반적인 솔루션을 기반으로합니다. 예를 들어, 점이 많은 동일한 다각형을 검사하려는 경우 매우 빠른 TIN 검색 알고리즘이 많으므로 다각형을 삼각 측량하면 속도가 크게 향상 될 수 있습니다. 다른 하나는 다각형과 점이 저해상도의 제한된 평면에있는 경우입니다 (예 : 화면 표시). 다각형을 지정된 색상의 메모리 매핑 된 표시 버퍼에 칠하고 주어진 픽셀의 색상을 검사하여 거짓말이 있는지 확인할 수 있습니다 다각형에서.
많은 최적화와 마찬가지로 이들은 일반적인 경우가 아닌 특정 사례를 기반으로하며 단일 사용이 아닌 상각 시간을 기반으로 수익을 창출합니다.
이 분야에서 일하면서 Joeseph O'Rourkes 'Computation Geometry in C'ISBN 0-521-44034-3이 큰 도움이된다는 것을 알았습니다.
사소한 해결책은 다각형을 삼각형으로 나누고 여기에 설명 된대로 삼각형을 치는 것입니다.
다각형이 CONVEX 인 경우 더 나은 접근 방법이있을 수 있습니다. 다각형을 무한 선 모음으로 봅니다. 공간을 두 개로 나누는 각 줄. 모든 점에서 선의 한쪽 또는 다른쪽에 있는지 쉽게 말할 수 있습니다. 점이 모든 선의 같은쪽에 있으면 다각형 안에 있습니다.
나는 이것이 오래되었다는 것을 알고 있지만 여기에 누군가 관심이있는 경우 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;
}
포인트 테스트를위한 샘플 방법이 포함 된 nirg 's answer의 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()
문제의 귀납적 정의보다 더 아름다운 것은 없습니다. 완전성을 위해 여기에 광선 주조 뒤에있는 생각을 분명히 할 수있는 프롤로그 버전이 있습니다. .
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 (선 (A, B))가 주어진 선의 방정식은 다음과 같습니다.
(YB-YA)
Y - YA = ------- * (X - XA)
(XB-YB)
선의 회전 방향은 경계의 경우 시계 방향으로, 구멍의 경우 시계 반대 방향으로 설정해야합니다. 우리는 점 (X, Y), 즉 테스트 된 점이 우리 선의 왼쪽 반 평면에 있는지 여부를 확인하려고합니다 (맛의 문제, 오른쪽 일 수도 있고 경계의 방향 일 수도 있습니다) 이 경우 선을 변경해야합니다), 이것은 점에서 오른쪽 (또는 왼쪽)으로 광선을 투사하고 선과의 교차점을 확인하는 것입니다. 우리는 광선을 수평 방향으로 투사하기로 선택했습니다 (다시 맛의 문제이며 비슷한 제한으로 수직으로 할 수도 있습니다).
(XB-XA)
X < ------- * (Y - YA) + XA
(YB-YA)
이제 점이 전체 평면이 아닌 선 세그먼트의 왼쪽 (또는 오른쪽)에 있는지 알아야하므로 검색을이 세그먼트로만 제한해야하지만 세그먼트 내부에 있기 때문에 쉬워집니다. 수직 축에서 선의 한 점만 Y보다 높을 수 있습니다. 이것이 더 강력한 제한이므로 먼저 확인해야하므로 먼저이 요구 사항을 충족하는 행만 취한 다음 위치를 확인하십시오. Jordan Curve 정리에 의해 다각형으로 투영 된 광선은 짝수 개의 선에서 교차해야합니다. 그래서 우리는 광선을 오른쪽으로 던질 때마다 선을 교차 할 때마다 상태를 토글합니다. 그러나 우리의 구현에서 우리는 주어진 제한을 충족시키는 솔루션 백의 길이를 확인하고 그에 대한 내성을 결정해야합니다. 다각형의 각 선에 대해이 작업을 수행해야합니다.
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;
}
자바 버전 :
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 버전 :
참고 : 다각형이지도 내의 영역 인 경우 위도 / 경도가 X / Y (위도 = Y, 경도 = X)와 반대로 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
nirg의 C ++ 코드를 Python으로 구현했습니다 .
입력
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가 이것을 포함하여 지리 쿼리를 훌륭하게 지원합니다.
당신이 찾고있는 것은 :
db.neighborhoods.findOne ({geometry : {$ geoIntersects : {$ geometry : {유형 : "Point", 좌표 : [ "longitude", "latitude"]}}}})
Neighborhoods
표준 GeoJson 형식으로 하나 이상의 다각형을 저장하는 모음입니다. 쿼리가 null을 반환하면 교차되지 않습니다.
https://docs.mongodb.com/manual/tutorial/geospatial-tutorial/에 잘 설명되어 있습니다.
330 개의 불규칙 다각형 그리드로 분류 된 6,000 개 이상의 포인트에 대한 성능은 전혀 최적화되지 않았으며 각각의 다각형으로 문서를 업데이트하는 시간을 포함하여 1 분 미만이었습니다.
광선 캐스팅을 사용하지 않는 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
이지만,이 스레드를 읽는 개발자에게는 관심이있을 수 있습니다 (제 테스트에서는 ~ 23x 느리고 라인 교차 방법 사용).
레이 캐스팅 알고리즘 에서 다음과 같은 특수한 경우를 처리하려면 다음을 수행하십시오 .
점이 복잡한 다각형 안에 있는지 확인 합니다. 이 기사는 이러한 문제를 해결하는 쉬운 방법을 제공하므로 위의 경우 특별한 처리가 필요하지 않습니다.
자바 스크립트 라이브러리를 찾고 있다면 Polygon 클래스에 대한 자바 스크립트 google maps v3 확장 기능이 포인트가 그 안에 있는지 여부를 감지합니다.
var polygon = new google.maps.Polygon([], "#000000", 1, 1, "#336699", 0.3);
var isWithinPolygon = polygon.containsLatLng(40, -90);
사용할 때 qt(Qt 4.3 이상) QPolygon의 함수 containsPoint를 사용할 수 있습니다
대답은 단순하거나 복잡한 다각형이 있는지 여부에 따라 다릅니다. 단순 다각형에는 선분 교차점이 없어야합니다. 따라서 구멍이 생길 수 있지만 선은 서로 교차 할 수 없습니다. 복잡한 영역에는 선 교차점이있을 수 있으므로 겹치는 영역 또는 단일 포인트만으로 서로 닿는 영역이있을 수 있습니다.
단순한 다각형의 경우 가장 좋은 알고리즘은 Ray casting (Crossing number) 알고리즘입니다. 복잡한 다각형의 경우이 알고리즘은 겹치는 영역 안에있는 점을 감지하지 않습니다. 복잡한 다각형의 경우 권선 수 알고리즘을 사용해야합니다.
다음은 두 알고리즘의 C 구현에 대한 훌륭한 기사입니다. 나는 그들을 시도하고 그들은 잘 작동합니다.
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 answer의 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
}