How can I determine whether a 2D Point is within a Polygon?

前端 未结 30 2149
醉梦人生
醉梦人生 2020-11-21 05:06

I\'m trying to create a fast 2D point inside polygon algorithm, for use in hit-testing (e.g. Polygon.contains(p:Point)). Suggestions for effective tech

相关标签:
30条回答
  • 2020-11-21 06:01

    This is a presumably slightly less optimized version of the C code from here which was sourced from this page.

    My C++ version uses a std::vector<std::pair<double, double>> and two doubles as an x and y. The logic should be exactly the same as the original C code, but I find mine easier to read. I can't speak for the performance.

    bool point_in_poly(std::vector<std::pair<double, double>>& verts, double point_x, double point_y)
    {
        bool in_poly = false;
        auto num_verts = verts.size();
        for (int i = 0, j = num_verts - 1; i < num_verts; j = i++) {
            double x1 = verts[i].first;
            double y1 = verts[i].second;
            double x2 = verts[j].first;
            double y2 = verts[j].second;
    
            if (((y1 > point_y) != (y2 > point_y)) &&
                (point_x < (x2 - x1) * (point_y - y1) / (y2 - y1) + x1))
                in_poly = !in_poly;
        }
        return in_poly;
    }
    

    The original C code is

    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;
    }
    
    0 讨论(0)
  • 2020-11-21 06:02

    I did some work on this back when I was a researcher under Michael Stonebraker - you know, the professor who came up with Ingres, PostgreSQL, etc.

    We realized that the fastest way was to first do a bounding box because it's SUPER fast. If it's outside the bounding box, it's outside. Otherwise, you do the harder work...

    If you want a great algorithm, look to the open source project PostgreSQL source code for the geo work...

    I want to point out, we never got any insight into right vs left handedness (also expressible as an "inside" vs "outside" problem...


    UPDATE

    BKB's link provided a good number of reasonable algorithms. I was working on Earth Science problems and therefore needed a solution that works in latitude/longitude, and it has the peculiar problem of handedness - is the area inside the smaller area or the bigger area? The answer is that the "direction" of the verticies matters - it's either left-handed or right handed and in this way you can indicate either area as "inside" any given polygon. As such, my work used solution three enumerated on that page.

    In addition, my work used separate functions for "on the line" tests.

    ...Since someone asked: we figured out that bounding box tests were best when the number of verticies went beyond some number - do a very quick test before doing the longer test if necessary... A bounding box is created by simply taking the largest x, smallest x, largest y and smallest y and putting them together to make four points of a box...

    Another tip for those that follow: we did all our more sophisticated and "light-dimming" computing in a grid space all in positive points on a plane and then re-projected back into "real" longitude/latitude, thus avoiding possible errors of wrapping around when one crossed line 180 of longitude and when handling polar regions. Worked great!

    0 讨论(0)
  • 2020-11-21 06:04

    This question is so interesting. I have another workable idea different from other answers to this post. The idea is to use the sum of angles to decide whether the target is inside or outside. Better known as winding number.

    Let x be the target point. Let array [0, 1, .... n] be the all the points of the area. Connect the target point with every border point with a line. If the target point is inside of this area. The sum of all angles will be 360 degrees. If not the angles will be less than 360.

    Refer to this image to get a basic understanding of the idea:

    My algorithm assumes the clockwise is the positive direction. Here is a potential input:

    [[-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]]
    

    The following is the python code that implements the idea:

    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
    
    0 讨论(0)
  • 2020-11-21 06:04

    I've made a Python implementation of nirg's c++ code:

    Inputs

    • bounding_points: nodes that make up the polygon.
    • bounding_box_positions: candidate points to filter. (In my implementation created from the bounding box.

      (The inputs are lists of tuples in the format: [(xcord, ycord), ...])

    Returns

    • All the points that are inside the polygon.
    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
    

    Again, the idea is taken from here

    0 讨论(0)
  • 2020-11-21 06:04

    To deal with the following special cases in Ray casting algorithm:

    1. The ray overlaps one of the polygon's side.
    2. The point is inside of the polygon and the ray passes through a vertex of the polygon.
    3. The point is outside of the polygon and the ray just touches one of the polygon's angle.

    Check Determining Whether A Point Is Inside A Complex Polygon. The article provides an easy way to resolve them so there will be no special treatment required for the above cases.

    0 讨论(0)
  • 2020-11-21 06:05

    If you are looking for a java-script library there's a javascript google maps v3 extension for the Polygon class to detect whether or not a point resides within it.

    var polygon = new google.maps.Polygon([], "#000000", 1, 1, "#336699", 0.3);
    var isWithinPolygon = polygon.containsLatLng(40, -90);
    

    Google Extention Github

    0 讨论(0)
提交回复
热议问题