Class GeometryPrecisionReducer

  1  
  2 /*
  3  * Copyright (c) 2016 Martin Davis.
  4  *
  5  * All rights reserved. This program and the accompanying materials
  6  * are made available under the terms of the Eclipse Public License 2.0
  7  * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
  8  * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
  9  * and the Eclipse Distribution License is available at
 10  *
 11  * http://www.eclipse.org/org/documents/edl-v10.php.
 12  */
 13 package org.locationtech.jts.precision;
 14  
 15 import org.locationtech.jts.geom.Geometry;
 16 import org.locationtech.jts.geom.GeometryFactory;
 17 import org.locationtech.jts.geom.Polygonal;
 18 import org.locationtech.jts.geom.PrecisionModel;
 19 import org.locationtech.jts.geom.util.GeometryEditor;
 20  
 21 /**
 22  * Reduces the precision of a {@link Geometry}
 23  * according to the supplied {@link PrecisionModel},
 24  * ensuring that the result is topologically valid.
 25  *
 26  * @version 1.12
 27  */
 28 public class GeometryPrecisionReducer
 29 {
 30     /**
 31      * Convenience method for doing precision reduction 
 32    * on a single geometry,
 33      * with collapses removed 
 34    * and keeping the geometry precision model the same,
 35    * and preserving polygonal topology.
 36      * 
 37      * @param g the geometry to reduce
 38      * @param precModel the precision model to use
 39      * @return the reduced geometry
 40      */
 41     public static Geometry reduce(Geometry g, PrecisionModel precModel)
 42     {
 43         GeometryPrecisionReducer reducer = new GeometryPrecisionReducer(precModel);
 44         return reducer.reduce(g);
 45     }
 46     
 47     /**
 48      * Convenience method for doing pointwise precision reduction 
 49    * on a single geometry,
 50      * with collapses removed 
 51    * and keeping the geometry precision model the same,
 52    * but NOT preserving valid polygonal topology.
 53      * 
 54      * @param g the geometry to reduce
 55      * @param precModel the precision model to use
 56      * @return the reduced geometry
 57      */
 58     public static Geometry reducePointwise(Geometry g, PrecisionModel precModel)
 59     {
 60         GeometryPrecisionReducer reducer = new GeometryPrecisionReducer(precModel);
 61         reducer.setPointwise(true);
 62         return reducer.reduce(g);
 63     }
 64     
 65   private PrecisionModel targetPM;
 66   private boolean removeCollapsed = true;
 67   private boolean changePrecisionModel = false;
 68   private boolean isPointwise = false;
 69  
 70   public GeometryPrecisionReducer(PrecisionModel pm)
 71   {
 72     targetPM = pm;
 73   }
 74  
 75   /**
 76    * Sets whether the reduction will result in collapsed components
 77    * being removed completely, or simply being collapsed to an (invalid)
 78    * Geometry of the same type.
 79    * The default is to remove collapsed components.
 80    *
 81    * @param removeCollapsed if <code>true</code> collapsed components will be removed
 82    */
 83   public void setRemoveCollapsedComponents(boolean removeCollapsed)
 84   {
 85     this.removeCollapsed = removeCollapsed;
 86   }
 87  
 88   /**
 89    * Sets whether the {@link PrecisionModel} of the new reduced Geometry
 90    * will be changed to be the {@link PrecisionModel} supplied to
 91    * specify the precision reduction.
 92    * <p>  
 93    * The default is to <b>not</b> change the precision model
 94    *
 95    * @param changePrecisionModel if <code>true</code> the precision model of the created Geometry will be the
 96    * the precisionModel supplied in the constructor.
 97    */
 98   public void setChangePrecisionModel(boolean changePrecisionModel)
 99   {
100     this.changePrecisionModel = changePrecisionModel;
101   }
102  
103   /**
104    * Sets whether the precision reduction will be done 
105    * in pointwise fashion only.  
106    * Pointwise precision reduction reduces the precision
107    * of the individual coordinates only, but does
108    * not attempt to recreate valid topology.
109    * This is only relevant for geometries containing polygonal components.
110    * 
111    * @param isPointwise if reduction should be done pointwise only
112    */
113   public void setPointwise(boolean isPointwise)
114   {
115     this.isPointwise = isPointwise;
116   }
117  
118   public Geometry reduce(Geometry geom)
119   {
120     Geometry reducePW = reducePointwise(geom);
121     if (isPointwise)
122         return reducePW;
123     
124     //TODO: handle GeometryCollections containing polys
125     if (! (reducePW instanceof Polygonal))
126         return reducePW;
127     
128     // Geometry is polygonal - test if topology needs to be fixed
129     if (reducePW.isValid()) return reducePW;
130     
131     // hack to fix topology.  
132     // TODO: implement snap-rounding and use that.
133     return fixPolygonalTopology(reducePW);
134   }
135  
136   private Geometry reducePointwise(Geometry geom)
137   {
138     GeometryEditor geomEdit;
139     if (changePrecisionModel) {
140         GeometryFactory newFactory = createFactory(geom.getFactory(), targetPM);
141       geomEdit = new GeometryEditor(newFactory);
142     }
143     else
144       // don't change geometry factory
145       geomEdit = new GeometryEditor();
146  
147     /**
148      * For polygonal geometries, collapses are always removed, in order
149      * to produce correct topology
150      */
151     boolean finalRemoveCollapsed = removeCollapsed;
152     if (geom.getDimension() >= 2)
153         finalRemoveCollapsed = true;
154     
155     Geometry reduceGeom = geomEdit.edit(geom, 
156             new PrecisionReducerCoordinateOperation(targetPM, finalRemoveCollapsed));
157     
158     return reduceGeom;
159   }
160   
161   private Geometry fixPolygonalTopology(Geometry geom)
162   {
163       /**
164        * If precision model was *not* changed, need to flip
165        * geometry to targetPM, buffer in that model, then flip back
166        */
167       Geometry geomToBuffer = geom;
168       if (! changePrecisionModel) {
169           geomToBuffer = changePM(geom, targetPM);
170       }
171       
172       Geometry bufGeom = geomToBuffer.buffer(0);
173       
174       Geometry finalGeom = bufGeom;
175       if (! changePrecisionModel) {
176         // a slick way to copy the geometry with the original precision factory
177           finalGeom = geom.getFactory().createGeometry(bufGeom);
178       }
179       return finalGeom;
180   }
181   
182   /**
183    * Duplicates a geometry to one that uses a different PrecisionModel,
184    * without changing any coordinate values.
185    * 
186    * @param geom the geometry to duplicate
187    * @param newPM the precision model to use
188    * @return the geometry value with a new precision model
189    */
190   private Geometry changePM(Geometry geom, PrecisionModel newPM)
191   {
192       GeometryEditor geomEditor = createEditor(geom.getFactory(), newPM);
193       // this operation changes the PM for the entire geometry tree
194       return geomEditor.edit(geom, new GeometryEditor.NoOpGeometryOperation());
195   }
196   
197   private GeometryEditor createEditor(GeometryFactory geomFactory, PrecisionModel newPM)
198   {
199     // no need to change if precision model is the same
200       if (geomFactory.getPrecisionModel() == newPM)
201           return new GeometryEditor();
202       // otherwise create a geometry editor which changes PrecisionModel
203       GeometryFactory newFactory = createFactory(geomFactory, newPM);
204       GeometryEditor geomEdit = new GeometryEditor(newFactory);
205     return geomEdit;
206   }
207   
208   private GeometryFactory createFactory(GeometryFactory inputFactory, PrecisionModel pm)
209   {
210     GeometryFactory newFactory 
211       = new GeometryFactory(pm, 
212               inputFactory.getSRID(),
213               inputFactory.getCoordinateSequenceFactory());
214     return newFactory;
215   }
216   
217 }
218