Results 1 to 10 of 43

Thread: Test Whether A Point Is In A Polygon Or Not

Hybrid View

Previous Post Previous Post   Next Post Next Post
  1. #1
    Junior Member
    Join Date
    Sep 2016
    Posts
    2
    Rep Power
    0
    Quote Originally Posted by Rick Rothstein View Post
    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
    Hi Rick,

    first thanks for sharing your code, it has been very useful to me. Similar to theCloud, i work with aircraft center of gravity and needed a function similar to yours. However I had some problems:
    1- when a point was on a vertical boundarie
    2- when a point was on an horizontal boundarie
    3- when comparing two Double variable (b = Ycoord)...so I replace Double by Single and it works for now...I might need to use Double in the future, we'll see how it goes

    maybe this will help somebody, if you see any problems with my changes let me know

    thanks

    Code:
    Function PtInPoly(Xcoord As Double, Ycoord As Double, Polygon As Variant) As Variant
    Dim X As Long, NumSidesCrossed As Long, m As Single, b As Single, Poly As Variant '*********changed m and b variable type from Double to Single
    Dim btest As Single
    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
    Continu:
        If Poly(X, 1) >= Xcoord Xor Poly(X + 1, 1) >= Xcoord Then                '************changed > for >= to accomodate a point located on a vertical boundarie limit
            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 = 0 And b = Ycoord Then                                         '********* added this "if statement" to check if point is on horizontal boundaries limit
                If X < UBound(Poly) - 1 Then
                    X = X + 1
                    GoTo Continu:
                Else
                    Exit For
                End If
            Else
                If m * Xcoord + b > Ycoord Then NumSidesCrossed = NumSidesCrossed + 1
            End If
        End If
    Next X
    PtInPoly = CBool(NumSidesCrossed Mod 2)
    End Function

  2. #2
    Forum Guru Rick Rothstein's Avatar
    Join Date
    Feb 2012
    Posts
    659
    Rep Power
    13
    Quote Originally Posted by hugohonda View Post
    Hi Rick,

    first thanks for sharing your code, it has been very useful to me. Similar to theCloud, i work with aircraft center of gravity and needed a function similar to yours. However I had some problems:
    1- when a point was on a vertical boundarie
    2- when a point was on an horizontal boundarie
    3- when comparing two Double variable (b = Ycoord)...so I replace Double by Single and it works for now...I might need to use Double in the future, we'll see how it goes
    For point numbers 1 and 2, I would refer you to my Note #3. You should not be overly fixated on points lying on the boundary of your polygon. Too many things can affect whether a point actually lies on the boundary. VB's floating point math is not infinite in scale, so the numbers have a limited precision (15 decimal places) and each number in a calculation theoretically degrades the accuracy of the calculated number to the point that you can never be sure if the calculated coordinate really lies on the boundary or not. For example, let's assume point is actually on the boundary... what is the significance if that is considered inside or outside the polygon on a practical level. Given that a theoretical boundary line for a polygon has zero thickness, then if the point was a millionth, trillions, zillions or whatever further away, it would be considered outside the polygon... similarly, if the point were as millionth, trillions, zillions or whatever closer in, it would be considered inside the polygon. At a practical level, either of those points (the further out one or the closer in one) could be considered the same point (what is the practical significance of two points that are a trillions of a inch, millimeter or whatever units of measurement?) So whether a real boundary point calculates inside or outside of the polygon should have absolutely no real world significance at all.

    As for changing Doubles to Singles... I would not do that as it lessens the accuracy of all calculations that take place in the routine increasing the chance that a point in the vicinity of the boundary, but not truly "next to" or "on" it might calculate to the wrong side. Rather than making Doubles into Singles, leave them as Doubles and when comparing them, round each of the values down to the same number of decimal place (you choose the number of decimal places that is significant to you) and then compare those rounded down values instead.

  3. #3
    Junior Member
    Join Date
    Sep 2016
    Posts
    2
    Rep Power
    0
    Quote Originally Posted by Rick Rothstein View Post
    For point numbers 1 and 2, I would refer you to my Note #3. You should not be overly fixated on points lying on the boundary of your polygon. Too many things can affect whether a point actually lies on the boundary. VB's floating point math is not infinite in scale, so the numbers have a limited precision (15 decimal places) and each number in a calculation theoretically degrades the accuracy of the calculated number to the point that you can never be sure if the calculated coordinate really lies on the boundary or not. For example, let's assume point is actually on the boundary... what is the significance if that is considered inside or outside the polygon on a practical level. Given that a theoretical boundary line for a polygon has zero thickness, then if the point was a millionth, trillions, zillions or whatever further away, it would be considered outside the polygon... similarly, if the point were as millionth, trillions, zillions or whatever closer in, it would be considered inside the polygon. At a practical level, either of those points (the further out one or the closer in one) could be considered the same point (what is the practical significance of two points that are a trillions of a inch, millimeter or whatever units of measurement?) So whether a real boundary point calculates inside or outside of the polygon should have absolutely no real world significance at all.

    As for changing Doubles to Singles... I would not do that as it lessens the accuracy of all calculations that take place in the routine increasing the chance that a point in the vicinity of the boundary, but not truly "next to" or "on" it might calculate to the wrong side. Rather than making Doubles into Singles, leave them as Doubles and when comparing them, round each of the values down to the same number of decimal place (you choose the number of decimal places that is significant to you) and then compare those rounded down values instead.
    Hi Rick,

    first, thanks for your quick reply. I like your idea with using the round function with Doubles...I implemented it and it works great now.

    As for my point numbers 1 and 2, I understand everything you mentioned and you are absolutely right. The thing is, I'm plotting different weight (y axis) and center of gravity (x axis) of an aircraft into a "Flight Envelope" and all point needs to be inside the envelope otherwise it isn't safe to fly. In order to find if a specific aircraft configuration (weight and CG) is dangerous to fly, I had to modify the code as mentioned before.

    I know that the specific aircraft configuration touching the boundary, theoretically, won't be exactly on the line because like you mentioned, "a line has no thickness" but it will give me, I think, a very accurate aircraft configuration at which it is dangerous to fly.

    I don't know if i'm clear but this is why i'm concerned about boundary.

    I tested my code with many case scenarios and all of them give me a good result so far

    thanks again!

  4. #4
    Forum Guru Rick Rothstein's Avatar
    Join Date
    Feb 2012
    Posts
    659
    Rep Power
    13
    Quote Originally Posted by hugohonda View Post
    As for my point numbers 1 and 2, I understand everything you mentioned and you are absolutely right. The thing is, I'm plotting different weight (y axis) and center of gravity (x axis) of an aircraft into a "Flight Envelope" and all point needs to be inside the envelope otherwise it isn't safe to fly. In order to find if a specific aircraft configuration (weight and CG) is dangerous to fly, I had to modify the code as mentioned before.
    You are talking about aircraft and you are worried about what amounts to infinitesimally small fractions of an inch? I think you are being overly trusting in the supposed accuracy of numerical calculations on a computer. All floating point calculations on a computer are approximations Unless your numbers have power of 2 factors, the conversion from the decimal system we work in to the binary system computers use will involve rounding errors out at the 15th or 16th decimal place which will propagate with each calculation you do with those numbers... that is why the "which side of the line with no thickness" problem is an issue at all... small changes out in the 15th or 16th decimal place are more than enough to move a calculated point onto either side of a line with no thickness. Just so you know, what I discussed about points near the border applies to all borders, not just horizontal or vertical ones. If you really want to be "safe" (your word, not mine), you should shrink your polygon by some percentage that you are comfortable with so that it does not matter which side of a boundary line a boundary point falls on.

Similar Threads

  1. This is a test Test Let it be
    By Admin in forum Test Area
    Replies: 6
    Last Post: 05-30-2014, 09:44 AM
  2. This is a test of Signature Block Variable Dim
    By alansidman in forum Test Area
    Replies: 0
    Last Post: 10-17-2013, 07:42 PM
  3. Test
    By Excel Fox in forum Den Of The Fox
    Replies: 0
    Last Post: 07-31-2013, 08:15 AM
  4. Replies: 4
    Last Post: 06-10-2013, 01:27 PM
  5. Test
    By Excel Fox in forum Word Help
    Replies: 0
    Last Post: 07-05-2011, 01:51 AM

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •