/* * 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 com.revolsys.geometry.model.Geometry; import com.revolsys.geometry.model.LineString; import com.revolsys.geometry.model.LinearRing; import com.revolsys.geometry.model.MultiPolygon; import com.revolsys.geometry.model.Point; import com.revolsys.geometry.model.Polygon; import com.revolsys.geometry.model.Polygonal; import com.revolsys.geometry.model.impl.LineStringDouble; import com.revolsys.geometry.model.util.GeometryTransformer; /** * Simplifies a {@link Geometry} using the Visvalingam-Whyatt area-based algorithm. * Ensures that any polygonal geometries returned are valid. Simple lines are not * guaranteed to remain simple after simplification. All geometry types are * handled. Empty and point geometries are returned unchanged. Empty geometry * components are deleted. * <p> * The simplification tolerance is specified as a distance. * This is converted to an area tolerance by squaring it. * <p> * Note that in general this algorithm does not preserve topology - e.g. polygons can be split, * collapse to lines or disappear holes can be created or disappear, and lines * can cross. * * <h3>Known Bugs</h3> * <ul> * <li>Not yet optimized for performance * <li>Does not simplify the endpoint of rings * </ul> * <h3>To Do</h3> * <ul> * <li>Allow specifying desired number of vertices in the output * </ul> * * @version 1.7 */ public class VWSimplifier { class VWTransformer extends GeometryTransformer { private boolean isEnsureValidTopology = true; public VWTransformer(final boolean isEnsureValidTopology) { this.isEnsureValidTopology = isEnsureValidTopology; } /** * Creates a valid area geometry from one that possibly has bad topology * (i.e. self-intersections). Since buffer can handle invalid topology, but * always returns valid geometry, constructing a 0-width buffer "corrects" * the topology. Note this only works for area geometries, since buffer * always returns areas. This also may return empty geometries, if the input * has no actual area. * * @param rawAreaGeom * an area geometry possibly containing self-intersections * @return a valid area geometry */ private Geometry newValidArea(final Geometry rawAreaGeom) { if (this.isEnsureValidTopology) { return rawAreaGeom.buffer(0.0); } return rawAreaGeom; } @Override protected LineString transformCoordinates(final LineString coords, final Geometry parent) { if (coords.getVertexCount() == 0) { return coords; } else { final Point[] newPts = VWLineSimplifier.simplify(coords, VWSimplifier.this.distanceTolerance); return new LineStringDouble(newPts); } } /** * Simplifies a LinearRing. If the simplification results in a degenerate * ring, remove the component. * * @return null if the simplification results in a degenerate ring */ @Override protected Geometry transformLinearRing(final LinearRing geom, final Geometry parent) { final boolean removeDegenerateRings = parent instanceof Polygon; final Geometry simpResult = super.transformLinearRing(geom, parent); if (removeDegenerateRings && !(simpResult instanceof LinearRing)) { return null; } ; return simpResult; } /** * Simplifies a MultiPolygon, fixing it if required. */ @Override protected Geometry transformMultiPolygon(final Polygonal geom, final Geometry parent) { final Geometry rawGeom = super.transformMultiPolygon(geom, parent); return newValidArea(rawGeom); } /** * Simplifies a polygon, fixing it if required. */ @Override protected Geometry transformPolygon(final Polygon geom, final Geometry parent) { // empty geometries are simply removed if (geom.isEmpty()) { return null; } final Geometry rawGeom = super.transformPolygon(geom, parent); // don't try and correct if the parent is going to do this if (parent instanceof MultiPolygon) { return rawGeom; } return newValidArea(rawGeom); } } /** * Simplifies a geometry using a given tolerance. * * @param geom geometry to simplify * @param distanceTolerance the tolerance to use * @return a simplified version of the geometry */ public static Geometry simplify(final Geometry geom, final double distanceTolerance) { final VWSimplifier simp = new VWSimplifier(geom); simp.setDistanceTolerance(distanceTolerance); return simp.getResultGeometry(); } private double distanceTolerance; private final Geometry inputGeom; private boolean isEnsureValidTopology = true; /** * Creates a simplifier for a given geometry. * * @param inputGeom the geometry to simplify */ public VWSimplifier(final Geometry inputGeom) { this.inputGeom = inputGeom; } /** * Gets the simplified geometry. * * @return the simplified geometry */ public Geometry getResultGeometry() { // empty input produces an empty result if (this.inputGeom.isEmpty()) { return this.inputGeom.clone(); } return new VWTransformer(this.isEnsureValidTopology).transform(this.inputGeom); } /** * 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. * * @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.distanceTolerance = distanceTolerance; } /** * Controls whether simplified polygons will be "fixed" to have valid * topology. The caller may choose to disable this because: * <ul> * <li>valid topology is not required * <li>fixing topology is a relative expensive operation * <li>in some pathological cases the topology fixing operation may either * fail or run for too long * </ul> * * The default is to fix polygon topology. * * @param isEnsureValidTopology */ public void setEnsureValid(final boolean isEnsureValidTopology) { this.isEnsureValidTopology = isEnsureValidTopology; } }