Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix MinimumDiameter minimumRectangle for flat input #875

Merged
merged 2 commits into from
May 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}


}