@@ -182,6 +182,14 @@ def bbox_overlap(bbox1, bbox2):
182182 overlap = (xmin1 <= xmax2 and xmax1 >= xmin2 and ymin1 <= ymax2 and ymax1 >= ymin2 )
183183 return overlap
184184
185+ def bbox_contains (bbox1 , bbox2 ):
186+ """Tests whether bbox1 fully contains bbox2, returning a boolean
187+ """
188+ xmin1 ,ymin1 ,xmax1 ,ymax1 = bbox1
189+ xmin2 ,ymin2 ,xmax2 ,ymax2 = bbox2
190+ contains = (xmin1 < xmin2 and xmax1 > xmax2 and ymin1 < ymin2 and ymax1 > ymax2 )
191+ return contains
192+
185193def ring_contains_point (coords , p ):
186194 """Fast point-in-polygon crossings algorithm, MacMartin optimization.
187195
@@ -224,6 +232,44 @@ def ring_contains_point(coords, p):
224232
225233 return inside_flag
226234
235+ def ring_sample (coords , ccw = False ):
236+ """Return a sample point guaranteed to be within a ring, by efficiently
237+ finding the first centroid of a coordinate triplet whose orientation
238+ matches the orientation of the ring and passes the point-in-ring test.
239+ The orientation of the ring is assumed to be clockwise, unless ccw
240+ (counter-clockwise) is set to True.
241+ """
242+ coords = tuple (coords ) + (coords [1 ],) # add the second coordinate to the end to allow checking the last triplet
243+ triplet = []
244+ for p in coords :
245+ # add point to triplet (but not if duplicate)
246+ if p not in triplet :
247+ triplet .append (p )
248+
249+ # new triplet, try to get sample
250+ if len (triplet ) == 3 :
251+ # check that triplet does not form a straight line (not a triangle)
252+ is_straight_line = (triplet [0 ][1 ] - triplet [1 ][1 ]) * (triplet [0 ][0 ] - triplet [2 ][0 ]) == (triplet [0 ][1 ] - triplet [2 ][1 ]) * (triplet [0 ][0 ] - triplet [1 ][0 ])
253+ if not is_straight_line :
254+ # get triplet orientation
255+ closed_triplet = triplet + [triplet [0 ]]
256+ triplet_ccw = signed_area (closed_triplet ) >= 0
257+ # check that triplet has the same orientation as the ring (means triangle is inside the ring)
258+ if ccw == triplet_ccw :
259+ # get triplet centroid
260+ xs ,ys = zip (* triplet )
261+ xmean ,ymean = sum (xs ) / 3.0 , sum (ys ) / 3.0
262+ # check that triplet centroid is truly inside the ring
263+ if ring_contains_point (coords , (xmean ,ymean )):
264+ return xmean ,ymean
265+
266+ # failed to get sample point from this triplet
267+ # remove oldest triplet coord to allow iterating to next triplet
268+ triplet .pop (0 )
269+
270+ else :
271+ raise Exception ('Unexpected error: Unable to find a ring sample point.' )
272+
227273def ring_contains_ring (coords1 , coords2 ):
228274 '''Returns True if all vertexes in coords2 are fully inside coords1.
229275 '''
@@ -272,25 +318,26 @@ def organize_polygon_rings(rings):
272318 polys .append (poly )
273319 return polys
274320
275- # first determine each hole's candidate exteriors based on simple bbox overlap test
321+ # first determine each hole's candidate exteriors based on simple bbox contains test
276322 hole_exteriors = dict ([(hole_i ,[]) for hole_i in xrange (len (holes ))])
277323 exterior_bboxes = [ring_bbox (ring ) for ring in exteriors ]
278324 for hole_i in hole_exteriors .keys ():
279325 hole_bbox = ring_bbox (holes [hole_i ])
280326 for ext_i ,ext_bbox in enumerate (exterior_bboxes ):
281- if bbox_overlap ( hole_bbox , ext_bbox ):
327+ if bbox_contains ( ext_bbox , hole_bbox ):
282328 hole_exteriors [hole_i ].append ( ext_i )
283329
284330 # then, for holes with still more than one possible exterior, do more detailed hole-in-ring test
285331 for hole_i ,exterior_candidates in hole_exteriors .items ():
286332
287333 if len (exterior_candidates ) > 1 :
288- # get new exterior candidates
289- hole = holes [hole_i ]
334+ # get hole sample point
335+ hole_sample = ring_sample (holes [hole_i ], ccw = True )
336+ # collect new exterior candidates
290337 new_exterior_candidates = []
291338 for ext_i in exterior_candidates :
292- ext = exteriors [ ext_i ]
293- hole_in_exterior = ring_contains_ring ( ext , hole )
339+ # check that hole sample point is inside exterior
340+ hole_in_exterior = ring_contains_point ( exteriors [ ext_i ], hole_sample )
294341 if hole_in_exterior :
295342 new_exterior_candidates .append (ext_i )
296343
0 commit comments