/* * 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.vividsolutions.jts.geom.util; import com.vividsolutions.jts.geom.*; import com.vividsolutions.jts.util.Assert; import java.util.ArrayList; /** * Supports creating a new {@link Geometry} which is a modification of an existing one. * Geometry objects are intended to be treated as immutable. * This class allows you to "modify" a Geometry * by traversing it and creating a new Geometry with the same overall structure but * possibly modified components. * The following kinds of modifications can be made: * <ul> * <li>the values of the coordinates may be changed. * Changing coordinate values may make the result Geometry invalid; * this is not checked by the GeometryEditor * <li>the coordinate lists may be changed * (e.g. by adding or deleting coordinates). * The modifed coordinate lists must be consistent with their original parent component * (e.g. a LinearRing must always have at least 4 coordinates, and the first and last * coordinate must be equal) * <li>components of the original geometry may be deleted * (e.g. holes may be removed from a Polygon, or LineStrings removed from a MultiLineString). * Deletions will be propagated up the component tree appropriately. * </ul> * Note that all changes must be consistent with the original Geometry's structure * (e.g. a Polygon cannot be collapsed into a LineString). * <p> * The resulting Geometry is not checked for validity. * If validity needs to be enforced, the new Geometry's #isValid should be checked. * * @see Geometry#isValid * * @version 1.7 */ public class GeometryEditor { /** * The factory used to create the modified Geometry */ private GeometryFactory factory = null; /** * Creates a new GeometryEditor object which will create * an edited {@link Geometry} with the same {@link GeometryFactory} as the input Geometry. */ public GeometryEditor() { } /** * Creates a new GeometryEditor object which will create * the edited Geometry with the given {@link GeometryFactory} * * @param factory the GeometryFactory to create the edited Geometry with */ public GeometryEditor(GeometryFactory factory) { this.factory = factory; } /** * Edit the input {@link Geometry} with the given edit operation. * Clients will create subclasses of {@link GeometryEditorOperation} or * {@link CoordinateOperation} to perform required modifications. * * @param geometry the Geometry to edit * @param operation the edit operation to carry out * @return a new {@link Geometry} which is the result of the editing */ public Geometry edit(Geometry geometry, GeometryEditorOperation operation) { // nothing to do if (geometry == null) return null; // if client did not supply a GeometryFactory, use the one from the input Geometry if (factory == null) factory = geometry.getFactory(); if (geometry instanceof GeometryCollection) { return editGeometryCollection((GeometryCollection) geometry, operation); } if (geometry instanceof Polygon) { return editPolygon((Polygon) geometry, operation); } if (geometry instanceof Point) { return operation.edit(geometry, factory); } if (geometry instanceof LineString) { return operation.edit(geometry, factory); } Assert.shouldNeverReachHere("Unsupported Geometry class: " + geometry.getClass().getName()); return null; } private Polygon editPolygon(Polygon polygon, GeometryEditorOperation operation) { Polygon newPolygon = (Polygon) operation.edit(polygon, factory); if (newPolygon.isEmpty()) { //RemoveSelectedPlugIn relies on this behaviour. [Jon Aquino] return newPolygon; } LinearRing shell = (LinearRing) edit(newPolygon.getExteriorRing(), operation); if (shell.isEmpty()) { //RemoveSelectedPlugIn relies on this behaviour. [Jon Aquino] return factory.createPolygon(null, null); } ArrayList holes = new ArrayList(); for (int i = 0; i < newPolygon.getNumInteriorRing(); i++) { LinearRing hole = (LinearRing) edit(newPolygon.getInteriorRingN(i), operation); if (hole.isEmpty()) { continue; } holes.add(hole); } return factory.createPolygon(shell, (LinearRing[]) holes.toArray(new LinearRing[] { })); } private GeometryCollection editGeometryCollection( GeometryCollection collection, GeometryEditorOperation operation) { GeometryCollection newCollection = (GeometryCollection) operation.edit(collection, factory); ArrayList geometries = new ArrayList(); for (int i = 0; i < newCollection.getNumGeometries(); i++) { Geometry geometry = edit(newCollection.getGeometryN(i), operation); if (geometry.isEmpty()) { continue; } geometries.add(geometry); } if (newCollection.getClass() == MultiPoint.class) { return factory.createMultiPoint((Point[]) geometries.toArray( new Point[] { })); } if (newCollection.getClass() == MultiLineString.class) { return factory.createMultiLineString((LineString[]) geometries.toArray( new LineString[] { })); } if (newCollection.getClass() == MultiPolygon.class) { return factory.createMultiPolygon((Polygon[]) geometries.toArray( new Polygon[] { })); } return factory.createGeometryCollection((Geometry[]) geometries.toArray( new Geometry[] { })); } /** * A interface which specifies an edit operation for Geometries. * * @version 1.7 */ public interface GeometryEditorOperation { /** * Edits a Geometry by returning a new Geometry with a modification. * The returned Geometry might be the same as the Geometry passed in. * * @param geometry the Geometry to modify * @param factory the factory with which to construct the modified Geometry * (may be different to the factory of the input geometry) * @return a new Geometry which is a modification of the input Geometry */ Geometry edit(Geometry geometry, GeometryFactory factory); } /** * A {@link GeometryEditorOperation} which modifies the coordinate list of a {@link Geometry}. * Operates on Geometry subclasses which contains a single coordinate list. */ public abstract static class CoordinateOperation implements GeometryEditorOperation { public final Geometry edit(Geometry geometry, GeometryFactory factory) { if (geometry instanceof LinearRing) { return factory.createLinearRing(edit(geometry.getCoordinates(), geometry)); } if (geometry instanceof LineString) { return factory.createLineString(edit(geometry.getCoordinates(), geometry)); } if (geometry instanceof Point) { Coordinate[] newCoordinates = edit(geometry.getCoordinates(), geometry); return factory.createPoint((newCoordinates.length > 0) ? newCoordinates[0] : null); } return geometry; } /** * Edits the array of {@link Coordinate}s from a {@link Geometry}. * * @param coordinates the coordinate array to operate on * @param geometry the geometry containing the coordinate list * @return an edited coordinate array (which may be the same as the input) */ public abstract Coordinate[] edit(Coordinate[] coordinates, Geometry geometry); } }