Skip to content

Commit a7faa11

Browse files
authored
Merge pull request #2044 from MJohnson459/fix-multipolygon
fix: restore Polygon cutline schema for Shapely 2
2 parents c81c6b3 + 8a6dee7 commit a7faa11

2 files changed

Lines changed: 74 additions & 11 deletions

File tree

opendm/cutline.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from skimage.draw import line
1414
from skimage.graph import route_through_array
1515
import shapely
16-
from shapely.geometry import LineString, mapping, shape
16+
from shapely.geometry import LineString, MultiPolygon, mapping, shape
1717
from shapely.ops import polygonize, unary_union
1818

1919
def write_raster(data, file):
@@ -31,6 +31,19 @@ def write_raster(data, file):
3131
with rasterio.open(file, 'w', BIGTIFF="IF_SAFER", **profile) as wout:
3232
wout.write(data, 1)
3333

34+
def largest_polygon(polygons):
35+
cutline_union = unary_union(polygons)
36+
if isinstance(cutline_union, MultiPolygon):
37+
parts = list(cutline_union.geoms)
38+
else:
39+
parts = [cutline_union]
40+
41+
largest = parts[0]
42+
for p in parts[1:]:
43+
if p.area > largest.area:
44+
largest = p
45+
return largest
46+
3447
def compute_cutline(orthophoto_file, crop_area_file, destination, max_concurrency=1, scale=1):
3548
if io.file_exists(orthophoto_file) and io.file_exists(crop_area_file):
3649
log.ODM_INFO("Computing cutline")
@@ -140,16 +153,8 @@ def compute_linestrings(direction):
140153
return
141154

142155
log.ODM_INFO("Merging polygons")
143-
cutline_polygons = unary_union(polygons)
144-
if not hasattr(cutline_polygons, '__getitem__'):
145-
cutline_polygons = [cutline_polygons]
146-
147-
largest_cutline = cutline_polygons[0]
156+
largest_cutline = largest_polygon(polygons)
148157
max_area = largest_cutline.area
149-
for p in cutline_polygons:
150-
if p.area > max_area:
151-
max_area = p.area
152-
largest_cutline = p
153158

154159
log.ODM_INFO("Largest cutline found: %s m^2" % max_area)
155160

@@ -158,7 +163,7 @@ def compute_linestrings(direction):
158163
'driver': 'GPKG',
159164
'schema': {
160165
'properties': {},
161-
'geometry': 'MultiPolygon'
166+
'geometry': 'Polygon'
162167
}
163168
}
164169

tests/test_cutline.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import os
2+
import tempfile
3+
import unittest
4+
5+
import fiona
6+
from shapely.geometry import Polygon, mapping
7+
8+
from opendm.cutline import largest_polygon
9+
10+
11+
class TestCutline(unittest.TestCase):
12+
def test_largest_polygon_from_single_polygon(self):
13+
""" Tests that the largest polygon from a single polygon is the polygon itself """
14+
15+
p = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]) # 1x1 square
16+
largest = largest_polygon([p])
17+
18+
self.assertEqual(largest.geom_type, "Polygon")
19+
self.assertAlmostEqual(largest.area, 1.0)
20+
21+
def test_largest_polygon_from_multipolygon_union(self):
22+
""" Tests that the largest polygon from a multipolygon union is the largest polygon """
23+
24+
small = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]) # 1x1 square (area = 1)
25+
large = Polygon([(0, 0), (3, 0), (3, 2), (0, 2)]) # 3x2 rectangle (area = 6)
26+
largest = largest_polygon([small, large])
27+
self.assertEqual(largest.geom_type, "Polygon")
28+
self.assertAlmostEqual(largest.area, 6.0) # matches large polygon
29+
30+
def test_polygon_writes_to_gpkg_with_polygon_schema(self):
31+
""" Tests that fiona can write our polygon to a GPKG file"""
32+
33+
geom = largest_polygon([
34+
Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]),
35+
Polygon([(2, 0), (5, 0), (5, 2), (2, 2)]),
36+
])
37+
self.assertEqual(mapping(geom)["type"], "Polygon")
38+
39+
# Write out the polygon to quickly verify that Fiona can still process
40+
# our largest polygon.
41+
with tempfile.TemporaryDirectory() as tmp:
42+
path = os.path.join(tmp, "cutline.gpkg")
43+
with fiona.open(
44+
path,
45+
"w",
46+
driver="GPKG",
47+
crs="EPSG:4326",
48+
schema={"geometry": "Polygon", "properties": {}},
49+
) as sink:
50+
sink.write({"geometry": mapping(geom), "properties": {}})
51+
52+
with fiona.open(path) as src:
53+
feature = next(iter(src))
54+
self.assertEqual(feature["geometry"]["type"], "Polygon")
55+
56+
57+
if __name__ == "__main__":
58+
unittest.main()

0 commit comments

Comments
 (0)