Class GeometryEditor

  1  
  2 /*
  3  * Copyright (c) 2016 Vivid Solutions.
  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.geom.util;
 14  
 15  
 16 import java.util.ArrayList;
 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 import org.locationtech.jts.util.Assert;
 31  
 32  
 33 /**
 34  * A class which supports creating new {@link Geometry}s 
 35  * which are modifications of existing ones,
 36  * maintaining the same type structure.
 37  * Geometry objects are intended to be treated as immutable.
 38  * This class "modifies" Geometrys
 39  * by traversing them, applying a user-defined
 40  * {@link GeometryEditorOperation}, {@link CoordinateSequenceOperation} or {@link CoordinateOperation}  
 41  * and creating new Geometrys with the same structure but
 42  * (possibly) modified components.
 43  * <p>
 44  * Examples of the kinds of modifications which can be made are:
 45  * <ul>
 46  * <li>the values of the coordinates may be changed.
 47  *     The editor does not check whether changing coordinate values makes the result Geometry invalid
 48  * <li>the coordinate lists may be changed
 49  *     (e.g. by adding, deleting or modifying coordinates).
 50  *     The modified coordinate lists must be consistent with their original parent component
 51  *     (e.g. a <tt>LinearRing</tt> must always have at least 4 coordinates, and the first and last
 52  *     coordinate must be equal)
 53  * <li>components of the original geometry may be deleted
 54  *    (e.g. holes may be removed from a Polygon, or LineStrings removed from a MultiLineString).
 55  *     Deletions will be propagated up the component tree appropriately.
 56  * </ul>
 57  * All changes must be consistent with the original Geometry's structure
 58  * (e.g. a <tt>Polygon</tt> cannot be collapsed into a <tt>LineString</tt>).
 59  * If changing the structure is required, use a {@link GeometryTransformer}.
 60  * <p>
 61  * This class supports creating an edited Geometry
 62  * using a different <code>GeometryFactory</code> via the {@link #GeometryEditor(GeometryFactory)}
 63  * constructor.  
 64  * Examples of situations where this is required is if the geometry is 
 65  * transformed to a new SRID and/or a new PrecisionModel.
 66  * <p>
 67  * <b>Usage Notes</b>
 68  * <ul>
 69  * <li>The resulting Geometry is not checked for validity.
 70  * If validity needs to be enforced, the new Geometry's 
 71  * {@link Geometry#isValid} method should be called.
 72  * <li>By default the UserData of the input geometry is not copied to the result.
 73  * </ul>
 74  * 
 75  * @see GeometryTransformer
 76  * @see Geometry#isValid
 77  *
 78  * @version 1.7
 79  */
 80 public class GeometryEditor
 81 {
 82   /**
 83    * The factory used to create the modified Geometry.
 84    * If <tt>null</tt> the GeometryFactory of the input is used.
 85    */
 86   private GeometryFactory factory = null;
 87   private boolean isUserDataCopied = false;
 88  
 89   /**
 90    * Creates a new GeometryEditor object which will create
 91    * edited {@link Geometry}s with the same {@link GeometryFactory} as the input Geometry.
 92    */
 93   public GeometryEditor()
 94   {
 95   }
 96  
 97   /**
 98    * Creates a new GeometryEditor object which will create
 99    * edited {@link Geometry}s with the given {@link GeometryFactory}.
100    *
101    * @param factory the GeometryFactory to create  edited Geometrys with
102    */
103   public GeometryEditor(GeometryFactory factory)
104   {
105     this.factory = factory;
106   }
107  
108   /**
109    * Sets whether the User Data is copied to the edit result.
110    * Only the object reference is copied.
111    * 
112    * @param isUserDataCopied true if the input user data should be copied.
113    */
114   public void setCopyUserData(boolean isUserDataCopied)
115   {
116     this.isUserDataCopied = isUserDataCopied;
117   }
118   
119   /**
120    * Edit the input {@link Geometry} with the given edit operation.
121    * Clients can create subclasses of {@link GeometryEditorOperation} or
122    * {@link CoordinateOperation} to perform required modifications.
123    *
124    * @param geometry the Geometry to edit
125    * @param operation the edit operation to carry out
126    * @return a new {@link Geometry} which is the result of the editing (which may be empty)
127    */
128   public Geometry edit(Geometry geometry, GeometryEditorOperation operation)
129   {
130     // nothing to do
131     if (geometry == nullreturn null;
132     
133     Geometry result = editInternal(geometry, operation);
134     if (isUserDataCopied) {
135       result.setUserData(geometry.getUserData());
136     }
137     return result;
138   }
139   
140   private Geometry editInternal(Geometry geometry, GeometryEditorOperation operation)
141   {
142     // if client did not supply a GeometryFactory, use the one from the input Geometry
143     if (factory == null)
144       factory = geometry.getFactory();
145  
146     if (geometry instanceof GeometryCollection) {
147       return editGeometryCollection((GeometryCollection) geometry,
148                                     operation);
149     }
150  
151     if (geometry instanceof Polygon) {
152       return editPolygon((Polygon) geometry, operation);
153     }
154  
155     if (geometry instanceof Point) {
156       return operation.edit(geometry, factory);
157     }
158  
159     if (geometry instanceof LineString) {
160       return operation.edit(geometry, factory);
161     }
162  
163     Assert.shouldNeverReachHere("Unsupported Geometry class: " + geometry.getClass().getName());
164     return null;
165   }
166  
167   private Polygon editPolygon(Polygon polygon,
168                               GeometryEditorOperation operation) {
169     Polygon newPolygon = (Polygon) operation.edit(polygon, factory);
170     // create one if needed
171     if (newPolygon == null)
172       newPolygon = factory.createPolygon();
173     if (newPolygon.isEmpty()) {
174       //RemoveSelectedPlugIn relies on this behaviour. [Jon Aquino]
175       return newPolygon;
176     }
177  
178     LinearRing shell = (LinearRing) edit(newPolygon.getExteriorRing(), operation);
179     if (shell == null || shell.isEmpty()) {
180       //RemoveSelectedPlugIn relies on this behaviour. [Jon Aquino]
181       return factory.createPolygon();
182     }
183  
184     ArrayList holes = new ArrayList();
185     for (int i = 0; i < newPolygon.getNumInteriorRing(); i++) {
186       LinearRing hole = (LinearRing) edit(newPolygon.getInteriorRingN(i), operation);
187       if (hole == null || hole.isEmpty()) {
188         continue;
189       }
190       holes.add(hole);
191     }
192  
193     return factory.createPolygon(shell,
194                                  (LinearRing[]) holes.toArray(new LinearRing[] {  }));
195   }
196  
197   private GeometryCollection editGeometryCollection(
198       GeometryCollection collection, GeometryEditorOperation operation) {
199     // first edit the entire collection
200     // MD - not sure why this is done - could just check original collection?
201     GeometryCollection collectionForType = (GeometryCollection) operation.edit(collection,
202         factory);
203     
204     // edit the component geometries
205     ArrayList geometries = new ArrayList();
206     for (int i = 0; i < collectionForType.getNumGeometries(); i++) {
207       Geometry geometry = edit(collectionForType.getGeometryN(i), operation);
208       if (geometry == null || geometry.isEmpty()) {
209         continue;
210       }
211       geometries.add(geometry);
212     }
213  
214     if (collectionForType.getClass() == MultiPoint.class) {
215       return factory.createMultiPoint((Point[]) geometries.toArray(
216             new Point[] {  }));
217     }
218     if (collectionForType.getClass() == MultiLineString.class) {
219       return factory.createMultiLineString((LineString[]) geometries.toArray(
220             new LineString[] {  }));
221     }
222     if (collectionForType.getClass() == MultiPolygon.class) {
223       return factory.createMultiPolygon((Polygon[]) geometries.toArray(
224             new Polygon[] {  }));
225     }
226     return factory.createGeometryCollection((Geometry[]) geometries.toArray(
227           new Geometry[] {  }));
228   }
229  
230   /**
231    * A interface which specifies an edit operation for Geometries.
232    *
233    * @version 1.7
234    */
235   public interface GeometryEditorOperation
236   {
237     /**
238      * Edits a Geometry by returning a new Geometry with a modification.
239      * The returned geometry may be:
240      * <ul>
241      * <li>the input geometry itself.
242      * The returned Geometry might be the same as the Geometry passed in.
243      * <li><code>null</code> if the geometry is to be deleted.
244      * </ul>
245      *
246      * @param geometry the Geometry to modify
247      * @param factory the factory with which to construct the modified Geometry
248      * (may be different to the factory of the input geometry)
249      * @return a new Geometry which is a modification of the input Geometry
250      * @return null if the Geometry is to be deleted completely
251      */
252     Geometry edit(Geometry geometry, GeometryFactory factory);
253   }
254  
255   /**
256    * A GeometryEditorOperation which does not modify
257    * the input geometry.
258    * This can be used for simple changes of 
259    * GeometryFactory (including PrecisionModel and SRID).
260    * 
261    * @author mbdavis
262    *
263    */
264   public static class NoOpGeometryOperation
265   implements GeometryEditorOperation
266   {
267       public Geometry edit(Geometry geometry, GeometryFactory factory)
268       {
269           return geometry;
270       }
271   }
272   
273   /**
274    * A {@link GeometryEditorOperation} which edits the coordinate list of a {@link Geometry}.
275    * Operates on Geometry subclasses which contains a single coordinate list.
276    */
277   public abstract static class CoordinateOperation
278       implements GeometryEditorOperation
279   {
280     public final Geometry edit(Geometry geometry, GeometryFactory factory) {
281       if (geometry instanceof LinearRing) {
282         return factory.createLinearRing(edit(geometry.getCoordinates(),
283             geometry));
284       }
285  
286       if (geometry instanceof LineString) {
287         return factory.createLineString(edit(geometry.getCoordinates(),
288             geometry));
289       }
290  
291       if (geometry instanceof Point) {
292         Coordinate[] newCoordinates = edit(geometry.getCoordinates(),
293             geometry);
294  
295         return factory.createPoint((newCoordinates.length > 0)
296                                    ? newCoordinates[0] : null);
297       }
298  
299       return geometry;
300     }
301  
302     /**
303      * Edits the array of {@link Coordinate}s from a {@link Geometry}.
304      * <p>
305      * If it is desired to preserve the immutability of Geometrys,
306      * if the coordinates are changed a new array should be created
307      * and returned.
308      *
309      * @param coordinates the coordinate array to operate on
310      * @param geometry the geometry containing the coordinate list
311      * @return an edited coordinate array (which may be the same as the input)
312      */
313     public abstract Coordinate[] edit(Coordinate[] coordinates,
314                                       Geometry geometry);
315   }
316   
317   /**
318    * A {@link GeometryEditorOperation} which edits the {@link CoordinateSequence}
319    * of a {@link Geometry}.
320    * Operates on Geometry subclasses which contains a single coordinate list.
321    */
322   public abstract static class CoordinateSequenceOperation
323       implements GeometryEditorOperation
324   {
325     public final Geometry edit(Geometry geometry, GeometryFactory factory) {
326       if (geometry instanceof LinearRing) {
327         return factory.createLinearRing(edit(
328             ((LinearRing)geometry).getCoordinateSequence(),
329             geometry));
330       }
331  
332       if (geometry instanceof LineString) {
333         return factory.createLineString(edit(
334             ((LineString)geometry).getCoordinateSequence(),
335             geometry));
336       }
337  
338       if (geometry instanceof Point) {
339         return factory.createPoint(edit(
340             ((Point)geometry).getCoordinateSequence(),
341             geometry));
342       }
343  
344       return geometry;
345     }
346  
347     /**
348      * Edits a {@link CoordinateSequence} from a {@link Geometry}.
349      *
350      * @param coordSeq the coordinate array to operate on
351      * @param geometry the geometry containing the coordinate list
352      * @return an edited coordinate sequence (which may be the same as the input)
353      */
354     public abstract CoordinateSequence edit(CoordinateSequence coordSeq,
355                                       Geometry geometry);
356   }
357 }
358