001: /*
002: * The JTS Topology Suite is a collection of Java classes that
003: * implement the fundamental operations required to validate a given
004: * geo-spatial data set to a known topological specification.
005: *
006: * Copyright (C) 2001 Vivid Solutions
007: *
008: * This library is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU Lesser General Public
010: * License as published by the Free Software Foundation; either
011: * version 2.1 of the License, or (at your option) any later version.
012: *
013: * This library is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
016: * Lesser General Public License for more details.
017: *
018: * You should have received a copy of the GNU Lesser General Public
019: * License along with this library; if not, write to the Free Software
020: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
021: *
022: * For more information, contact:
023: *
024: * Vivid Solutions
025: * Suite #1A
026: * 2328 Government Street
027: * Victoria BC V8T 5G5
028: * Canada
029: *
030: * (250)385-6040
031: * www.vividsolutions.com
032: */
033: package com.vividsolutions.jts.geom.util;
034:
035: import com.vividsolutions.jts.geom.*;
036: import com.vividsolutions.jts.util.Assert;
037:
038: import java.util.ArrayList;
039:
040: /**
041: * Supports creating a new {@link Geometry} which is a modification of an existing one.
042: * Geometry objects are intended to be treated as immutable.
043: * This class allows you to "modify" a Geometry
044: * by traversing it and creating a new Geometry with the same overall structure but
045: * possibly modified components.
046: * The following kinds of modifications can be made:
047: * <ul>
048: * <li>the values of the coordinates may be changed.
049: * Changing coordinate values may make the result Geometry invalid;
050: * this is not checked by the GeometryEditor
051: * <li>the coordinate lists may be changed
052: * (e.g. by adding or deleting coordinates).
053: * The modifed coordinate lists must be consistent with their original parent component
054: * (e.g. a LinearRing must always have at least 4 coordinates, and the first and last
055: * coordinate must be equal)
056: * <li>components of the original geometry may be deleted
057: * (e.g. holes may be removed from a Polygon, or LineStrings removed from a MultiLineString).
058: * Deletions will be propagated up the component tree appropriately.
059: * </ul>
060: * Note that all changes must be consistent with the original Geometry's structure
061: * (e.g. a Polygon cannot be collapsed into a LineString).
062: * <p>
063: * The resulting Geometry is not checked for validity.
064: * If validity needs to be enforced, the new Geometry's #isValid should be checked.
065: *
066: * @see Geometry#isValid
067: *
068: * @version 1.7
069: */
070: public class GeometryEditor {
071: /**
072: * The factory used to create the modified Geometry
073: */
074: private GeometryFactory factory = null;
075:
076: /**
077: * Creates a new GeometryEditor object which will create
078: * an edited {@link Geometry} with the same {@link GeometryFactory} as the input Geometry.
079: */
080: public GeometryEditor() {
081: }
082:
083: /**
084: * Creates a new GeometryEditor object which will create
085: * the edited Geometry with the given {@link GeometryFactory}
086: *
087: * @param factory the GeometryFactory to create the edited Geometry with
088: */
089: public GeometryEditor(GeometryFactory factory) {
090: this .factory = factory;
091: }
092:
093: /**
094: * Edit the input {@link Geometry} with the given edit operation.
095: * Clients will create subclasses of {@link GeometryEditorOperation} or
096: * {@link CoordinateOperation} to perform required modifications.
097: *
098: * @param geometry the Geometry to edit
099: * @param operation the edit operation to carry out
100: * @return a new {@link Geometry} which is the result of the editing
101: */
102: public Geometry edit(Geometry geometry,
103: GeometryEditorOperation operation) {
104: // nothing to do
105: if (geometry == null)
106: return null;
107:
108: // if client did not supply a GeometryFactory, use the one from the input Geometry
109: if (factory == null)
110: factory = geometry.getFactory();
111:
112: if (geometry instanceof GeometryCollection) {
113: return editGeometryCollection(
114: (GeometryCollection) geometry, operation);
115: }
116:
117: if (geometry instanceof Polygon) {
118: return editPolygon((Polygon) geometry, operation);
119: }
120:
121: if (geometry instanceof Point) {
122: return operation.edit(geometry, factory);
123: }
124:
125: if (geometry instanceof LineString) {
126: return operation.edit(geometry, factory);
127: }
128:
129: Assert.shouldNeverReachHere("Unsupported Geometry class: "
130: + geometry.getClass().getName());
131: return null;
132: }
133:
134: private Polygon editPolygon(Polygon polygon,
135: GeometryEditorOperation operation) {
136: Polygon newPolygon = (Polygon) operation.edit(polygon, factory);
137:
138: if (newPolygon.isEmpty()) {
139: //RemoveSelectedPlugIn relies on this behaviour. [Jon Aquino]
140: return newPolygon;
141: }
142:
143: LinearRing shell = (LinearRing) edit(newPolygon
144: .getExteriorRing(), operation);
145:
146: if (shell.isEmpty()) {
147: //RemoveSelectedPlugIn relies on this behaviour. [Jon Aquino]
148: return factory.createPolygon(null, null);
149: }
150:
151: ArrayList holes = new ArrayList();
152:
153: for (int i = 0; i < newPolygon.getNumInteriorRing(); i++) {
154: LinearRing hole = (LinearRing) edit(newPolygon
155: .getInteriorRingN(i), operation);
156:
157: if (hole.isEmpty()) {
158: continue;
159: }
160:
161: holes.add(hole);
162: }
163:
164: return factory.createPolygon(shell, (LinearRing[]) holes
165: .toArray(new LinearRing[] {}));
166: }
167:
168: private GeometryCollection editGeometryCollection(
169: GeometryCollection collection,
170: GeometryEditorOperation operation) {
171: GeometryCollection newCollection = (GeometryCollection) operation
172: .edit(collection, factory);
173: ArrayList geometries = new ArrayList();
174:
175: for (int i = 0; i < newCollection.getNumGeometries(); i++) {
176: Geometry geometry = edit(newCollection.getGeometryN(i),
177: operation);
178:
179: if (geometry.isEmpty()) {
180: continue;
181: }
182:
183: geometries.add(geometry);
184: }
185:
186: if (newCollection.getClass() == MultiPoint.class) {
187: return factory.createMultiPoint((Point[]) geometries
188: .toArray(new Point[] {}));
189: }
190:
191: if (newCollection.getClass() == MultiLineString.class) {
192: return factory
193: .createMultiLineString((LineString[]) geometries
194: .toArray(new LineString[] {}));
195: }
196:
197: if (newCollection.getClass() == MultiPolygon.class) {
198: return factory.createMultiPolygon((Polygon[]) geometries
199: .toArray(new Polygon[] {}));
200: }
201:
202: return factory.createGeometryCollection((Geometry[]) geometries
203: .toArray(new Geometry[] {}));
204: }
205:
206: /**
207: * A interface which specifies an edit operation for Geometries.
208: *
209: * @version 1.7
210: */
211: public interface GeometryEditorOperation {
212: /**
213: * Edits a Geometry by returning a new Geometry with a modification.
214: * The returned Geometry might be the same as the Geometry passed in.
215: *
216: * @param geometry the Geometry to modify
217: * @param factory the factory with which to construct the modified Geometry
218: * (may be different to the factory of the input geometry)
219: * @return a new Geometry which is a modification of the input Geometry
220: */
221: Geometry edit(Geometry geometry, GeometryFactory factory);
222: }
223:
224: /**
225: * A {@link GeometryEditorOperation} which modifies the coordinate list of a {@link Geometry}.
226: * Operates on Geometry subclasses which contains a single coordinate list.
227: */
228: public abstract static class CoordinateOperation implements
229: GeometryEditorOperation {
230: public final Geometry edit(Geometry geometry,
231: GeometryFactory factory) {
232: if (geometry instanceof LinearRing) {
233: return factory.createLinearRing(edit(geometry
234: .getCoordinates(), geometry));
235: }
236:
237: if (geometry instanceof LineString) {
238: return factory.createLineString(edit(geometry
239: .getCoordinates(), geometry));
240: }
241:
242: if (geometry instanceof Point) {
243: Coordinate[] newCoordinates = edit(geometry
244: .getCoordinates(), geometry);
245:
246: return factory
247: .createPoint((newCoordinates.length > 0) ? newCoordinates[0]
248: : null);
249: }
250:
251: return geometry;
252: }
253:
254: /**
255: * Edits the array of {@link Coordinate}s from a {@link Geometry}.
256: *
257: * @param coordinates the coordinate array to operate on
258: * @param geometry the geometry containing the coordinate list
259: * @return an edited coordinate array (which may be the same as the input)
260: */
261: public abstract Coordinate[] edit(Coordinate[] coordinates,
262: Geometry geometry);
263: }
264: }
|