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:
033: package com.vividsolutions.jump.workbench.ui.cursortool.editing;
034:
035: import java.awt.Color;
036: import java.awt.geom.NoninvertibleTransformException;
037: import java.awt.geom.Point2D;
038: import java.util.ArrayList;
039: import java.util.Collection;
040: import java.util.HashMap;
041: import java.util.Iterator;
042: import java.util.List;
043: import java.util.Map;
044: import java.util.TreeSet;
045:
046: import com.vividsolutions.jts.geom.Coordinate;
047: import com.vividsolutions.jts.geom.Geometry;
048: import com.vividsolutions.jts.geom.GeometryCollection;
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.feature.Feature;
054: import com.vividsolutions.jump.feature.FeatureUtil;
055: import com.vividsolutions.jump.geom.CoordUtil;
056: import com.vividsolutions.jump.util.CollectionUtil;
057: import com.vividsolutions.jump.workbench.model.FenceLayerFinder;
058: import com.vividsolutions.jump.workbench.model.Layer;
059: import com.vividsolutions.jump.workbench.model.Task;
060: import com.vividsolutions.jump.workbench.ui.EditTransaction;
061: import com.vividsolutions.jump.workbench.ui.GeometryEditor;
062: import com.vividsolutions.jump.workbench.ui.LayerViewPanel;
063: import com.vividsolutions.jump.workbench.ui.cursortool.Animations;
064: import com.vividsolutions.jump.workbench.ui.plugin.VerticesInFencePlugIn;
065:
066: public class SnapVerticesOp {
067:
068: public static final String INSERT_VERTICES_IF_NECESSARY_KEY = SnapVerticesOp.class
069: .getName()
070: + " - INSERT_VERTICES_IF_NECESSARY";
071:
072: private final static String NO_TARGET_VERTICES_IN_FENCE_WARNING = I18N
073: .get("ui.cursortool.editing.SnapVerticesOp.fence-contains-no-vertices-of-the-selected-feature-part-or-linestring");
074:
075: public SnapVerticesOp() {
076: }
077:
078: private Collection featuresInFence(Layer layer, Geometry fence,
079: LayerViewPanel panel) {
080: Collection featuresInFence = (Collection) panel
081: .visibleLayerToFeaturesInFenceMap(fence).get(layer);
082:
083: if (featuresInFence == null) {
084: return new ArrayList();
085: }
086:
087: return featuresInFence;
088: }
089:
090: /**
091: * @return null if the geometries have no vertices in the fence
092: */
093: public Coordinate pickTarget(Geometry targetGeometry,
094: Geometry fence, Coordinate suggestedTarget)
095: throws Exception {
096: Collection verticesInFence = VerticesInFencePlugIn
097: .verticesInFence(targetGeometry, fence, true)
098: .getCoordinates();
099:
100: if (verticesInFence.isEmpty()) {
101: return null;
102: }
103:
104: return CoordUtil.closest(verticesInFence, suggestedTarget);
105: }
106:
107: /**
108: * @param insertVerticesIfNecessary whether to insert vertices into
109: * editable features with line segments (but not vertices) inside the fence
110: */
111: public boolean execute(Geometry fence, Collection editableLayers,
112: boolean rollingBackInvalidEdits,
113: final LayerViewPanel panel, Task task,
114: Coordinate suggestedTarget, Feature targetFeature,
115: boolean insertVerticesIfNecessary) throws Exception {
116:
117: Map editableLayerToFeaturesInFenceMap = editableLayerToFeaturesInFenceMap(
118: editableLayers, fence, panel);
119:
120: Collection editableFeatures = CollectionUtil
121: .concatenate(editableLayerToFeaturesInFenceMap.values());
122:
123: if (editableFeatures.isEmpty()) {
124: panel
125: .getContext()
126: .warnUser(
127: I18N
128: .get("ui.cursortool.editing.SnapVerticesOp.fence-contains-no-features-from-editable-layers"));
129: return false;
130: }
131:
132: if (VerticesInFencePlugIn.verticesInFence(
133: targetFeature.getGeometry(), fence, true)
134: .getCoordinates().isEmpty()
135: && VerticesInFencePlugIn.verticesInFence(
136: FeatureUtil.toGeometries(editableFeatures),
137: fence, true).isEmpty()) {
138: panel.getContext().warnUser(
139: NO_TARGET_VERTICES_IN_FENCE_WARNING);
140: return false;
141: }
142:
143: Geometry targetGeometry = targetFeature.getGeometry();
144:
145: List transactions = new ArrayList();
146: for (Iterator i = editableLayers.iterator(); i.hasNext();) {
147: Layer editableLayer = (Layer) i.next();
148: Collection featuresInFence = (Collection) editableLayerToFeaturesInFenceMap
149: .get(editableLayer);
150: EditTransaction transaction = new EditTransaction(
151: featuresInFence,
152: I18N
153: .get("ui.cursortool.editing.SnapVerticesOp.snap-vertices-together"),
154: editableLayer, rollingBackInvalidEdits, false,
155: panel);
156: transactions.add(transaction);
157: if (insertVerticesIfNecessary) {
158: insertVerticesIfNecessary(transaction, suggestedTarget,
159: fence);
160: //Target geometry may have had a vertex inserted. [Jon Aquino]
161: if (featuresInFence.contains(targetFeature)) {
162: targetGeometry = transaction
163: .getGeometry(targetFeature);
164: }
165: }
166: }
167:
168: final Coordinate target = pickTarget(targetGeometry, fence,
169: suggestedTarget);
170: if (target == null) {
171: //Can get here if targetFeature is not on the editable layer. [Jon Aquino]
172: panel.getContext().warnUser(
173: NO_TARGET_VERTICES_IN_FENCE_WARNING);
174: return false;
175: }
176:
177: boolean geometryChanged = moveVertices(transactions, fence,
178: target);
179: if (!geometryChanged) {
180: return true;
181: }
182:
183: return EditTransaction.commit(transactions,
184: new EditTransaction.SuccessAction() {
185: public void run() {
186: try {
187: indicateSuccess(target, panel);
188: } catch (Throwable t) {
189: panel.getContext().warnUser(t.toString());
190: }
191: }
192: });
193: }
194:
195: private boolean moveVertices(List transactions, Geometry fence,
196: final Coordinate target) {
197: boolean geometryChanged = false;
198: for (Iterator i = transactions.iterator(); i.hasNext();) {
199: EditTransaction transaction = (EditTransaction) i.next();
200: for (int j = 0; j < transaction.size(); j++) {
201: Geometry proposedGeometry = (Geometry) transaction
202: .getGeometry(j);
203: move(VerticesInFencePlugIn.verticesInFence(
204: proposedGeometry, fence, false)
205: .getCoordinates(), target);
206: try {
207: proposedGeometry = geometryEditor
208: .removeRepeatedPoints(proposedGeometry);
209: } catch (IllegalArgumentException e) {
210: Assert
211: .isTrue(
212: e.getMessage().toLowerCase()
213: .indexOf("point") > -1
214: && e.getMessage()
215: .toLowerCase()
216: .indexOf(">") > -1,
217: "I assumed that we would get here only if too few points "
218: + "were passed into the Geometry constructor [Jon Aquino]");
219: proposedGeometry = new Point(target,
220: proposedGeometry.getPrecisionModel(),
221: proposedGeometry.getSRID());
222: }
223: transaction.setGeometry(j, proposedGeometry);
224: }
225: //Brute force check to see whether we should skip showing the animated
226: //indicator [Jon Aquino]
227: geometryChanged = geometryChanged
228: || !coordinatesEqual(transaction, fence);
229: }
230: return geometryChanged;
231: }
232:
233: private Map editableLayerToFeaturesInFenceMap(
234: Collection editableLayers, Geometry fence,
235: final LayerViewPanel panel) {
236: Map editableLayerToFeaturesInFenceMap = new HashMap();
237: for (Iterator i = editableLayers.iterator(); i.hasNext();) {
238: Layer editableLayer = (Layer) i.next();
239: Assert.isTrue(editableLayer.isEditable());
240: editableLayerToFeaturesInFenceMap.put(editableLayer,
241: featuresInFence(editableLayer, fence, panel));
242: }
243: return editableLayerToFeaturesInFenceMap;
244: }
245:
246: private boolean coordinatesEqual(EditTransaction transaction,
247: Geometry fence) {
248: for (int i = 0; i < transaction.size(); i++) {
249: Feature originalFeature = transaction.getFeature(i);
250: Geometry newGeometry = transaction.getGeometry(i);
251:
252: if (!coordinatesEqual(VerticesInFencePlugIn
253: .verticesInFence(originalFeature.getGeometry(),
254: fence, true).getCoordinates(),
255: VerticesInFencePlugIn.verticesInFence(newGeometry,
256: fence, true).getCoordinates())) {
257: return false;
258: }
259: }
260:
261: return true;
262: }
263:
264: private boolean coordinatesEqual(List a, List b) {
265: if (a.size() != b.size()) {
266: return false;
267: }
268:
269: TreeSet A = new TreeSet(a);
270: TreeSet B = new TreeSet(b);
271:
272: if (A.size() != B.size()) {
273: return false;
274: }
275:
276: Iterator Ai = A.iterator();
277: Iterator Bi = B.iterator();
278:
279: while (Ai.hasNext()) {
280: if (!Ai.next().equals(Bi.next())) {
281: return false;
282: }
283: }
284:
285: return true;
286: }
287:
288: private void indicateSuccess(Coordinate target, LayerViewPanel panel)
289: throws NoninvertibleTransformException {
290: Point2D center = panel.getViewport().toViewPoint(
291: CoordUtil.toPoint2D(target));
292: Animations.drawExpandingRing(center, false, Color.green, panel,
293: null);
294: }
295:
296: private void move(Collection verticesToMove, Coordinate target) {
297: for (Iterator i = verticesToMove.iterator(); i.hasNext();) {
298: Coordinate vertexToMove = (Coordinate) i.next();
299: vertexToMove.setCoordinate(target);
300: }
301: }
302:
303: private int insertVerticesIfNecessary(
304: final EditTransaction transaction, final Coordinate target,
305: final Geometry fence)
306: throws NoninvertibleTransformException {
307: //Trick: Wrap count in array to avoid "must be declared final" warnings. [Jon Aquino]
308: final int[] verticesInserted = new int[] { 0 };
309:
310: for (int i = 0; i < transaction.size(); i++) {
311: //GeometryEditor is being used in two ways here. GeometryEditor#edit
312: //recurses through GeometryCollection/Polygon elements (if any).
313: //GeometryEditor#insertVertex does the vertex insertion on each
314: //Geometry or GeometryCollection/Polygon element. [Jon Aquino]
315: transaction.setGeometry(i, geometryEditor.edit(transaction
316: .getGeometry(i),
317: new GeometryEditor.GeometryEditorOperation() {
318: public Geometry edit(Geometry geometry) {
319: if (geometry instanceof Polygon) {
320: //Wait for the individual LinearRings to come in. [Jon Aquino]
321: return geometry;
322: }
323: if (geometry instanceof GeometryCollection) {
324: return geometry;
325: }
326: if (!fence.intersects(geometry)) {
327: //A part of the feature that doesn't lie inside the fence. [Jon Aquino]
328: return geometry;
329: }
330: if (!VerticesInFencePlugIn.verticesInFence(
331: geometry, fence, true)
332: .getCoordinates().isEmpty()) {
333: return geometry;
334: }
335: verticesInserted[0] = verticesInserted[0] + 1;
336: //Important to pass in the fence, so that vertex isn't inserted into
337: //a segment that doesn't intersect the fence. [Jon Aquino]
338: Geometry newGeometry = geometryEditor
339: .insertVertex(geometry, target,
340: fence);
341: Assert.isTrue(newGeometry != null);
342: return newGeometry;
343: }
344: }));
345:
346: }
347:
348: return verticesInserted[0];
349: }
350:
351: private GeometryEditor geometryEditor = new GeometryEditor();
352: }
|