ฉันกำลังพยายามสร้างจุด 2D ที่รวดเร็วภายในอัลกอริทึมรูปหลายเหลี่ยมเพื่อใช้ในการทดสอบการตี (เช่นPolygon.contains(p:Point)
) ข้อเสนอแนะสำหรับเทคนิคที่มีประสิทธิภาพจะได้รับการชื่นชม
ฉันกำลังพยายามสร้างจุด 2D ที่รวดเร็วภายในอัลกอริทึมรูปหลายเหลี่ยมเพื่อใช้ในการทดสอบการตี (เช่นPolygon.contains(p:Point)
) ข้อเสนอแนะสำหรับเทคนิคที่มีประสิทธิภาพจะได้รับการชื่นชม
คำตอบ:
สำหรับกราฟิกฉันไม่ชอบจำนวนเต็ม ระบบจำนวนมากใช้จำนวนเต็มสำหรับการวาด UI (พิกเซลเป็น ints ทั้งหมด) แต่ macOS เช่นใช้การลอยสำหรับทุกสิ่ง macOS รู้จุดเท่านั้นและจุดหนึ่งสามารถแปลเป็นหนึ่งพิกเซลได้ แต่ขึ้นอยู่กับความละเอียดของจอภาพมันอาจแปลเป็นอย่างอื่น บนหน้าจอเรตินาครึ่งจุด (0.5 / 0.5) คือพิกเซล ถึงกระนั้นฉันไม่เคยสังเกตว่า macOS UIs ช้ากว่า UIs อื่น ๆ อย่างมีนัยสำคัญ หลังจาก 3D APIs ทั้งหมด (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)
:
คุณต้องทดสอบรังสีกับทุกด้าน พิจารณารังสีให้เป็นเวกเตอร์และทุก ๆ ด้านเป็นเวกเตอร์ รังสีจะกระทบแต่ละด้านอย่างแน่นอนหรือไม่เคยเลย มันไม่สามารถชนด้านเดียวกันสองครั้ง เส้นสองเส้นในพื้นที่ 2 มิติจะตัดกันหนึ่งครั้งเสมอเว้นแต่จะขนานกันซึ่งในกรณีนี้พวกเขาไม่เคยตัดกัน อย่างไรก็ตามเนื่องจากเวกเตอร์มีความยาว จำกัด เวกเตอร์สองตัวอาจไม่ขนานกันและยังไม่เคยตัดกันเพราะมันสั้นเกินไปที่จะเจอกัน
// 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/v1y1
และv1x2/v1y2
) และเวกเตอร์ 2 ( v2x1/v2y1
และv2x2/v2y2
) คุณมีเวกเตอร์ 2 ตัว, 4 คะแนน, 8 พิกัด 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 นั้นต้องการการระบุแหล่งที่มา
ตรวจสอบกล่องขอบเขตได้รับการเพิ่มที่ด้านบน อย่างไรก็ตามเจมส์บราวน์ชี้ให้เห็นว่ารหัสหลักเกือบจะเร็วเท่ากับกล่องตรวจสอบตัวเองดังนั้นการตรวจสอบกล่องขอบเขตสามารถชะลอการดำเนินงานโดยรวมได้ในกรณีที่จุดตรวจสอบส่วนใหญ่อยู่ในกล่องเชื่อมโยง . ดังนั้นคุณสามารถออกจากกล่องของขอบเขตหรือตรวจสอบทางเลือกที่จะ precompute กล่องขอบเขตของรูปหลายเหลี่ยมของคุณถ้าพวกเขาไม่เปลี่ยนรูปร่างบ่อยเกินไป
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;
}
นี่คือตัวแปร JavaScript ของคำตอบโดย M. Katz ตามแนวทางของ Nirg:
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 เป็นที่ยอดเยี่ยมจริงๆ น่าสนใจอย่างยิ่งคือตารางเปรียบเทียบประสิทธิภาพของอัลกอริทึม วิธีการรวมมุมไม่ดีจริง ๆ เมื่อเปรียบเทียบกับวิธีอื่น สิ่งที่น่าสนใจก็คือการเพิ่มประสิทธิภาพเช่นการใช้ตารางการค้นหาเพื่อแบ่งรูปหลายเหลี่ยมออกเป็นส่วน "ใน" และ "ออก" สามารถทำให้การทดสอบเร็วขึ้นอย่างไม่น่าเชื่อแม้ในรูปหลายเหลี่ยมที่มีมากกว่า 1,000 ด้าน
อย่างไรก็ตามมันเป็นวันแรก แต่การลงคะแนนของฉันไปที่วิธี "ข้าม" ซึ่งเป็นสิ่งที่สวยมาก 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
คำตอบเวอร์ชั่นที่รวดเร็วโดย nirg :
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 สำหรับงานทางภูมิศาสตร์ ...
ฉันต้องการชี้ให้เห็นว่าเราไม่เคยได้รับข้อมูลเชิงลึกเกี่ยวกับความถนัดซ้ายและซ้ายมือ (ยังแสดงให้เห็นว่าเป็นปัญหา "ภายใน" vs "นอก" ...
UPDATE
ลิงก์ของ BKB ให้อัลกอริธึมที่เหมาะสมจำนวนมาก ฉันทำงานเกี่ยวกับปัญหา Earth Science และดังนั้นจึงจำเป็นต้องมีวิธีแก้ปัญหาที่ทำงานในละติจูด / ลองจิจูดและมันมีปัญหาที่แปลกประหลาดของการถนัดซ้าย - เป็นพื้นที่ภายในพื้นที่ขนาดเล็กหรือพื้นที่ขนาดใหญ่กว่าหรือไม่? คำตอบก็คือ "ทิศทาง" ของจุดยอดมีความสำคัญ - เป็นทางซ้ายหรือทางขวาและด้วยวิธีนี้คุณสามารถระบุพื้นที่ว่า "ภายใน" รูปหลายเหลี่ยมที่กำหนด ดังนั้นงานของฉันจึงใช้วิธีแก้ปัญหาสามข้อที่แจกแจงในหน้านั้น
นอกจากนี้งานของฉันยังใช้ฟังก์ชันแยกต่างหากสำหรับการทดสอบ "ออนไลน์"
... เนื่องจากมีคนถามว่า: เราคิดว่าการทดสอบกล่อง bounding นั้นดีที่สุดเมื่อจำนวนจุดยอดเกินกว่าจำนวน - ทำแบบทดสอบอย่างรวดเร็วมากก่อนที่จะทำการทดสอบอีกต่อไปหากจำเป็น ... กล่องขอบเขตถูกสร้างขึ้นโดยเพียงแค่การ ที่ใหญ่ที่สุด x ที่เล็กที่สุด x ที่เล็กที่สุด y และ y ที่เล็กที่สุดและรวมเข้าด้วยกันเพื่อทำสี่จุดของกล่อง ...
เคล็ดลับอีกข้อสำหรับสิ่งต่อไปนี้: เราทำการคำนวณที่ซับซ้อนและ "แสงสลัว" ในพื้นที่กริดทั้งหมดในจุดบวกบนระนาบแล้วฉายกลับไปที่ลองจิจูด / ละติจูด "ของจริง" เพื่อหลีกเลี่ยงข้อผิดพลาดที่เป็นไปได้ของ ล้อมรอบเมื่อหนึ่งข้าม 180 เส้นลองจิจูดและเมื่อจัดการพื้นที่ขั้วโลก ใช้งานได้ดีมาก!
คำตอบของ David Segond นั้นเป็นคำตอบทั่วไปที่ค่อนข้างธรรมดาและ Richard T's นั้นเป็นการเพิ่มประสิทธิภาพที่พบได้บ่อยที่สุด การเพิ่มประสิทธิภาพที่แข็งแกร่งอื่น ๆ ขึ้นอยู่กับการแก้ปัญหาทั่วไปน้อยลง ตัวอย่างเช่นหากคุณกำลังตรวจสอบรูปหลายเหลี่ยมเดียวกันกับจำนวนมากการหารูปหลายเหลี่ยมนั้นสามารถเร่งความเร็วของสิ่งต่างๆได้อย่างมหาศาลเนื่องจากมีอัลกอริธึมการค้นหา TIN จำนวนมากที่รวดเร็ว อีกอย่างคือถ้ารูปหลายเหลี่ยมและจุดอยู่บนระนาบ จำกัด ที่ความละเอียดต่ำพูดถึงหน้าจอคุณสามารถวาดรูปหลายเหลี่ยมบนบัฟเฟอร์หน่วยความจำที่แมปหน่วยความจำในสีที่กำหนดและตรวจสอบสีของพิกเซลที่กำหนดเพื่อดูว่ามันอยู่ ในรูปหลายเหลี่ยม
เช่นเดียวกับการเพิ่มประสิทธิภาพหลายอย่างสิ่งเหล่านี้มีพื้นฐานมาจากเฉพาะมากกว่ากรณีทั่วไปและให้ผลประโยชน์ตามเวลาที่ตัดจำหน่ายมากกว่าการใช้เพียงครั้งเดียว
การทำงานในสาขานี้ฉันพบว่าเรขาคณิตการคำนวณ Joeseph O'Rourkes ใน C 'ไอ 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()
ไม่มีอะไรจะยิ่งใหญ่ไปกว่าการนิยามอุปนัยของปัญหา เพื่อความสมบูรณ์ที่นี่คุณมีเวอร์ชันในภาษาอารัมภบทซึ่งอาจอธิบายความคิดเบื้องหลังการฉายรังสี :
อ้างอิงจากการจำลองอัลกอริธึมความเรียบง่ายใน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;
}
เวอร์ชั่น 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;
}
}
. สุทธิพอร์ต:
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
ฉันใช้ Python เพื่อนำไปใช้กับรหัส c ++ ของnirg :
ปัจจัยการผลิต
bounding_box_positions:คะแนนผู้สมัครที่จะกรอง (ในการใช้งานของฉันสร้างขึ้นจากกล่องขอบเขต
(ปัจจัยการผลิตคือรายการของ tuples ในรูปแบบ: [(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.ne Neighborhoods.findOne ({เรขาคณิต: {$ geoIntersects: {$ เรขาคณิต: {ประเภท: "จุด", พิกัด: ["ลองจิจูด", "ละติจูด"]}}}})
Neighborhoods
คือคอลเล็กชันที่เก็บรูปหลายเหลี่ยมหนึ่งรูปแบบขึ้นไปในรูปแบบ GeoJson มาตรฐาน ถ้าแบบสอบถามส่งกลับค่า null มันจะไม่ intersected มิฉะนั้นเป็น
มีเอกสารที่ดีมากที่นี่: https://docs.mongodb.com/manual/tutorial/geospatial-tutorial/
ประสิทธิภาพมากกว่า 6,000 คะแนนที่จัดอยู่ในตารางรูปหลายเหลี่ยมผิดปกติ 330 ตัวนั้นน้อยกว่าหนึ่งนาทีโดยไม่มีการปรับให้เหมาะสมเลยและรวมถึงเวลาในการอัปเดตเอกสารด้วยรูปหลายเหลี่ยมที่เกี่ยวข้อง
เป็นจุดในการทดสอบรูปหลายเหลี่ยมใน 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
แต่อาจเป็นที่สนใจของนักพัฒนาที่อ่านหัวข้อนี้
สำหรับการตรวจจับการชนรูปหลายเหลี่ยมเราจำเป็นต้องทดสอบสองสิ่ง:
ในการจัดการกับกรณีพิเศษต่อไปนี้ในอัลกอริทึมการคัดเลือกเรย์ :
ตรวจสอบการตัดสินว่าจุดหนึ่งอยู่ภายในรูปหลายเหลี่ยมที่ซับซ้อนหรือไม่ บทความนี้ให้วิธีง่ายๆในการแก้ไขปัญหาดังกล่าวดังนั้นจึงไม่จำเป็นต้องได้รับการดูแลเป็นพิเศษสำหรับกรณีข้างต้น
คุณสามารถทำได้โดยตรวจสอบว่าพื้นที่ที่เกิดขึ้นจากการเชื่อมต่อจุดที่ต้องการไปยังจุดยอดของรูปหลายเหลี่ยมของคุณตรงกับพื้นที่ของรูปหลายเหลี่ยมนั้นหรือไม่
หรือคุณสามารถตรวจสอบว่าผลรวมของมุมด้านในจากจุดของคุณไปยังจุดยอดรูปหลายเหลี่ยมสองคู่ต่อเนื่องกันไปยังจุดตรวจสอบของคุณถึงจำนวน 360 แต่ฉันมีความรู้สึกว่าตัวเลือกแรกเร็วกว่าเพราะไม่เกี่ยวข้องกับการหารหรือการคำนวณ ของฟังก์ชันผกผันของตรีโกณมิติ
ฉันไม่รู้ว่าจะเกิดอะไรขึ้นถ้ารูปหลายเหลี่ยมของคุณมีรูอยู่ข้างใน แต่ดูเหมือนว่าสำหรับแนวคิดหลักนั้นสามารถปรับให้เข้ากับสถานการณ์นี้ได้
คุณสามารถโพสต์คำถามในชุมชนคณิตศาสตร์ได้เช่นกัน ฉันเดิมพันพวกเขามีหนึ่งล้านวิธีในการทำเช่นนั้น
หากคุณกำลังมองหาไลบรารีจาวาสคริปต์มี javascript google maps v3 extension สำหรับคลาส Polygon เพื่อตรวจสอบว่ามีจุดอยู่ภายในหรือไม่
var polygon = new google.maps.Polygon([], "#000000", 1, 1, "#336699", 0.3);
var isWithinPolygon = polygon.containsLatLng(40, -90);
เมื่อใช้งาน QT(Qt 4.3 ขึ้นไป) สามารถใช้ฟังก์ชั่น QPolygon containsPoint
คำตอบนั้นขึ้นอยู่กับว่าคุณมีรูปหลายเหลี่ยมที่เรียบง่ายหรือซับซ้อน รูปหลายเหลี่ยมอย่างง่ายจะต้องไม่มีการแยกส่วนของเส้นตรงใด ๆ ดังนั้นพวกเขาจึงสามารถมีรูได้ แต่เส้นไม่สามารถข้ามกันได้ ภูมิภาคที่ซับซ้อนสามารถมีจุดตัดของเส้น - เพื่อให้สามารถมีพื้นที่ที่ทับซ้อนกันหรือภูมิภาคที่สัมผัสกันเพียงแค่จุดเดียว
สำหรับรูปหลายเหลี่ยมอย่างง่ายอัลกอริธึมที่ดีที่สุดคืออัลกอริธึมการเรย์ (หมายเลขข้าม) สำหรับรูปหลายเหลี่ยมที่ซับซ้อนอัลกอริทึมนี้จะไม่ตรวจจับจุดที่อยู่ภายในขอบเขตที่ทับซ้อนกัน ดังนั้นสำหรับรูปหลายเหลี่ยมที่ซับซ้อนคุณต้องใช้อัลกอริทึมหมายเลข Winding
นี่คือบทความที่ยอดเยี่ยมด้วยการใช้ 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)
}
นี่คือรุ่น golang ของคำตอบ @nirg (แรงบันดาลใจจากรหัส C # โดย @@ m-katz)
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
}