@@ -182,6 +182,14 @@ def bbox_overlap(bbox1, bbox2):
182
182
overlap = (xmin1 <= xmax2 and xmax1 >= xmin2 and ymin1 <= ymax2 and ymax1 >= ymin2 )
183
183
return overlap
184
184
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
+
185
193
def ring_contains_point (coords , p ):
186
194
"""Fast point-in-polygon crossings algorithm, MacMartin optimization.
187
195
@@ -224,6 +232,44 @@ def ring_contains_point(coords, p):
224
232
225
233
return inside_flag
226
234
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
+
227
273
def ring_contains_ring (coords1 , coords2 ):
228
274
'''Returns True if all vertexes in coords2 are fully inside coords1.
229
275
'''
@@ -272,25 +318,26 @@ def organize_polygon_rings(rings):
272
318
polys .append (poly )
273
319
return polys
274
320
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
276
322
hole_exteriors = dict ([(hole_i ,[]) for hole_i in xrange (len (holes ))])
277
323
exterior_bboxes = [ring_bbox (ring ) for ring in exteriors ]
278
324
for hole_i in hole_exteriors .keys ():
279
325
hole_bbox = ring_bbox (holes [hole_i ])
280
326
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 ):
282
328
hole_exteriors [hole_i ].append ( ext_i )
283
329
284
330
# then, for holes with still more than one possible exterior, do more detailed hole-in-ring test
285
331
for hole_i ,exterior_candidates in hole_exteriors .items ():
286
332
287
333
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
290
337
new_exterior_candidates = []
291
338
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 )
294
341
if hole_in_exterior :
295
342
new_exterior_candidates .append (ext_i )
296
343
0 commit comments