Class VWSimplifier

  1 /*
  2  * Copyright (c) 2016 Vivid Solutions.
  3  *
  4  * All rights reserved. This program and the accompanying materials
  5  * are made available under the terms of the Eclipse Public License 2.0
  6  * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
  7  * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
  8  * and the Eclipse Distribution License is available at
  9  *
 10  * http://www.eclipse.org/org/documents/edl-v10.php.
 11  */
 12  
 13 package org.locationtech.jts.simplify;
 14  
 15 import org.locationtech.jts.geom.Coordinate;
 16 import org.locationtech.jts.geom.CoordinateSequence;
 17 import org.locationtech.jts.geom.Geometry;
 18 import org.locationtech.jts.geom.LinearRing;
 19 import org.locationtech.jts.geom.MultiPolygon;
 20 import org.locationtech.jts.geom.Polygon;
 21 import org.locationtech.jts.geom.util.GeometryTransformer;
 22  
 23 /**
 24  * Simplifies a {@link Geometry} using the Visvalingam-Whyatt area-based algorithm. 
 25  * Ensures that any polygonal geometries returned are valid. Simple lines are not
 26  * guaranteed to remain simple after simplification. All geometry types are
 27  * handled. Empty and point geometries are returned unchanged. Empty geometry
 28  * components are deleted.
 29  * <p>
 30  * The simplification tolerance is specified as a distance. 
 31  * This is converted to an area tolerance by squaring it.
 32  * <p>
 33  * Note that in general this algorithm does not preserve topology - e.g. polygons can be split,
 34  * collapse to lines or disappear holes can be created or disappear, and lines
 35  * can cross.
 36  * 
 37  * <h3>Known Bugs</h3>
 38  * <ul>
 39  * <li>Not yet optimized for performance
 40  * <li>Does not simplify the endpoint of rings
 41  * </ul>
 42  * <h3>To Do</h3>
 43  * <ul>
 44  * <li>Allow specifying desired number of vertices in the output
 45  * </ul>
 46  * 
 47  * @version 1.7
 48  */
 49 public class VWSimplifier
 50 {
 51  
 52   /**
 53    * Simplifies a geometry using a given tolerance.
 54    * 
 55    * @param geom geometry to simplify
 56    * @param distanceTolerance the tolerance to use
 57    * @return a simplified version of the geometry
 58    */
 59   public static Geometry simplify(Geometry geom, double distanceTolerance)
 60   {
 61     VWSimplifier simp = new VWSimplifier(geom);
 62     simp.setDistanceTolerance(distanceTolerance);
 63     return simp.getResultGeometry();
 64   }
 65  
 66   private Geometry inputGeom;
 67   private double distanceTolerance;
 68   private boolean isEnsureValidTopology = true;
 69  
 70   /**
 71    * Creates a simplifier for a given geometry.
 72    * 
 73    * @param inputGeom the geometry to simplify
 74    */
 75   public VWSimplifier(Geometry inputGeom)
 76   {
 77     this.inputGeom = inputGeom;
 78   }
 79  
 80   /**
 81    * Sets the distance tolerance for the simplification. All vertices in the
 82    * simplified geometry will be within this distance of the original geometry.
 83    * The tolerance value must be non-negative.
 84    * 
 85    * @param distanceTolerance
 86    *          the approximation tolerance to use
 87    */
 88   public void setDistanceTolerance(double distanceTolerance)
 89   {
 90     if (distanceTolerance < 0.0)
 91       throw new IllegalArgumentException("Tolerance must be non-negative");
 92     this.distanceTolerance = distanceTolerance;
 93   }
 94  
 95   /**
 96    * Controls whether simplified polygons will be "fixed" to have valid
 97    * topology. The caller may choose to disable this because:
 98    * <ul>
 99    * <li>valid topology is not required
100    * <li>fixing topology is a relative expensive operation
101    * <li>in some pathological cases the topology fixing operation may either
102    * fail or run for too long
103    * </ul>
104    * 
105    * The default is to fix polygon topology.
106    * 
107    * @param isEnsureValidTopology
108    */
109   public void setEnsureValid(boolean isEnsureValidTopology)
110   {
111     this.isEnsureValidTopology = isEnsureValidTopology;
112   }
113  
114   /**
115    * Gets the simplified geometry.
116    * 
117    * @return the simplified geometry
118    */
119   public Geometry getResultGeometry()
120   {
121     // empty input produces an empty result
122     if (inputGeom.isEmpty())
123       return inputGeom.copy();
124  
125     return (new VWTransformer(isEnsureValidTopology, distanceTolerance)).transform(inputGeom);
126   }
127  
128   static class VWTransformer extends GeometryTransformer
129   {
130     private boolean isEnsureValidTopology = true;
131     private double distanceTolerance;
132  
133     public VWTransformer(boolean isEnsureValidTopology, double distanceTolerance)
134     {
135       this.isEnsureValidTopology = isEnsureValidTopology;
136       this.distanceTolerance = distanceTolerance;
137     }
138  
139     protected CoordinateSequence transformCoordinates(CoordinateSequence coords, Geometry parent)
140     {
141       Coordinate[] inputPts = coords.toCoordinateArray();
142       Coordinate[] newPts = null;
143       if (inputPts.length == 0) {
144         newPts = new Coordinate[0];
145       }
146       else {
147         newPts = VWLineSimplifier.simplify(inputPts, distanceTolerance);
148       }
149       return factory.getCoordinateSequenceFactory().create(newPts);
150     }
151  
152     /**
153      * Simplifies a polygon, fixing it if required.
154      */
155     protected Geometry transformPolygon(Polygon geom, Geometry parent)
156     {
157       // empty geometries are simply removed
158       if (geom.isEmpty())
159         return null;
160       Geometry rawGeom = super.transformPolygon(geom, parent);
161       // don't try and correct if the parent is going to do this
162       if (parent instanceof MultiPolygon) {
163         return rawGeom;
164       }
165       return createValidArea(rawGeom);
166     }
167  
168     /**
169      * Simplifies a LinearRing. If the simplification results in a degenerate
170      * ring, remove the component.
171      * 
172      * @return null if the simplification results in a degenerate ring
173      */
174     protected Geometry transformLinearRing(LinearRing geom, Geometry parent)
175     {
176       boolean removeDegenerateRings = parent instanceof Polygon;
177       Geometry simpResult = super.transformLinearRing(geom, parent);
178       if (removeDegenerateRings && !(simpResult instanceof LinearRing))
179         return null;
180       ;
181       return simpResult;
182     }
183  
184     /**
185      * Simplifies a MultiPolygon, fixing it if required.
186      */
187     protected Geometry transformMultiPolygon(MultiPolygon geom, Geometry parent)
188     {
189       Geometry rawGeom = super.transformMultiPolygon(geom, parent);
190       return createValidArea(rawGeom);
191     }
192  
193     /**
194      * Creates a valid area geometry from one that possibly has bad topology
195      * (i.e. self-intersections). Since buffer can handle invalid topology, but
196      * always returns valid geometry, constructing a 0-width buffer "corrects"
197      * the topology. Note this only works for area geometries, since buffer
198      * always returns areas. This also may return empty geometries, if the input
199      * has no actual area.
200      * 
201      * @param rawAreaGeom
202      *          an area geometry possibly containing self-intersections
203      * @return a valid area geometry
204      */
205     private Geometry createValidArea(Geometry rawAreaGeom)
206     {
207       if (isEnsureValidTopology)
208         return rawAreaGeom.buffer(0.0);
209       return rawAreaGeom;
210     }
211   }
212  
213 }
214