This function is not one that many will find a use for, but if you ever need its functionality, then here it is. The function can be called from other VB code or used as a UDF (user defined function) directly on a worksheet. What it does is tell you whether a point is located inside a polygon (simple or complex, convex or concave) or not. That's it... if you should ever need such a function, this is the code for it...
Code:
Public Function PtInPoly(Xcoord As Double, Ycoord As Double, Polygon As Variant) As Variant
Dim x As Long, NumSidesCrossed As Long, m As Double, b As Double, Poly As Variant
Poly = Polygon
If Not (Poly(LBound(Poly), 1) = Poly(UBound(Poly), 1) And _
Poly(LBound(Poly), 2) = Poly(UBound(Poly), 2)) Then
If TypeOf Application.Caller Is Range Then
PtInPoly = "#UnclosedPolygon!"
Else
Err.Raise 998, , "Polygon Does Not Close!"
End If
Exit Function
ElseIf UBound(Poly, 2) - LBound(Poly, 2) <> 1 Then
If TypeOf Application.Caller Is Range Then
PtInPoly = "#WrongNumberOfCoordinates!"
Else
Err.Raise 999, , "Array Has Wrong Number Of Coordinates!"
End If
Exit Function
End If
For x = LBound(Poly) To UBound(Poly) - 1
If Poly(x, 1) > Xcoord Xor Poly(x + 1, 1) > Xcoord Then
m = (Poly(x + 1, 2) - Poly(x, 2)) / (Poly(x + 1, 1) - Poly(x, 1))
b = (Poly(x, 2) * Poly(x + 1, 1) - Poly(x, 1) * Poly(x + 1, 2)) / (Poly(x + 1, 1) - Poly(x, 1))
If m * Xcoord + b > Ycoord Then NumSidesCrossed = NumSidesCrossed + 1
End If
Next
PtInPoly = CBool(NumSidesCrossed Mod 2)
End Function
The theory behind the function is simplicity itself... start at the point being tested and project a line from that point outward in any direction (I chose straight up as that made some of the math easier) and count how many polygon sides it crosses... if the number of sides is odd, the point lies inside the polygon and if the number of sides is even, the point lies outside of the polygon. The function will return either True, False (for inside/outside respectively) or an error message (see below). The first argument is the X-Coordinate and the second argument is the Y-Coordinate of the point you want to test for being inside the polygon or not. The third argument is either a two-dimensional array of numbers (when called from another VB code procedure) or a range of numbers consisting of two columns and as many rows as needed (when called from a worksheet). The numbers describe the nodes composing the polygon.
NOTE #1: The polygon must be closed, meaning the first listed point and the last listed point must be the same. If they are not the same, the function will raise "Error #998 - Polygon Does Not Close!" if the function was called from other VB code or it will return #UnclosedPolygon! if called from the worksheet. Normally, if called from a worksheet, you would probably be using the function in a formula something like this...
=IF(PtInPoly(B9,C9,E18:F37),"In Polygon","Out Polygon")
In that case, the formula will return a #VALUE! error, not the #UnclosedPolygon! error, because the returned value to the IF function is not a Boolean; however, if you select the "PtInPoly(B9,C9,E18:F37)" part of the function in the Formula Bar and press F9, it will show you the returned value from the PtInPoly function as being #UnclosedPolygon!.
NOTE #2: The range or array specified for the third argument must be two-dimensional. If it is not, then the function will raise "Error #999 - Array Has Wrong Number Of Coordinates!" if the function was called from other VB code or it will return #WrongNumberOfCoordinates! if called from the worksheet. Error reporting when called from the worksheet will be the same as described in NOTE #1.
NOTE #3: Points extremely close to, or theoretically lying on, the polygon borders may or may not report back correctly... the vagrancies of floating point math, coupled with the limitations that the "significant digits limit" in VBA imposes, can rear its ugly head in those circumstances producing values that can calculate to either side of the polygon border being tested (remember, a mathematical line has
no thickness, so it does not take too much of a difference in the significant digits to "move" a calculated point's position to one side or the other of such a line).
NOTE #4: While I think error checking is a good thing, the setup for this function is rather straightforward and, with the possible exception of the requirement for the first and last point needing to be the same, easy enough for the programmer to maintain control over during coding. If you feel confident in your ability to meet the needs of NOTE #1 and NOTE #2 without having the code "looking over your shoulder", then the function can be simplified down to this compact code...
Code:
Public Function PtInPoly(Xcoord As Double, Ycoord As Double, Polygon As Variant) As Variant
Dim x As Long, NumSidesCrossed As Long, m As Double, b As Double, Poly As Variant
Poly = Polygon
For x = LBound(Poly) To UBound(Poly) - 1
If Poly(x, 1) > Xcoord Xor Poly(x + 1, 1) > Xcoord Then
m = (Poly(x + 1, 2) - Poly(x, 2)) / (Poly(x + 1, 1) - Poly(x, 1))
b = (Poly(x, 2) * Poly(x + 1, 1) - Poly(x, 1) * Poly(x + 1, 2)) / (Poly(x + 1, 1) - Poly(x, 1))
If m * Xcoord + b > Ycoord Then NumSidesCrossed = NumSidesCrossed + 1
End If
Next
PtInPoly = CBool(NumSidesCrossed Mod 2)
End Function
Bookmarks