Skip to content

Commit

Permalink
Revert back to not enforcing geojson ring orientation
Browse files Browse the repository at this point in the history
This is only required in the RFC7946 GeoJSON specification, which it won't be possible to fully support in pyshp. Instead sticking to the original 2008 GeoJSON, keeping all rings in the same orientation as stored in the shapefile. More details at SciTools/cartopy#2012.
  • Loading branch information
karimbahgat committed Mar 17, 2022
1 parent 186933b commit a9ba72d
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 43 deletions.
10 changes: 2 additions & 8 deletions shapefile.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,11 +349,9 @@ def organize_polygon_rings(rings, return_errors=None):
# where exterior rings are clockwise, and holes counterclockwise
if is_cw(ring):
# ring is exterior
ring = rewind(ring) # GeoJSON and Shapefile exteriors have opposite orientation
exteriors.append(ring)
else:
# ring is a hole
ring = rewind(ring) # GeoJSON and Shapefile holes have opposite orientation
holes.append(ring)

# if only one exterior, then all holes belong to that exterior
Expand Down Expand Up @@ -389,8 +387,8 @@ def organize_polygon_rings(rings, return_errors=None):

if len(exterior_candidates) > 1:
# get hole sample point
# Note: all rings now follow GeoJSON orientation, i.e. holes are clockwise
hole_sample = ring_sample(holes[hole_i], ccw=False)
ccw = not is_cw(holes[hole_i])
hole_sample = ring_sample(holes[hole_i], ccw=ccw)
# collect new exterior candidates
new_exterior_candidates = []
for ext_i in exterior_candidates:
Expand Down Expand Up @@ -434,8 +432,6 @@ def organize_polygon_rings(rings, return_errors=None):
# add orphan holes as exteriors
for hole_i in orphan_holes:
ext = holes[hole_i]
# since this was previously a clockwise ordered hole, inverse the winding order
ext = rewind(ext)
# add as single exterior without any holes
poly = [ext]
polys.append(poly)
Expand All @@ -450,8 +446,6 @@ def organize_polygon_rings(rings, return_errors=None):
if return_errors is not None:
return_errors['polygon_only_holes'] = len(holes)
exteriors = holes
# since these were previously clockwise ordered holes, inverse the winding order
exteriors = [rewind(ext) for ext in exteriors]
# add as single exterior without any holes
polys = [[ext] for ext in exteriors]
return polys
Expand Down
67 changes: 32 additions & 35 deletions test_shapefile.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
],
[0],
{'type':'Polygon','coordinates':[
shapefile.rewind([(1,1),(1,9),(9,9),(9,1),(1,1)]),
[(1,1),(1,9),(9,9),(9,1),(1,1)],
]}
),
(shapefile.POLYGON, # single polygon, holes (ordered)
Expand All @@ -59,9 +59,9 @@
],
[0,5,5+5],
{'type':'Polygon','coordinates':[
shapefile.rewind([(1,1),(1,9),(9,9),(9,1),(1,1)]), # exterior
shapefile.rewind([(2,2),(4,2),(4,4),(2,4),(2,2)]), # hole 1
shapefile.rewind([(5,5),(7,5),(7,7),(5,7),(5,5)]), # hole 2
[(1,1),(1,9),(9,9),(9,1),(1,1)], # exterior
[(2,2),(4,2),(4,4),(2,4),(2,2)], # hole 1
[(5,5),(7,5),(7,7),(5,7),(5,5)], # hole 2
]}
),
(shapefile.POLYGON, # single polygon, holes (unordered)
Expand All @@ -72,9 +72,9 @@
],
[0,5,5+5],
{'type':'Polygon','coordinates':[
shapefile.rewind([(1,1),(1,9),(9,9),(9,1),(1,1)]), # exterior
shapefile.rewind([(2,2),(4,2),(4,4),(2,4),(2,2)]), # hole 1
shapefile.rewind([(5,5),(7,5),(7,7),(5,7),(5,5)]), # hole 2
[(1,1),(1,9),(9,9),(9,1),(1,1)], # exterior
[(2,2),(4,2),(4,4),(2,4),(2,2)], # hole 1
[(5,5),(7,5),(7,7),(5,7),(5,5)], # hole 2
]}
),
(shapefile.POLYGON, # multi polygon, no holes
Expand All @@ -84,10 +84,10 @@
[0,5],
{'type':'MultiPolygon','coordinates':[
[ # poly 1
shapefile.rewind([(1,1),(1,9),(9,9),(9,1),(1,1)]),
[(1,1),(1,9),(9,9),(9,1),(1,1)],
],
[ # poly 2
shapefile.rewind([(11,11),(11,19),(19,19),(19,11),(11,11)]),
[(11,11),(11,19),(19,19),(19,11),(11,11)],
],
]}
),
Expand All @@ -102,14 +102,14 @@
[0,5,10,15,20,25],
{'type':'MultiPolygon','coordinates':[
[ # poly 1
shapefile.rewind([(1,1),(1,9),(9,9),(9,1),(1,1)]), # exterior
shapefile.rewind([(2,2),(4,2),(4,4),(2,4),(2,2)]), # hole 1
shapefile.rewind([(5,5),(7,5),(7,7),(5,7),(5,5)]), # hole 2
[(1,1),(1,9),(9,9),(9,1),(1,1)], # exterior
[(2,2),(4,2),(4,4),(2,4),(2,2)], # hole 1
[(5,5),(7,5),(7,7),(5,7),(5,5)], # hole 2
],
[ # poly 2
shapefile.rewind([(11,11),(11,19),(19,19),(19,11),(11,11)]), # exterior
shapefile.rewind([(12,12),(14,12),(14,14),(12,14),(12,12)]), # hole 1
shapefile.rewind([(15,15),(17,15),(17,17),(15,17),(15,15)]), # hole 2
[(11,11),(11,19),(19,19),(19,11),(11,11)], # exterior
[(12,12),(14,12),(14,14),(12,14),(12,12)], # hole 1
[(15,15),(17,15),(17,17),(15,17),(15,15)], # hole 2
],
]}
),
Expand All @@ -123,15 +123,15 @@
[0,5,10,15,20],
{'type':'MultiPolygon','coordinates':[
[ # poly 1
shapefile.rewind([(1,1),(1,9),(9,9),(9,1),(1,1)]), # exterior 1
shapefile.rewind([(2,2),(8,2),(8,8),(2,8),(2,2)]), # hole 1.1
[(1,1),(1,9),(9,9),(9,1),(1,1)], # exterior 1
[(2,2),(8,2),(8,8),(2,8),(2,2)], # hole 1.1
],
[ # poly 2
shapefile.rewind([(3,3),(3,7),(7,7),(7,3),(3,3)]), # exterior 2
shapefile.rewind([(4,4),(6,4),(6,6),(4,6),(4,4)]), # hole 2.1
[(3,3),(3,7),(7,7),(7,3),(3,3)], # exterior 2
[(4,4),(6,4),(6,6),(4,6),(4,4)], # hole 2.1
],
[ # poly 3
shapefile.rewind([(4.5,4.5),(4.5,5.5),(5.5,5.5),(5.5,4.5),(4.5,4.5)]), # exterior 3
[(4.5,4.5),(4.5,5.5),(5.5,5.5),(5.5,4.5),(4.5,4.5)], # exterior 3
],
]}
),
Expand All @@ -145,15 +145,15 @@
[0,5,10,15,20+3],
{'type':'MultiPolygon','coordinates':[
[ # poly 1
shapefile.rewind([(1,1),(1,9),(9,9),(9,1),(1,1)]), # exterior 1
shapefile.rewind([(2,2),(3,3),(4,2),(8,2),(8,8),(4,8),(2,8),(2,4),(2,2)]), # hole 1.1
[(1,1),(1,9),(9,9),(9,1),(1,1)], # exterior 1
[(2,2),(3,3),(4,2),(8,2),(8,8),(4,8),(2,8),(2,4),(2,2)], # hole 1.1
],
[ # poly 2
shapefile.rewind([(3,3),(3,7),(7,7),(7,3),(3,3)]), # exterior 2
shapefile.rewind([(4,4),(4,4),(6,4),(6,4),(6,4),(6,6),(4,6),(4,4)]), # hole 2.1
[(3,3),(3,7),(7,7),(7,3),(3,3)], # exterior 2
[(4,4),(4,4),(6,4),(6,4),(6,4),(6,6),(4,6),(4,4)], # hole 2.1
],
[ # poly 3
shapefile.rewind([(4.5,4.5),(4.5,5.5),(5.5,5.5),(5.5,4.5),(4.5,4.5)]), # exterior 3
[(4.5,4.5),(4.5,5.5),(5.5,5.5),(5.5,4.5),(4.5,4.5)], # exterior 3
],
]}
),
Expand All @@ -169,17 +169,16 @@
[0,5,10,15,20,25,30],
{'type':'MultiPolygon','coordinates':[
[ # poly 1
shapefile.rewind([(1,1),(1,9),(9,9),(9,1),(1,1)]), # exterior
shapefile.rewind([(2,2),(4,2),(4,4),(2,4),(2,2)]), # hole 1
shapefile.rewind([(5,5),(7,5),(7,7),(5,7),(5,5)]), # hole 2
[(1,1),(1,9),(9,9),(9,1),(1,1)], # exterior
[(2,2),(4,2),(4,4),(2,4),(2,2)], # hole 1
[(5,5),(7,5),(7,7),(5,7),(5,5)], # hole 2
],
[ # poly 2
shapefile.rewind([(11,11),(11,19),(19,19),(19,11),(11,11)]), # exterior
shapefile.rewind([(12,12),(14,12),(14,14),(12,14),(12,12)]), # hole 1
shapefile.rewind([(15,15),(17,15),(17,17),(15,17),(15,15)]), # hole 2
[(11,11),(11,19),(19,19),(19,11),(11,11)], # exterior
[(12,12),(14,12),(14,14),(12,14),(12,12)], # hole 1
[(15,15),(17,15),(17,17),(15,17),(15,15)], # hole 2
],
[ # poly 3 (orphaned hole)
# Note: due to the hole-to-exterior conversion, should return the same ring orientation
[ # poly 3 (orphaned hole)
[(95,95),(97,95),(97,97),(95,97),(95,95)], # exterior
],
]}
Expand All @@ -191,11 +190,9 @@
[0,5],
{'type':'MultiPolygon','coordinates':[
[ # poly 1
# Note: due to the hole-to-exterior conversion, should return the same ring orientation
[(1,1),(9,1),(9,9),(1,9),(1,1)],
],
[ # poly 2
# Note: due to the hole-to-exterior conversion, should return the same ring orientation
[(11,11),(19,11),(19,19),(11,19),(11,11)],
],
]}
Expand Down

0 comments on commit a9ba72d

Please sign in to comment.