GeometryPrecisionReducer.java
/*
* Copyright (c) 2016 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.precision;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.PrecisionModel;
import org.locationtech.jts.geom.util.GeometryEditor;
/**
* Reduces the precision of a {@link Geometry}
* according to the supplied {@link PrecisionModel},
* ensuring that the result is valid (unless specified otherwise).
* <p>
* By default the geometry precision model is not changed.
* This can be overridden by using {@link #setChangePrecisionModel(boolean)}.
*
* <h4>Topological Precision Reduction</h4>
*
* The default mode of operation ensures the reduced result is topologically valid
* (i.e. {@link Geometry#isValid()} is true).
* To ensure this polygonal geometry is reduced in a topologically valid fashion
* (technically, by using snap-rounding).
* Note that this may change polygonal geometry structure
* (e.g. two polygons separated by a distance below the specified precision
* will be merged into a single polygon).
* Duplicate vertices are removed.
* This mode is invoked by the static method {@link #reduce(Geometry, PrecisionModel)}.
* <p>
* Normally, collapsed linear components (e.g. lines collapsing to a point)
* are not included in the result.
* This behavior can be changed
* by setting {@link #setRemoveCollapsedComponents(boolean)} to <code>false</code>,
* or by using the static method {@link #reduceKeepCollapsed(Geometry, PrecisionModel)}.
* <p>
* In general input must be valid geometry, or an {@link IllegalArgumentException}
* will be thrown. However if the invalidity is "mild" or very small then it
* may be eliminated by precision reduction.
*
*
* <h4>Pointwise Precision Reduction</h4>
*
* Alternatively, geometry can be reduced pointwise by using {@link #setPointwise(boolean)}.
* Linear and point geometry are always reduced pointwise (i.e. without further change to
* topology or structure), since this does not change validity.
* Invalid inputs are allowed.
* Duplicate vertices are preserved.
* Collapsed components are always included in the result.
* The result geometry may be invalid.
* <p>
* This mode is invoked by the static method {@link #reducePointwise(Geometry, PrecisionModel)}.
*
* @version 1.12
*/
public class GeometryPrecisionReducer
{
/**
* Reduces precision of a geometry, ensuring output geometry is valid.
* Collapsed linear and polygonal components are removed.
* Duplicate vertices are removed.
* The geometry precision model is not changed.
* <p>
* Invalid input geometry may cause an error,
* unless the invalidity is below the scale of the precision reduction.
*
* @param g the geometry to reduce
* @param precModel the precision model to use
* @return the reduced geometry
* @throws IllegalArgumentException if the reduction fails due to invalid input geometry
*/
public static Geometry reduce(Geometry g, PrecisionModel precModel)
{
GeometryPrecisionReducer reducer = new GeometryPrecisionReducer(precModel);
return reducer.reduce(g);
}
/**
* Reduces precision of a geometry, ensuring output polygonal geometry is valid,
* and preserving collapsed linear elements.
* Duplicate vertices are removed.
* The geometry precision model is not changed.
* <p>
* Invalid input geometry may cause an error,
* unless the invalidity is below the scale of the precision reduction.
*
* @param g the geometry to reduce
* @param precModel the precision model to use
* @return the reduced geometry
* @throws IllegalArgumentException if the reduction fails due to invalid input geometry
*/
public static Geometry reduceKeepCollapsed(Geometry geom, PrecisionModel pm) {
GeometryPrecisionReducer reducer = new GeometryPrecisionReducer(pm);
reducer.setRemoveCollapsedComponents(false);
return reducer.reduce(geom);
}
/**
* Reduce precision of a geometry in a pointwise way.
* All input geometry elements are preserved in the output,
* including invalid polygons and collapsed polygons and linestrings.
* The output may not be valid, due to collapse or self-intersection.
* Duplicate vertices are not removed.
* The geometry precision model is not changed.
* <p>
* Invalid input geometry is allowed.
*
* @param g the geometry to reduce
* @param precModel the precision model to use
* @return the reduced geometry
*/
public static Geometry reducePointwise(Geometry g, PrecisionModel precModel)
{
GeometryPrecisionReducer reducer = new GeometryPrecisionReducer(precModel);
reducer.setPointwise(true);
return reducer.reduce(g);
}
private PrecisionModel targetPM;
private boolean removeCollapsed = true;
private boolean changePrecisionModel = false;
private boolean isPointwise = false;
public GeometryPrecisionReducer(PrecisionModel pm)
{
targetPM = pm;
}
/**
* Sets whether the reduction will result in collapsed components
* being removed completely, or simply being collapsed to an (invalid)
* Geometry of the same type.
* The default is to remove collapsed components.
*
* @param removeCollapsed if <code>true</code> collapsed components will be removed
*/
public void setRemoveCollapsedComponents(boolean removeCollapsed)
{
this.removeCollapsed = removeCollapsed;
}
/**
* Sets whether the {@link PrecisionModel} of the new reduced Geometry
* will be changed to be the {@link PrecisionModel} supplied to
* specify the precision reduction.
* <p>
* The default is to <b>not</b> change the precision model
*
* @param changePrecisionModel if <code>true</code> the precision model of the created Geometry will be the
* the precisionModel supplied in the constructor.
*/
public void setChangePrecisionModel(boolean changePrecisionModel)
{
this.changePrecisionModel = changePrecisionModel;
}
/**
* Sets whether the precision reduction will be done
* in pointwise fashion only.
* Pointwise precision reduction reduces the precision
* of the individual coordinates only, but does
* not attempt to recreate valid topology.
* This is only relevant for geometries containing polygonal components.
*
* @param isPointwise if reduction should be done pointwise only
*/
public void setPointwise(boolean isPointwise)
{
this.isPointwise = isPointwise;
}
/**
* Reduces the precision of a geometry,
* according to the specified strategy of this reducer.
*
* @param geom the geometry to reduce
* @return the precision-reduced geometry
* @throws IllegalArgumentException if the reduction fails due to invalid input geometry is invalid
*/
public Geometry reduce(Geometry geom)
{
Geometry reduced;
if (isPointwise) {
reduced = PointwisePrecisionReducerTransformer.reduce(geom, targetPM);
}
else {
reduced = PrecisionReducerTransformer.reduce(geom, targetPM, removeCollapsed);
}
// TODO: incorporate this in the Transformer above
if (changePrecisionModel) {
return changePM(reduced, targetPM);
}
return reduced;
}
/**
* Duplicates a geometry to one that uses a different PrecisionModel,
* without changing any coordinate values.
*
* @param geom the geometry to duplicate
* @param newPM the precision model to use
* @return the geometry value with a new precision model
*/
private Geometry changePM(Geometry geom, PrecisionModel newPM)
{
GeometryEditor geomEditor = createEditor(geom.getFactory(), newPM);
// this operation changes the PM for the entire geometry tree
return geomEditor.edit(geom, new GeometryEditor.NoOpGeometryOperation());
}
private GeometryEditor createEditor(GeometryFactory geomFactory, PrecisionModel newPM)
{
// no need to change if precision model is the same
if (geomFactory.getPrecisionModel() == newPM)
return new GeometryEditor();
// otherwise create a geometry editor which changes PrecisionModel
GeometryFactory newFactory = createFactory(geomFactory, newPM);
GeometryEditor geomEdit = new GeometryEditor(newFactory);
return geomEdit;
}
private GeometryFactory createFactory(GeometryFactory inputFactory, PrecisionModel pm)
{
GeometryFactory newFactory
= new GeometryFactory(pm,
inputFactory.getSRID(),
inputFactory.getCoordinateSequenceFactory());
return newFactory;
}
}