diff --git a/CHANGELOG.md b/CHANGELOG.md index f7e3bf57..82749975 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed * `urquhartFaces()`, `relativeNeighborFaces()`, `gabrielFaces()` and `spannerFaces()` from `PGS_Meshing` now preserve holes from the input. +* The `from` and `to` arguments for `align()` were the wrong way round. +* The output of `PGS_Morphology.smoothGaussian()` is no longer (slightly) affected by the vertex ordering of the input. ### Removed * `simplifyDCE(shape, targetNumVertices)` and `simplifyDCE(shape, vertexRemovalFraction)` in favour a single method that accepts a user-defined termination callback that is supplied with the current vertex candidate's coordinate, relevance score, and the number of vertices remaining. diff --git a/src/main/java/micycle/pgs/commons/GaussianLineSmoothing.java b/src/main/java/micycle/pgs/commons/GaussianLineSmoothing.java index 3765a8aa..d4d53e6e 100644 --- a/src/main/java/micycle/pgs/commons/GaussianLineSmoothing.java +++ b/src/main/java/micycle/pgs/commons/GaussianLineSmoothing.java @@ -1,7 +1,10 @@ package micycle.pgs.commons; +import java.util.Arrays; + import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.LineString; + import net.jafama.FastMath; /** @@ -43,6 +46,9 @@ public static LineString get(LineString line, double sigmaM, double resolution) return (LineString) line.copy(); } + // output is sensitive to vertex order, so normalise the line (note doesn't + // normalise orientation) + line = normalise(line); boolean isClosed = line.isClosed(); double length = line.getLength(); double densifiedResolution = sigmaM / 3; @@ -165,4 +171,43 @@ public static LineString get(LineString line, double sigmaM, double resolution) return lsOut; } + /** + * Normalises the LineString so that it starts from the coordinate with the + * smallest x value. In case of a tie on the x value, the smallest y value is + * used. + * + * @param line The open or closed LineString to be normalised. + * @return A new LineString with coordinates ordered starting from the smallest + * x (and y, if tied). + */ + private static LineString normalise(LineString line) { + boolean isClosed = line.isClosed(); + + Coordinate[] originalCoords = line.getCoordinates(); + if (isClosed && originalCoords[0].equals2D(originalCoords[originalCoords.length - 1])) { + // Remove last vertex if it is a duplicate of the first for closed lines + originalCoords = Arrays.copyOf(originalCoords, originalCoords.length - 1); + } + + // Find index of coordinate with smallest x value, tie by y value + int minIndex = 0; + for (int i = 1; i < originalCoords.length; i++) { + if (originalCoords[i].x < originalCoords[minIndex].x + || (originalCoords[i].x == originalCoords[minIndex].x && originalCoords[i].y < originalCoords[minIndex].y)) { + minIndex = i; + } + } + + // Rotate array to start from vertex with smallest x (and y, if tied) + Coordinate[] rotatedCoords = new Coordinate[originalCoords.length + (isClosed ? 1 : 0)]; + System.arraycopy(originalCoords, minIndex, rotatedCoords, 0, originalCoords.length - minIndex); + System.arraycopy(originalCoords, 0, rotatedCoords, originalCoords.length - minIndex, minIndex); + + if (isClosed) { + rotatedCoords[rotatedCoords.length - 1] = rotatedCoords[0]; // Close the loop + } + + return line.getFactory().createLineString(rotatedCoords); + } + } \ No newline at end of file