Skip to content

Commit c2bdf32

Browse files
authored
Fix densification functions (#412)
- densify_by_distance now retains the original polygon vertex coordinates and only inserts new points on polygon segments longer than the `distance` argument. - densify_by_factor now retains the last vertex in the original polygon - enforces counter-clockwise polygons
1 parent cddb118 commit c2bdf32

File tree

3 files changed

+160
-141
lines changed

3 files changed

+160
-141
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Fixed
11+
12+
- `densify_by_distance` now includes the points in the original coordinate list in the returned densified coordinate list ([#412](https://github.com/stac-utils/stactools/pull/412))
13+
- `densify_by_factor` now includes the final coordinate in the original coordinate list in the returned densifed coordinate list ([#412](https://github.com/stac-utils/stactools/pull/412))
14+
- The `footprint` method on the `RasterFootprint` class now always returns counter-clockwise polygons in line with the GeoJSON specification ([#412](https://github.com/stac-utils/stactools/pull/412))
15+
1016
## [0.4.5] - 2023-03-24
1117

1218
### Added

src/stactools/core/utils/raster_footprint.py

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from rasterio.warp import transform_geom
1717
from shapely.geometry import mapping, shape
1818
from shapely.geometry.multipolygon import MultiPolygon
19-
from shapely.geometry.polygon import Polygon
19+
from shapely.geometry.polygon import Polygon, orient
2020

2121
logger = logging.getLogger(__name__)
2222

@@ -49,11 +49,10 @@ def densify_by_factor(
4949
points: Any = np.asarray(point_list)
5050
densified_number = len(points) * factor
5151
existing_indices = np.arange(0, densified_number, factor)
52-
interp_indices = np.arange(existing_indices[-1])
52+
interp_indices = np.arange(existing_indices[-1] + 1)
5353
interp_x = np.interp(interp_indices, existing_indices, points[:, 0])
5454
interp_y = np.interp(interp_indices, existing_indices, points[:, 1])
55-
densified_points = [(x, y) for x, y in zip(interp_x, interp_y)]
56-
return densified_points
55+
return [(x, y) for x, y in zip(interp_x, interp_y)]
5756

5857

5958
def densify_by_distance(
@@ -81,14 +80,17 @@ def densify_by_distance(
8180
points: Any = np.asarray(point_list)
8281
dxdy = points[1:, :] - points[:-1, :]
8382
segment_lengths = np.sqrt(np.sum(np.square(dxdy), axis=1))
84-
total_length = np.sum(segment_lengths)
85-
cum_segment_lengths = np.cumsum(segment_lengths)
86-
cum_segment_lengths = np.insert(cum_segment_lengths, 0, [0])
87-
cum_interp_lengths = np.arange(0, total_length, distance)
88-
cum_interp_lengths = np.append(cum_interp_lengths, [total_length])
89-
interp_x = np.interp(cum_interp_lengths, cum_segment_lengths, points[:, 0])
90-
interp_y = np.interp(cum_interp_lengths, cum_segment_lengths, points[:, 1])
91-
return [(x, y) for x, y in zip(interp_x, interp_y)]
83+
steps = segment_lengths / distance
84+
coordinate_steps = dxdy / steps.reshape(-1, 1)
85+
densified_points = np.empty((len(point_list) - 1,), dtype="O")
86+
for index in range(len(point_list) - 1):
87+
step = np.arange(steps[index])
88+
densified_points[index] = (
89+
np.array((step, step)).T * coordinate_steps[index] + points[index]
90+
)
91+
final_point = points[-1].reshape(1, -1)
92+
densified_array = np.concatenate((*densified_points, final_point), axis=0)
93+
return [(float(row[0]), float(row[1])) for row in densified_array]
9294

9395

9496
def reproject_polygon(
@@ -290,7 +292,7 @@ def data_extent(self, mask: npt.NDArray[np.uint8]) -> Optional[Polygon]:
290292
else:
291293
polygon = MultiPolygon(data_polygons).convex_hull
292294

293-
return polygon
295+
return orient(polygon)
294296

295297
def densify_polygon(self, polygon: Polygon) -> Polygon:
296298
"""Adds vertices to the footprint polygon in the native CRS using
@@ -343,8 +345,10 @@ def simplify_polygon(self, polygon: Polygon) -> Polygon:
343345
Polygon: Reduced vertex polygon.
344346
"""
345347
if self.simplify_tolerance is not None:
346-
return polygon.simplify(
347-
tolerance=self.simplify_tolerance, preserve_topology=False
348+
return orient(
349+
polygon.simplify(
350+
tolerance=self.simplify_tolerance, preserve_topology=False
351+
)
348352
)
349353
return polygon
350354

0 commit comments

Comments
 (0)