/*
* The JTS Topology Suite is a collection of Java classes that
* implement the fundamental operations required to validate a given
* geo-spatial data set to a known topological specification.
*
* Copyright (C) 2001 Vivid Solutions
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* For more information, contact:
*
* Vivid Solutions
* Suite #1A
* 2328 Government Street
* Victoria BC V8T 5G5
* Canada
*
* (250)385-6040
* www.vividsolutions.com
*/
package com.revolsys.geometry.simplify;
import java.util.HashMap;
import java.util.Map;
import com.revolsys.geometry.model.Geometry;
import com.revolsys.geometry.model.LineString;
import com.revolsys.geometry.model.Polygonal;
import com.revolsys.geometry.model.impl.LineStringDouble;
import com.revolsys.geometry.model.util.GeometryTransformer;
/**
* Simplifies a geometry and ensures that
* the result is a valid geometry having the
* same dimension and number of components as the input,
* and with the components having the same topological
* relationship.
* <p>
* If the input is a {@link Polygonal} geometry
* <ul>
* <li>The result has the same number of shells and holes as the input,
* with the same topological structure
* <li>The result rings touch at <b>no more</b> than the number of touching points in the input
* (although they may touch at fewer points).
* The key implication of this statement is that if the
* input is topologically valid, so is the simplified output.
* </ul>
* For linear geometries, if the input does not contain
* any intersecting line segments, this property
* will be preserved in the output.
* <p>
* For all geometry types, the result will contain
* enough vertices to ensure validity. For polygons
* and closed linear geometries, the result will have at
* least 4 vertices; for open linestrings the result
* will have at least 2 vertices.
* <p>
* All geometry types are handled.
* Empty and point geometries are returned unchanged.
* Empty geometry components are deleted.
* <p>
* The simplification uses a maximum-distance difference algorithm
* similar to the Douglas-Peucker algorithm.
*
* <h3>KNOWN BUGS</h3>
* <ul>
* <li>If a small hole is very near an edge, it is possible for the edge to be moved by
* a relatively large tolerance value and end up with the hole outside the result shell.
* Similarly, it is possible for a small polygon component to end up inside
* a nearby larger polygon.
* A workaround is to test for this situation in post-processing and remove
* any invalid holes or polygons.
* </ul>
*
* @author Martin Davis
* @see DouglasPeuckerSimplifier
*
*/
public class TopologyPreservingSimplifier {
class LineStringTransformer extends GeometryTransformer {
@Override
protected LineString transformCoordinates(final LineString coords, final Geometry parent) {
if (coords.getVertexCount() == 0) {
return null;
}
// for linear components (including rings), simplify the linestring
if (parent instanceof LineString) {
final TaggedLineString taggedLine = TopologyPreservingSimplifier.this.linestringMap
.get(parent);
return new LineStringDouble(taggedLine.getResultCoordinates());
}
// for anything else (e.g. points) just copy the coordinates
return super.transformCoordinates(coords, parent);
}
}
public static Geometry simplify(final Geometry geom, final double distanceTolerance) {
final TopologyPreservingSimplifier tss = new TopologyPreservingSimplifier(geom);
tss.setDistanceTolerance(distanceTolerance);
return tss.getResultGeometry();
}
private final Geometry geometry;
private final TaggedLinesSimplifier lineSimplifier = new TaggedLinesSimplifier();
private Map<LineString, TaggedLineString> linestringMap;
public TopologyPreservingSimplifier(final Geometry geometry) {
this.geometry = geometry;
}
public Geometry getResultGeometry() {
// empty input produces an empty result
if (this.geometry.isEmpty()) {
return this.geometry.clone();
} else {
this.linestringMap = new HashMap<>();
for (final LineString line : this.geometry.getGeometryComponents(LineString.class)) {
// skip empty geometries
if (!line.isEmpty()) {
final int minSize = line.isClosed() ? 4 : 2;
final TaggedLineString taggedLine = new TaggedLineString(line, minSize);
this.linestringMap.put(line, taggedLine);
}
}
this.lineSimplifier.simplify(this.linestringMap.values());
final Geometry result = new LineStringTransformer().transform(this.geometry);
return result;
}
}
/**
* Sets the distance tolerance for the simplification.
* All vertices in the simplified geometry will be within this
* distance of the original geometry.
* The tolerance value must be non-negative. A tolerance value
* of zero is effectively a no-op.
*
* @param distanceTolerance the approximation tolerance to use
*/
public void setDistanceTolerance(final double distanceTolerance) {
if (distanceTolerance < 0.0) {
throw new IllegalArgumentException("Tolerance must be non-negative");
}
this.lineSimplifier.setDistanceTolerance(distanceTolerance);
}
}