001: /*
002: * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI
003: * for visualizing and manipulating spatial features with geometry and attributes.
004: *
005: * Copyright (C) 2003 Vivid Solutions
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: *
021: * For more information, contact:
022: *
023: * Vivid Solutions
024: * Suite #1A
025: * 2328 Government Street
026: * Victoria BC V8T 5G5
027: * Canada
028: *
029: * (250)385-6040
030: * www.vividsolutions.com
031: */
032: package com.vividsolutions.jump.workbench.ui;
033:
034: import java.util.ArrayList;
035: import java.util.Arrays;
036: import java.util.Collection;
037: import java.util.Iterator;
038: import java.util.List;
039: import com.vividsolutions.jts.geom.Coordinate;
040: import com.vividsolutions.jts.geom.Geometry;
041: import com.vividsolutions.jts.geom.GeometryCollection;
042: import com.vividsolutions.jts.geom.GeometryFactory;
043: import com.vividsolutions.jts.geom.LineSegment;
044: import com.vividsolutions.jts.geom.LineString;
045: import com.vividsolutions.jts.geom.LinearRing;
046: import com.vividsolutions.jts.geom.MultiLineString;
047: import com.vividsolutions.jts.geom.MultiPoint;
048: import com.vividsolutions.jts.geom.MultiPolygon;
049: import com.vividsolutions.jts.geom.Point;
050: import com.vividsolutions.jts.geom.Polygon;
051: import com.vividsolutions.jts.util.Assert;
052: import com.vividsolutions.jump.I18N;
053: import com.vividsolutions.jump.util.CoordinateArrays;
054:
055: /**
056: * Geometry objects are unmodifiable; this class allows you to "modify" a Geometry
057: * in a sense -- the modified Geometry is returned as a new Geometry.
058: * The new Geometry's #isValid should be checked.
059: */
060: public class GeometryEditor {
061: private GeometryFactory factory = new GeometryFactory();
062:
063: public GeometryEditor() {
064: }
065:
066: public Geometry edit(Geometry geometry,
067: GeometryEditorOperation operation) {
068: if (geometry instanceof GeometryCollection) {
069: return editGeometryCollection(
070: (GeometryCollection) geometry, operation);
071: }
072: if (geometry instanceof Polygon) {
073: return editPolygon((Polygon) geometry, operation);
074: }
075: if (geometry instanceof Point) {
076: return operation.edit(geometry);
077: }
078: if (geometry instanceof LineString) {
079: return operation.edit(geometry);
080: }
081: Assert
082: .shouldNeverReachHere(I18N
083: .get("ui.GeometryEditor.unsupported-geometry-classes-should-be-caught-in-the-GeometryEditorOperation"));
084: return null;
085: }
086:
087: private Polygon editPolygon(Polygon polygon,
088: GeometryEditorOperation operation) {
089: Polygon newPolygon = (Polygon) operation.edit(polygon);
090: if (newPolygon.isEmpty()) {
091: //RemoveSelectedPlugIn relies on this behaviour. [Jon Aquino]
092: return newPolygon;
093: }
094: LinearRing shell = (LinearRing) edit(newPolygon
095: .getExteriorRing(), operation);
096: if (shell.isEmpty()) {
097: //RemoveSelectedPlugIn relies on this behaviour. [Jon Aquino]
098: return factory.createPolygon(null, null);
099: }
100: ArrayList holes = new ArrayList();
101: for (int i = 0; i < newPolygon.getNumInteriorRing(); i++) {
102: LinearRing hole = (LinearRing) edit(newPolygon
103: .getInteriorRingN(i), operation);
104: if (hole.isEmpty()) {
105: continue;
106: }
107: holes.add(hole);
108: }
109: return factory.createPolygon(shell, (LinearRing[]) holes
110: .toArray(new LinearRing[] {}));
111: }
112:
113: private GeometryCollection editGeometryCollection(
114: GeometryCollection collection,
115: GeometryEditorOperation operation) {
116: GeometryCollection newCollection = (GeometryCollection) operation
117: .edit(collection);
118: ArrayList geometries = new ArrayList();
119: for (int i = 0; i < newCollection.getNumGeometries(); i++) {
120: Geometry geometry = edit(newCollection.getGeometryN(i),
121: operation);
122: if (geometry.isEmpty()) {
123: continue;
124: }
125: geometries.add(geometry);
126: }
127: if (newCollection.getClass() == MultiPoint.class) {
128: return factory.createMultiPoint((Point[]) geometries
129: .toArray(new Point[] {}));
130: }
131: if (newCollection.getClass() == MultiLineString.class) {
132: return factory
133: .createMultiLineString((LineString[]) geometries
134: .toArray(new LineString[] {}));
135: }
136: if (newCollection.getClass() == MultiPolygon.class) {
137: return factory.createMultiPolygon((Polygon[]) geometries
138: .toArray(new Polygon[] {}));
139: }
140: return factory.createGeometryCollection((Geometry[]) geometries
141: .toArray(new Geometry[] {}));
142: }
143:
144: /**
145: * The input and output Geometries may share some Coordinate arrays.
146: */
147: public Geometry removeRepeatedPoints(Geometry geometry) {
148: if (geometry.isEmpty()) {
149: return geometry;
150: }
151: return edit(geometry, new CoordinateOperation() {
152: public Coordinate[] edit(Coordinate[] coordinates,
153: boolean linearRing) {
154: //May return the same Coordinate array. [Jon Aquino]
155: return com.vividsolutions.jts.geom.CoordinateArrays
156: .removeRepeatedPoints(coordinates);
157: }
158: });
159: }
160:
161: /**
162: * @return null if parent == itemToRemove
163: */
164: public Geometry remove(Geometry g, final Geometry itemToRemove) {
165: return edit(g, new GeometryEditorOperation() {
166: public Geometry edit(Geometry geometry) {
167: if (geometry == itemToRemove) {
168: return createNullGeometry(geometry.getClass());
169: }
170: return geometry;
171: }
172: });
173: }
174:
175: private Geometry createNullGeometry(Class geometryClass) {
176: if (geometryClass == MultiPolygon.class) {
177: return factory.createMultiPolygon(null);
178: }
179: if (geometryClass == MultiLineString.class) {
180: return factory.createMultiLineString(null);
181: }
182: if (geometryClass == MultiPoint.class) {
183: return factory.createMultiPoint((Coordinate[]) null);
184: }
185: if (geometryClass == GeometryCollection.class) {
186: return factory.createGeometryCollection(null);
187: }
188: if (geometryClass == Polygon.class) {
189: return factory.createPolygon(null, null);
190: }
191: if (geometryClass == LinearRing.class) {
192: return factory.createLinearRing((Coordinate[]) null);
193: }
194: if (geometryClass == LineString.class) {
195: return factory.createLineString((Coordinate[]) null);
196: }
197: if (geometryClass == Point.class) {
198: return factory.createPoint((Coordinate) null);
199: }
200: Assert.shouldNeverReachHere();
201: return null;
202: }
203:
204: /**
205: * The vertex will be inserted at the point closest to the target.
206: */
207: public Geometry insertVertex(Geometry geometry, Coordinate target,
208: Geometry ignoreSegmentsOutside) {
209: LineString closestSegment = null;
210: Point targetPoint = factory.createPoint(target);
211: for (Iterator i = CoordinateArrays.toCoordinateArrays(geometry,
212: false).iterator(); i.hasNext();) {
213: Coordinate[] coordinates = (Coordinate[]) i.next();
214: if (coordinates.length < 2) {
215: continue;
216: }
217: for (int j = 1; j < coordinates.length; j++) { //1
218: LineString candidate = factory
219: .createLineString(new Coordinate[] {
220: coordinates[j], coordinates[j - 1] });
221: if (!candidate.intersects(ignoreSegmentsOutside)) {
222: continue;
223: }
224: if (closestSegment == null) {
225: closestSegment = candidate;
226: } else if (candidate.distance(targetPoint) < closestSegment
227: .distance(targetPoint)) {
228: closestSegment = candidate;
229: }
230: }
231: }
232: if (closestSegment == null) {
233: return null;
234: }
235: return insertVertex(geometry, closestSegment.getCoordinateN(0),
236: closestSegment.getCoordinateN(1), new LineSegment(
237: closestSegment.getCoordinateN(0),
238: closestSegment.getCoordinateN(1))
239: .closestPoint(target));
240: }
241:
242: /**
243: * Inserts v on the line segment with endpoints equal to existing1 and existing2
244: */
245: public Geometry insertVertex(Geometry geometry,
246: final Coordinate existing1, final Coordinate existing2,
247: final Coordinate v) {
248: if (geometry.isEmpty()) {
249: return geometry;
250: }
251: return edit(geometry, new CoordinateOperation() {
252: private boolean vertexInserted = false;
253:
254: public Coordinate[] edit(Coordinate[] coordinates,
255: boolean linearRing) {
256: if (vertexInserted) {
257: return coordinates;
258: }
259: for (int i = 1; i < coordinates.length; i++) { //1
260: if ((coordinates[i - 1].equals(existing1) && coordinates[i]
261: .equals(existing2))
262: || (coordinates[i - 1].equals(existing2) && coordinates[i]
263: .equals(existing1))) {
264: Coordinate[] newCoordinates = new Coordinate[coordinates.length + 1];
265: System.arraycopy(coordinates, 0,
266: newCoordinates, 0, i);
267: newCoordinates[i] = v;
268: System.arraycopy(coordinates, i,
269: newCoordinates, i + 1,
270: coordinates.length - i);
271: vertexInserted = true;
272: return newCoordinates;
273: }
274: }
275: return coordinates;
276: }
277: });
278: }
279:
280: /**
281: * Deletes the given vertices (matched using ==, not #equals).
282: */
283: public Geometry deleteVertices(Geometry geometry,
284: final Collection vertices) {
285: return edit(geometry, new CoordinateOperation() {
286: public Coordinate[] edit(Coordinate[] coordinates,
287: boolean linearRing) {
288: List newCoordinates = new ArrayList(Arrays
289: .asList(coordinates));
290: boolean firstCoordinateDeleted = false;
291: int j = -1;
292: for (Iterator i = newCoordinates.iterator(); i
293: .hasNext();) {
294: Coordinate c = (Coordinate) i.next();
295: j++;
296: if (containsReference(vertices, c)) {
297: i.remove();
298: if (j == 0) {
299: firstCoordinateDeleted = true;
300: }
301: }
302: }
303: if (linearRing && firstCoordinateDeleted) {
304: newCoordinates.remove(newCoordinates.size() - 1);
305: }
306: if (linearRing
307: && firstCoordinateDeleted
308: && !newCoordinates.isEmpty()
309: && !newCoordinates.get(0).equals(
310: newCoordinates.get(newCoordinates
311: .size() - 1))) {
312: newCoordinates.add(new Coordinate(
313: (Coordinate) newCoordinates.get(0)));
314: }
315: return (Coordinate[]) newCoordinates
316: .toArray(new Coordinate[] {});
317: }
318: });
319: }
320:
321: public boolean containsReference(Collection collection, Object o) {
322: //Inefficient. [Jon Aquino]
323: for (Iterator i = collection.iterator(); i.hasNext();) {
324: Object item = (Object) i.next();
325: if (item == o) {
326: return true;
327: }
328: }
329: return false;
330: }
331:
332: public interface GeometryEditorOperation {
333: /**
334: * "Modifies" a Geometry by returning a new Geometry with a modification.
335: * The returned Geometry might be the same as the Geometry passed in.
336: */
337: public Geometry edit(Geometry geometry);
338: }
339:
340: private Coordinate[] atLeastNCoordinatesOrNothing(int n,
341: Coordinate[] c) {
342: return c.length >= n ? c : new Coordinate[] {};
343: }
344:
345: private abstract class CoordinateOperation implements
346: GeometryEditorOperation {
347: public Geometry edit(Geometry geometry) {
348: if (geometry instanceof LinearRing) {
349: return factory
350: .createLinearRing(atLeastNCoordinatesOrNothing(
351: 4,
352: edit(geometry.getCoordinates(), true)));
353: }
354: if (geometry instanceof LineString) {
355: return factory
356: .createLineString(atLeastNCoordinatesOrNothing(
357: 2, edit(geometry.getCoordinates(),
358: false)));
359: }
360: if (geometry instanceof Point) {
361: Coordinate[] newCoordinates = edit(geometry
362: .getCoordinates(), false);
363: Assert.isTrue(newCoordinates.length < 2);
364: return factory
365: .createPoint((newCoordinates.length > 0) ? newCoordinates[0]
366: : null);
367: }
368: return geometry;
369: }
370:
371: public abstract Coordinate[] edit(Coordinate[] coordinates,
372: boolean linearRing);
373: }
374: }
|