Skip to content

Commit

Permalink
Enhance GeometryPrecisionReducer to use OverlayNG PrecisionReducer
Browse files Browse the repository at this point in the history
Signed-off-by: Martin Davis <mtnclimb@gmail.com>
  • Loading branch information
dr-jts committed Sep 11, 2020
1 parent a3e5d17 commit c6f2208
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,28 @@
/**
* Functions to reduce the precision of a geometry
* by rounding it to a given precision model.
* <p>
* This class handles only polygonal and linear inputs.
* For full functionality see {@link GeometryPrecisionReducer}.
*
* @see GeometryPrecisionReducer
* @author Martin Davis
*
*/
public class PrecisionReducer {

/**
* Reduces the precision of a geometry by rounding it to the
* Reduces the precision of a geometry by rounding and snapping it to the
* supplied {@link PrecisionModel}.
* The input geometry must be polygonal or linear.
* <p>
* The output is always a valid geometry. This implies that input components
* may be merged if they are closer than the grid precision.
* if merging is not desired, then the individual geometry components
* should be processed separately.
* <p>
* The output is fully noded.
* The output is fully noded
* (i.e. coincident lines are merged and noded).
* This provides an effective way to node / snap-round a collection of {@link LineString}s.
*
* @param geom the geometry to reduce
Expand All @@ -42,7 +48,13 @@ public class PrecisionReducer {
*/
public static Geometry reducePrecision(Geometry geom, PrecisionModel pm) {
OverlayNG ov = new OverlayNG(geom, pm);
ov.setAreaResultOnly(true);
/**
* Ensure reducing a area only produces polygonal result.
* (I.e. collapse lines are not output)
*/
if (geom.getDimension() == 2) {
ov.setAreaResultOnly(true);
}
Geometry reduced = ov.getResult();
return reduced;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,28 @@
import org.locationtech.jts.geom.Polygonal;
import org.locationtech.jts.geom.PrecisionModel;
import org.locationtech.jts.geom.util.GeometryEditor;
import org.locationtech.jts.operation.overlayng.PrecisionReducer;

/**
* Reduces the precision of a {@link Geometry}
* according to the supplied {@link PrecisionModel},
* ensuring that the result is topologically valid.
* ensuring that the result is valid (unless specified otherwise).
* <p>
* By default the reduced result is topologically valid
* (i.e. {@link Geometry#isValid()} is true).
* To ensure this a polygonal geometry is reduced in a topologically valid fashion
* (technically, by using snap-rounding).
* It can be forced to be reduced pointwise by using {@link #setPointwise(boolean)}.
* Note that in this case the result geometry may be invalid.
* Linear and point geometry is always reduced pointwise (i.e. without further change to
* its topology or stucture), since this does not change validity.
* <p>
* By default the geometry precision model is not changed.
* This can be overridden by using {@link #setChangePrecisionModel(boolean)}.
* <p>
* Normally collapsed components (e.g. lines collapsing to a point)
* are not included in the result.
* This behavior can be changed by using {@link #setRemoveCollapsedComponents(boolean)}.
*
* @version 1.12
*/
Expand Down Expand Up @@ -117,20 +134,19 @@ public void setPointwise(boolean isPointwise)

public Geometry reduce(Geometry geom)
{
if (!isPointwise && geom instanceof Polygonal) {
Geometry reduced = PrecisionReducer.reducePrecision(geom, targetPM);
if (changePrecisionModel) {
return changePM(reduced, targetPM);
}
return reduced;
}
/**
* Process pointwise reduction
* (which is only strategy used for linear and point geoms)
*/
Geometry reducePW = reducePointwise(geom);
if (isPointwise)
return reducePW;

//TODO: handle GeometryCollections containing polys
if (! (reducePW instanceof Polygonal))
return reducePW;

// Geometry is polygonal - test if topology needs to be fixed
if (reducePW.isValid()) return reducePW;

// hack to fix topology.
// TODO: implement snap-rounding and use that.
return fixPolygonalTopology(reducePW);
return reducePW;
}

private Geometry reducePointwise(Geometry geom)
Expand Down Expand Up @@ -158,27 +174,6 @@ private Geometry reducePointwise(Geometry geom)
return reduceGeom;
}

private Geometry fixPolygonalTopology(Geometry geom)
{
/**
* If precision model was *not* changed, need to flip
* geometry to targetPM, buffer in that model, then flip back
*/
Geometry geomToBuffer = geom;
if (! changePrecisionModel) {
geomToBuffer = changePM(geom, targetPM);
}

Geometry bufGeom = geomToBuffer.buffer(0);

Geometry finalGeom = bufGeom;
if (! changePrecisionModel) {
// a slick way to copy the geometry with the original precision factory
finalGeom = geom.getFactory().createGeometry(bufGeom);
}
return finalGeom;
}

/**
* Duplicates a geometry to one that uses a different PrecisionModel,
* without changing any coordinate values.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,21 @@ public void testMultiPolygonGapToHole( ) {
1, "POLYGON ((9 1, 1 1, 1 4, 1 9, 9 9, 9 4, 9 1), (6 4, 4 6, 3 4, 6 4))");
}

public void testLine( ) {
checkReduce("LINESTRING(-3 6, 9 1)",
0.5, "LINESTRING (-2 6, 10 2)");
}

public void testCollapsedLine( ) {
checkReduce("LINESTRING(1 1, 1 9, 1.1 1)",
1, "LINESTRING (1 1, 1 9)");
}

public void testCollapsedNodedLine( ) {
checkReduce("LINESTRING(1 1, 3 3, 9 9, 5.1 5, 2.1 2)",
1, "MULTILINESTRING ((1 1, 2 2), (2 2, 3 3), (3 3, 5 5), (5 5, 9 9))");
}

private void checkReduce(String wkt, double scaleFactor, String wktExpected) {
Geometry geom = read(wkt);
Geometry expected = read(wktExpected);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@
import org.locationtech.jts.geom.PrecisionModel;
import org.locationtech.jts.io.WKTReader;

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


/**
* @version 1.12
*/
public class GeometryPrecisionReducerTest
extends TestCase
extends GeometryTestCase
{
private PrecisionModel pmFloat = new PrecisionModel();
private PrecisionModel pmFixed1 = new PrecisionModel(1);
Expand All @@ -49,70 +49,76 @@ public GeometryPrecisionReducerTest(String name)
public void testSquare()
throws Exception
{
Geometry g = reader.read("POLYGON (( 0 0, 0 1.4, 1.4 1.4, 1.4 0, 0 0 ))");
Geometry g2 = reader.read("POLYGON (( 0 0, 0 1, 1 1, 1 0, 0 0 ))");
Geometry gReduce = reducer.reduce(g);
assertEqualsExactAndHasSameFactory(gReduce, g2);
checkReduceSameFactory("POLYGON (( 0 0, 0 1.4, 1.4 1.4, 1.4 0, 0 0 ))",
"POLYGON (( 0 0, 0 1, 1 1, 1 0, 0 0 ))");
}
public void testTinySquareCollapse()
throws Exception
{
Geometry g = reader.read("POLYGON (( 0 0, 0 .4, .4 .4, .4 0, 0 0 ))");
Geometry g2 = reader.read("POLYGON EMPTY");
Geometry gReduce = reducer.reduce(g);
assertEqualsExactAndHasSameFactory(gReduce, g2);
checkReduceSameFactory("POLYGON (( 0 0, 0 .4, .4 .4, .4 0, 0 0 ))",
"POLYGON EMPTY");
}

public void testSquareCollapse()
throws Exception
{
Geometry g = reader.read("POLYGON (( 0 0, 0 1.4, .4 .4, .4 0, 0 0 ))");
Geometry g2 = reader.read("POLYGON EMPTY");
Geometry gReduce = reducer.reduce(g);
assertEqualsExactAndHasSameFactory(gReduce, g2);
checkReduceSameFactory("POLYGON (( 0 0, 0 1.4, .4 .4, .4 0, 0 0 ))",
"POLYGON EMPTY");
}

public void testSquareKeepCollapse()
throws Exception
{
Geometry g = reader.read("POLYGON (( 0 0, 0 1.4, .4 .4, .4 0, 0 0 ))");
Geometry g2 = reader.read("POLYGON EMPTY");
Geometry gReduce = reducerKeepCollapse.reduce(g);
assertEqualsExactAndHasSameFactory(gReduce, g2);
checkReduceSameFactory("POLYGON (( 0 0, 0 1.4, .4 .4, .4 0, 0 0 ))",
"POLYGON EMPTY");
}

public void testLine()
throws Exception
{
Geometry g = reader.read("LINESTRING ( 0 0, 0 1.4 )");
Geometry g2 = reader.read("LINESTRING (0 0, 0 1)");
Geometry gReduce = reducer.reduce(g);
assertEqualsExactAndHasSameFactory(gReduce, g2);
checkReduceExactSameFactory("LINESTRING ( 0 0, 0 1.4 )",
"LINESTRING (0 0, 0 1)");
}

public void testLineNotNoded()
throws Exception
{
checkReduceExactSameFactory("LINESTRING(1 1, 3 3, 9 9, 5.1 5, 2.1 2)",
"LINESTRING(1 1, 3 3, 9 9, 5 5, 2 2)");
}

public void testLineRemoveCollapse()
throws Exception
{
Geometry g = reader.read("LINESTRING ( 0 0, 0 .4 )");
Geometry g2 = reader.read("LINESTRING EMPTY");
Geometry gReduce = reducer.reduce(g);
assertEqualsExactAndHasSameFactory(gReduce, g2);
checkReduceExactSameFactory("LINESTRING ( 0 0, 0 .4 )",
"LINESTRING EMPTY");
}

public void testLineKeepCollapse()
throws Exception
{
Geometry g = reader.read("LINESTRING ( 0 0, 0 .4 )");
Geometry g2 = reader.read("LINESTRING ( 0 0, 0 0 )");
Geometry gReduce = reducerKeepCollapse.reduce(g);
assertEqualsExactAndHasSameFactory(gReduce, g2);
checkReduceExactSameFactory(reducerKeepCollapse,
"LINESTRING ( 0 0, 0 .4 )",
"LINESTRING ( 0 0, 0 0 )");
}


public void testPoint()
throws Exception
{
checkReduceExactSameFactory("POINT(1.1 4.9)",
"POINT(1 5)");
}

public void testMultiPoint()
throws Exception
{
checkReduceExactSameFactory("MULTIPOINT( (1.1 4.9),(1.2 4.8), (3.3 6.6))",
"MULTIPOINT((1 5), (1 5), (3 7))");
}

public void testPolgonWithCollapsedLine() throws Exception {
Geometry g = reader.read("POLYGON ((10 10, 100 100, 200 10.1, 300 10, 10 10))");
Geometry g2 = reader.read("POLYGON ((10 10, 100 100, 200 10, 10 10))");
Geometry gReduce = reducer.reduce(g);
assertEqualsExactAndHasSameFactory(gReduce, g2);
checkReduceSameFactory("POLYGON ((10 10, 100 100, 200 10.1, 300 10, 10 10))",
"POLYGON ((10 10, 100 100, 200 10, 10 10))");
}

public void testPolgonWithCollapsedLinePointwise() throws Exception {
Expand All @@ -123,10 +129,8 @@ public void testPolgonWithCollapsedLinePointwise() throws Exception {
}

public void testPolgonWithCollapsedPoint() throws Exception {
Geometry g = reader.read("POLYGON ((10 10, 100 100, 200 10.1, 300 100, 400 10, 10 10))");
Geometry g2 = reader.read("MULTIPOLYGON (((10 10, 100 100, 200 10, 10 10)), ((200 10, 300 100, 400 10, 200 10)))");
Geometry gReduce = reducer.reduce(g);
assertEqualsExactAndHasSameFactory(gReduce, g2);
checkReduceSameFactory("POLYGON ((10 10, 100 100, 200 10.1, 300 100, 400 10, 10 10))",
"MULTIPOLYGON (((10 10, 100 100, 200 10, 10 10)), ((200 10, 300 100, 400 10, 200 10)))");
}

public void testPolgonWithCollapsedPointPointwise() throws Exception {
Expand All @@ -142,5 +146,27 @@ private void assertEqualsExactAndHasSameFactory(Geometry a, Geometry b)
assertTrue(a.getFactory() == b.getFactory());
}


private void checkReduceExactSameFactory(String wkt, String wktExpected) {
checkReduceExactSameFactory(reducer, wkt, wktExpected);
}

private void checkReduceExactSameFactory(GeometryPrecisionReducer reducer,
String wkt,
String wktExpected) {
Geometry g = read(wkt);
Geometry expected = read(wktExpected);
Geometry actual = reducer.reduce(g);
assertTrue(actual.equalsExact(expected));
assertTrue(expected.getFactory() == expected.getFactory());
}

private void checkReduceSameFactory(
String wkt,
String wktExpected) {
Geometry g = read(wkt);
Geometry expected = read(wktExpected);
Geometry actual = reducer.reduce(g);
checkEqual(expected, actual);
assertTrue(expected.getFactory() == expected.getFactory());
}
}

0 comments on commit c6f2208

Please sign in to comment.