Skip to content

Commit

Permalink
Fix MinimumDiameter minimumRectangle for flat input (#875)
Browse files Browse the repository at this point in the history
* Fix MinimumDiameter minRectangle for flat input

Signed-off-by: Martin Davis <mtnclimb@gmail.com>
  • Loading branch information
dr-jts committed May 25, 2022
1 parent 77aaf32 commit b09abfe
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 16 deletions.
1 change: 1 addition & 0 deletions doc/JTS_Version_History.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ Distributions for older JTS versions can be obtained at the
* Fix IsValidOp for repeated node points (#845)
* Fix `IsSimpleOp` for repeated endpoints (#851)
* Fix `GeometryFixer` via noding check for zero-distance buffers (#867)
* Fix `MinimumDiameter.minimumRectangle` for flat inputs (#875)

# Version 1.18.2

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LineSegment;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.LinearRing;
Expand All @@ -22,23 +23,25 @@
/**
* Computes the minimum diameter of a {@link Geometry}.
* The minimum diameter is defined to be the
* width of the smallest band that
* contains the geometry,
* where a band is a strip of the plane defined
* by two parallel lines.
* width of the smallest band that contains the geometry,
* where a band is a strip of the plane defined by two parallel lines.
* This can be thought of as the smallest hole that the geometry can be
* moved through, with a single rotation.
* <p>
* The first step in the algorithm is computing the convex hull of the Geometry.
* If the input Geometry is known to be convex, a hint can be supplied to
* avoid this computation.
* <p>
* This class can also be used to compute a line segment representing
* the minimum diameter, the supporting line segment of the minimum diameter,
* and a minimum rectangle enclosing the input geometry.
* This rectangle will
* have width equal to the minimum diameter, and have one side
* This class can also be used to compute:
* <ul>
* <li>a line segment representing the minimum diameter
* <li>the <b>supporting line segment</b> of the minimum diameter
* <li>the <b>minimum enclosing rectangle</b> of the input geometry.
* The rectangle has width equal to the minimum diameter, and has one side
* parallel to the supporting segment.
* In degenerate cases the minimum enclosing geometry may be a LineString or a Point.
* </ul>
*
*
* @see ConvexHull
*
Expand All @@ -47,7 +50,14 @@
public class MinimumDiameter
{
/**
* Gets the minimum rectangle enclosing a geometry.
* Gets the minimum rectangular {@link Polygon} which encloses the input geometry.
* The rectangle has width equal to the minimum diameter,
* and a longer length.
* If the convex hull of the input is degenerate (a line or point)
* a {@link LineString} or {@link Point} is returned.
* <p>
* The minimum rectangle can be used as an extremely generalized representation
* for the given geometry.
*
* @param geom the geometry
* @return the minimum rectangle enclosing the geometry
Expand Down Expand Up @@ -226,6 +236,8 @@ private int findMaxPerpDistance(Coordinate[] pts, LineSegment seg, int startInde
maxIndex = nextIndex;

nextIndex = nextIndex(pts, maxIndex);
if (nextIndex == startIndex)
break;
nextPerpDistance = seg.distancePerpendicular(pts[nextIndex]);
}
// found maximum width for this segment - update global min dist if appropriate
Expand Down Expand Up @@ -265,21 +277,18 @@ public Geometry getMinimumRectangle()

// check if minimum rectangle is degenerate (a point or line segment)
if (minWidth == 0.0) {
//-- Min rectangle is a point
if (minBaseSeg.p0.equals2D(minBaseSeg.p1)) {
return inputGeom.getFactory().createPoint(minBaseSeg.p0);
}
return minBaseSeg.toGeometry(inputGeom.getFactory());
//-- Min rectangle is a line. Use the diagonal of the extent
return computeMaximumLine(convexHullPts, inputGeom.getFactory());
}

// deltas for the base segment of the minimum diameter
double dx = minBaseSeg.p1.x - minBaseSeg.p0.x;
double dy = minBaseSeg.p1.y - minBaseSeg.p0.y;

/*
double c0 = computeC(dx, dy, minBaseSeg.p0);
double c1 = computeC(dx, dy, minBaseSeg.p1);
*/

double minPara = Double.MAX_VALUE;
double maxPara = -Double.MAX_VALUE;
double minPerp = Double.MAX_VALUE;
Expand Down Expand Up @@ -315,6 +324,34 @@ public Geometry getMinimumRectangle()

}

/**
* Creates a line of maximum extent from the provided vertices
* @param pts the vertices
* @param factory the geometry factory
* @return the line of maximum extent
*/
private static LineString computeMaximumLine(Coordinate[] pts, GeometryFactory factory) {
//-- find max and min pts for X and Y
Coordinate ptMinX = null;
Coordinate ptMaxX = null;
Coordinate ptMinY = null;
Coordinate ptMaxY = null;
for (Coordinate p : pts) {
if (ptMinX == null || p.getX() < ptMinX.getX()) ptMinX = p;
if (ptMaxX == null || p.getX() > ptMaxX.getX()) ptMaxX = p;
if (ptMinY == null || p.getY() < ptMinY.getY()) ptMinY = p;
if (ptMaxY == null || p.getY() > ptMaxY.getY()) ptMaxY = p;
}
Coordinate p0 = ptMinX;
Coordinate p1 = ptMaxX;
//-- line is vertical - use Y pts
if (p0.getX() == p1.getX()) {
p0 = ptMinY;
p1 = ptMaxY;
}
return factory.createLineString(new Coordinate[] { p0.copy(), p1.copy() });
}

private static double computeC(double a, double b, Coordinate p)
{
return a * p.y - b * p.x;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright (c) 2022 Martin Davis.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* and Eclipse Distribution License v. 1.0 which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
* and the Eclipse Distribution License is available at
*
* http://www.eclipse.org/org/documents/edl-v10.php.
*/
package org.locationtech.jts.algorithm;

import org.locationtech.jts.geom.Geometry;

import junit.textui.TestRunner;
import test.jts.GeometryTestCase;


/**
* @version 1.7
*/
public class MinimumRectanglelTest extends GeometryTestCase {

private static final double TOL = 1e-10;

public static void main(String args[]) {
TestRunner.run(MinimumRectanglelTest.class);
}

public MinimumRectanglelTest(String name) { super(name); }

public void testLengthZero() {
checkMinRectangle("LINESTRING (1 1, 1 1)", "POINT (1 1)");
}

public void testHorizontal() {
checkMinRectangle("LINESTRING (1 1, 3 1, 5 1, 7 1)", "LINESTRING (1 1, 7 1)");
}

public void testVertical() {
checkMinRectangle("LINESTRING (1 1, 1 4, 1 7, 1 9)", "LINESTRING (1 1, 1 9)");
}

public void testBentLine() {
checkMinRectangle("LINESTRING (1 2, 3 8, 9 6)", "POLYGON ((9 6, 7 10, -1 6, 1 2, 9 6))");
}

/**
* Failure case from https://trac.osgeo.org/postgis/ticket/5163
* @throws Exception
*/
public void testFlatDiagonal() throws Exception {
checkMinRectangle("LINESTRING(-99.48710639268086 34.79029839231914,-99.48370699999998 34.78689899963806,-99.48152167568102 34.784713675318976)",
"LINESTRING (-99.48710639268086 34.79029839231914, -99.48152167568102 34.784713675318976)");
}

private void checkMinRectangle(String wkt, String wktExpected) {
Geometry geom = read(wkt);
Geometry actual = MinimumDiameter.getMinimumRectangle(geom);
Geometry expected = read(wktExpected);
checkEqual(expected, actual, TOL);
}


}

0 comments on commit b09abfe

Please sign in to comment.