Class GeometryTransformer

  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.geom.util;
 14  
 15 import java.util.ArrayList;
 16 import java.util.List;
 17  
 18 import org.locationtech.jts.geom.Coordinate;
 19 import org.locationtech.jts.geom.CoordinateSequence;
 20 import org.locationtech.jts.geom.Geometry;
 21 import org.locationtech.jts.geom.GeometryCollection;
 22 import org.locationtech.jts.geom.GeometryFactory;
 23 import org.locationtech.jts.geom.LineString;
 24 import org.locationtech.jts.geom.LinearRing;
 25 import org.locationtech.jts.geom.MultiLineString;
 26 import org.locationtech.jts.geom.MultiPoint;
 27 import org.locationtech.jts.geom.MultiPolygon;
 28 import org.locationtech.jts.geom.Point;
 29 import org.locationtech.jts.geom.Polygon;
 30  
 31 /**
 32  * A framework for processes which transform an input {@link Geometry} into
 33  * an output {@link Geometry}, possibly changing its structure and type(s).
 34  * This class is a framework for implementing subclasses
 35  * which perform transformations on
 36  * various different Geometry subclasses.
 37  * It provides an easy way of applying specific transformations
 38  * to given geometry types, while allowing unhandled types to be simply copied.
 39  * Also, the framework ensures that if subcomponents change type
 40  * the parent geometries types change appropriately to maintain valid structure.
 41  * Subclasses will override whichever <code>transformX</code> methods
 42  * they need to to handle particular Geometry types.
 43  * <p>
 44  * A typically usage would be a transformation class that transforms <tt>Polygons</tt> into
 45  * <tt>Polygons</tt>, <tt>LineStrings</tt> or <tt>Points</tt>, depending on the geometry of the input
 46  * (For instance, a simplification operation).  
 47  * This class would likely need to override the {@link #transformMultiPolygon(MultiPolygon, Geometry)}
 48  * method to ensure that if input Polygons change type the result is a <tt>GeometryCollection</tt>,
 49  * not a <tt>MultiPolygon</tt>.
 50  * <p>
 51  * The default behaviour of this class is simply to recursively transform
 52  * each Geometry component into an identical object by deep copying down
 53  * to the level of, but not including, coordinates.
 54  * <p>
 55  * All <code>transformX</code> methods may return <code>null</code>,
 56  * to avoid creating empty or invalid geometry objects. This will be handled correctly
 57  * by the transformer.   <code>transform<i>XXX</i></code> methods should always return valid
 58  * geometry - if they cannot do this they should return <code>null</code>
 59  * (for instance, it may not be possible for a transformLineString implementation
 60  * to return at least two points - in this case, it should return <code>null</code>).
 61  * The {@link #transform(Geometry)} method itself will always
 62  * return a non-null Geometry object (but this may be empty).
 63  *
 64  * @version 1.7
 65  *
 66  * @see GeometryEditor
 67  */
 68 public class GeometryTransformer
 69 {
 70  
 71   /**
 72    * Possible extensions:
 73    * getParent() method to return immediate parent e.g. of LinearRings in Polygons
 74    */
 75  
 76   private Geometry inputGeom;
 77  
 78   protected GeometryFactory factory = null;
 79  
 80   // these could eventually be exposed to clients
 81   /**
 82    * <code>true</code> if empty geometries should not be included in the result
 83    */
 84   private boolean pruneEmptyGeometry = true;
 85  
 86   /**
 87    * <code>true</code> if a homogenous collection result
 88    * from a {@link GeometryCollection} should still
 89    * be a general GeometryCollection
 90    */
 91   private boolean preserveGeometryCollectionType = true;
 92  
 93   /**
 94    * <code>true</code> if the output from a collection argument should still be a collection
 95    */
 96   private boolean preserveCollections = false;
 97  
 98   /**
 99    * <code>true</code> if the type of the input should be preserved
100    */
101   private boolean preserveType = false;
102  
103   public GeometryTransformer() {
104   }
105  
106   /**
107    * Utility function to make input geometry available
108    *
109    * @return the input geometry
110    */
111   public Geometry getInputGeometry() { return inputGeom; }
112  
113   public final Geometry transform(Geometry inputGeom)
114   {
115     this.inputGeom = inputGeom;
116     this.factory = inputGeom.getFactory();
117  
118     if (inputGeom instanceof Point)
119       return transformPoint((Point) inputGeom, null);
120     if (inputGeom instanceof MultiPoint)
121       return transformMultiPoint((MultiPoint) inputGeom, null);
122     if (inputGeom instanceof LinearRing)
123       return transformLinearRing((LinearRing) inputGeom, null);
124     if (inputGeom instanceof LineString)
125       return transformLineString((LineString) inputGeom, null);
126     if (inputGeom instanceof MultiLineString)
127       return transformMultiLineString((MultiLineString) inputGeom, null);
128     if (inputGeom instanceof Polygon)
129       return transformPolygon((Polygon) inputGeom, null);
130     if (inputGeom instanceof MultiPolygon)
131       return transformMultiPolygon((MultiPolygon) inputGeom, null);
132     if (inputGeom instanceof GeometryCollection)
133       return transformGeometryCollection((GeometryCollection) inputGeom, null);
134  
135     throw new IllegalArgumentException("Unknown Geometry subtype: " + inputGeom.getClass().getName());
136   }
137  
138   /**
139    * Convenience method which provides standard way of
140    * creating a {@link CoordinateSequence}
141    *
142    * @param coords the coordinate array to copy
143    * @return a coordinate sequence for the array
144    */
145   protected final CoordinateSequence createCoordinateSequence(Coordinate[] coords)
146   {
147     return factory.getCoordinateSequenceFactory().create(coords);
148   }
149  
150   /**
151    * Convenience method which provides a standard way of copying {@link CoordinateSequence}s
152    * @param seq the sequence to copy
153    * @return a deep copy of the sequence
154    */
155   protected final CoordinateSequence copy(CoordinateSequence seq)
156   {
157     return seq.copy();
158   }
159  
160   /**
161    * Transforms a {@link CoordinateSequence}.
162    * This method should always return a valid coordinate list for
163    * the desired result type.  (E.g. a coordinate list for a LineString
164    * must have 0 or at least 2 points).
165    * If this is not possible, return an empty sequence -
166    * this will be pruned out.
167    *
168    * @param coords the coordinates to transform
169    * @param parent the parent geometry
170    * @return the transformed coordinates
171    */
172   protected CoordinateSequence transformCoordinates(CoordinateSequence coords, Geometry parent)
173   {
174     return copy(coords);
175   }
176  
177   protected Geometry transformPoint(Point geom, Geometry parent) {
178     return factory.createPoint(
179         transformCoordinates(geom.getCoordinateSequence(), geom));
180   }
181  
182   protected Geometry transformMultiPoint(MultiPoint geom, Geometry parent) {
183     List transGeomList = new ArrayList();
184     for (int i = 0; i < geom.getNumGeometries(); i++) {
185       Geometry transformGeom = transformPoint((Point) geom.getGeometryN(i), geom);
186       if (transformGeom == nullcontinue;
187       if (transformGeom.isEmpty()) continue;
188       transGeomList.add(transformGeom);
189     }
190     return factory.buildGeometry(transGeomList);
191   }
192  
193   /**
194    * Transforms a LinearRing.
195    * The transformation of a LinearRing may result in a coordinate sequence
196    * which does not form a structurally valid ring (i.e. a degenerate ring of 3 or fewer points).
197    * In this case a LineString is returned. 
198    * Subclasses may wish to override this method and check for this situation
199    * (e.g. a subclass may choose to eliminate degenerate linear rings)
200    * 
201    * @param geom the ring to simplify
202    * @param parent the parent geometry
203    * @return a LinearRing if the transformation resulted in a structurally valid ring
204    * @return a LineString if the transformation caused the LinearRing to collapse to 3 or fewer points
205    */
206   protected Geometry transformLinearRing(LinearRing geom, Geometry parent) {
207     CoordinateSequence seq = transformCoordinates(geom.getCoordinateSequence(), geom);
208     if (seq == null
209       return factory.createLinearRing((CoordinateSequence) null);
210     int seqSize = seq.size();
211     // ensure a valid LinearRing
212     if (seqSize > 0 && seqSize < 4 && ! preserveType)
213       return factory.createLineString(seq);
214     return factory.createLinearRing(seq);
215   }
216  
217   /**
218    * Transforms a {@link LineString} geometry.
219    *
220    * @param geom
221    * @param parent
222    * @return
223    */
224   protected Geometry transformLineString(LineString geom, Geometry parent) {
225     // should check for 1-point sequences and downgrade them to points
226     return factory.createLineString(
227         transformCoordinates(geom.getCoordinateSequence(), geom));
228   }
229  
230   protected Geometry transformMultiLineString(MultiLineString geom, Geometry parent) {
231     List transGeomList = new ArrayList();
232     for (int i = 0; i < geom.getNumGeometries(); i++) {  
233       Geometry transformGeom = transformLineString((LineString) geom.getGeometryN(i), geom);
234       if (transformGeom == nullcontinue;
235       if (transformGeom.isEmpty()) continue;
236       transGeomList.add(transformGeom);
237     }
238     return factory.buildGeometry(transGeomList);
239   }
240  
241   protected Geometry transformPolygon(Polygon geom, Geometry parent) {
242     boolean isAllValidLinearRings = true;
243     Geometry shell = transformLinearRing(geom.getExteriorRing(), geom);
244  
245     if (shell == null
246         || ! (shell instanceof LinearRing)
247         || shell.isEmpty() )
248       isAllValidLinearRings = false;
249  
250     ArrayList holes = new ArrayList();
251     for (int i = 0; i < geom.getNumInteriorRing(); i++) {
252       Geometry hole = transformLinearRing(geom.getInteriorRingN(i), geom);
253       if (hole == null || hole.isEmpty()) {
254         continue;
255       }
256       if (! (hole instanceof LinearRing))
257         isAllValidLinearRings = false;
258  
259       holes.add(hole);
260     }
261  
262     if (isAllValidLinearRings)
263       return factory.createPolygon((LinearRing) shell,
264                                    (LinearRing[]) holes.toArray(new LinearRing[] {  }));
265     else {
266       List components = new ArrayList();
267       if (shell != null) components.add(shell);
268       components.addAll(holes);
269       return factory.buildGeometry(components);
270     }
271   }
272  
273   protected Geometry transformMultiPolygon(MultiPolygon geom, Geometry parent) {
274     List transGeomList = new ArrayList();
275     for (int i = 0; i < geom.getNumGeometries(); i++) {
276       Geometry transformGeom = transformPolygon((Polygon) geom.getGeometryN(i), geom);
277       if (transformGeom == nullcontinue;
278       if (transformGeom.isEmpty()) continue;
279       transGeomList.add(transformGeom);
280     }
281     return factory.buildGeometry(transGeomList);
282   }
283  
284   protected Geometry transformGeometryCollection(GeometryCollection geom, Geometry parent) {
285     List transGeomList = new ArrayList();
286     for (int i = 0; i < geom.getNumGeometries(); i++) {
287       Geometry transformGeom = transform(geom.getGeometryN(i));
288       if (transformGeom == nullcontinue;
289       if (pruneEmptyGeometry && transformGeom.isEmpty()) continue;
290       transGeomList.add(transformGeom);
291     }
292     if (preserveGeometryCollectionType)
293       return factory.createGeometryCollection(GeometryFactory.toGeometryArray(transGeomList));
294     return factory.buildGeometry(transGeomList);
295   }
296  
297 }
298