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.cursortool.editing;
033:
034: import com.vividsolutions.jts.geom.Coordinate;
035: import com.vividsolutions.jts.geom.Envelope;
036:
037: import com.vividsolutions.jump.I18N;
038: import com.vividsolutions.jump.feature.Feature;
039: import com.vividsolutions.jump.geom.EnvelopeUtil;
040: import com.vividsolutions.jump.workbench.model.FenceLayerFinder;
041: import com.vividsolutions.jump.workbench.model.Layer;
042: import com.vividsolutions.jump.workbench.plugin.EnableCheck;
043: import com.vividsolutions.jump.workbench.plugin.EnableCheckFactory;
044: import com.vividsolutions.jump.workbench.ui.cursortool.AbstractCursorTool;
045: import com.vividsolutions.jump.workbench.ui.cursortool.SpecifyFeaturesTool;
046: import com.vividsolutions.jump.workbench.ui.images.IconLoader;
047: import com.vividsolutions.jump.workbench.ui.plugin.VerticesInFencePlugIn;
048:
049: import java.awt.BasicStroke;
050: import java.awt.Color;
051: import java.awt.Cursor;
052: import java.awt.Shape;
053: import java.awt.geom.NoninvertibleTransformException;
054:
055: import java.util.ArrayList;
056: import java.util.Collection;
057: import java.util.Iterator;
058: import java.util.Map;
059:
060: import javax.swing.Icon;
061: import javax.swing.JComponent;
062:
063: public class SnapVerticesTool extends SpecifyFeaturesTool {
064: private EnableCheckFactory checkFactory;
065:
066: public SnapVerticesTool(EnableCheckFactory checkFactory) {
067: this .checkFactory = checkFactory;
068: setColor(Color.green.darker());
069: setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
070: BasicStroke.JOIN_BEVEL, 0));
071:
072: //The cursor is 18 pixels long and wide
073: setViewClickBuffer(8);
074: }
075:
076: protected void gestureFinished() throws Exception {
077: reportNothingToUndoYet();
078:
079: if (!check(checkFactory
080: .createAtLeastNLayersMustBeEditableCheck(1))) {
081: return;
082: }
083:
084: Coordinate suggestedTarget = EnvelopeUtil
085: .centre(getBoxInModelCoordinates());
086: final Feature targetFeature = targetFeature(suggestedTarget,
087: getBoxInModelCoordinates());
088:
089: if (!check(new EnableCheck() {
090: public String check(JComponent component) {
091: return targetFeature == null ? I18N
092: .get("ui.cursortool.editing.SnapVerticesTool.no-vertices-or-edges-here")
093: : null;
094: }
095: })) {
096: return;
097: }
098:
099: snapVertices(getPanel().getLayerManager().getEditableLayers(),
100: suggestedTarget, targetFeature);
101: }
102:
103: protected void snapVertices(Collection editableLayers,
104: Coordinate suggestedTarget, final Feature targetFeature)
105: throws Exception, NoninvertibleTransformException {
106: new SnapVerticesOp()
107: .execute(
108: EnvelopeUtil
109: .toGeometry(getBoxInModelCoordinates()),
110: editableLayers,
111: isRollingBackInvalidEdits(),
112: getPanel(),
113: getTaskFrame().getTask(),
114: suggestedTarget,
115: targetFeature,
116: getWorkbench()
117: .getBlackboard()
118: .get(
119: SnapVerticesOp.INSERT_VERTICES_IF_NECESSARY_KEY,
120: true));
121: }
122:
123: private Feature targetFeature(Coordinate suggestedTarget,
124: Envelope fence) throws NoninvertibleTransformException {
125: Feature targetFeature = targetFeature(suggestedTarget, fence,
126: false);
127:
128: if (targetFeature == null) {
129: targetFeature = targetFeature(suggestedTarget, fence, true);
130: }
131:
132: return targetFeature;
133: }
134:
135: private Feature targetFeature(Coordinate suggestedTarget,
136: Envelope fence, boolean fromEditableLayers)
137: throws NoninvertibleTransformException {
138: ArrayList candidateFeatures = new ArrayList();
139: Map layerToSpecifiedFeaturesMap = layerToSpecifiedFeaturesMap();
140:
141: for (Iterator i = layerToSpecifiedFeaturesMap.keySet()
142: .iterator(); i.hasNext();) {
143: Layer layer = (Layer) i.next();
144:
145: if (layer.isEditable() != fromEditableLayers) {
146: continue;
147: }
148:
149: if (layer.getName().equals(FenceLayerFinder.LAYER_NAME)) {
150: continue;
151: }
152:
153: candidateFeatures
154: .addAll((Collection) layerToSpecifiedFeaturesMap
155: .get(layer));
156: }
157:
158: Feature targetFeature = null;
159: double distanceToTargetVertices = -1;
160:
161: for (Iterator i = candidateFeatures.iterator(); i.hasNext();) {
162: Feature candidate = (Feature) i.next();
163: double distanceToCandidateVertices = distanceToVertices(
164: suggestedTarget, candidate, fence);
165:
166: if (distanceToCandidateVertices == -1) {
167: continue;
168: }
169:
170: if (targetFeature == null
171: || distanceToCandidateVertices < distanceToTargetVertices) {
172: targetFeature = candidate;
173: distanceToTargetVertices = distanceToCandidateVertices;
174: }
175: }
176:
177: return targetFeature;
178:
179: //If anyone ever modifies this method to look for the feature with the
180: //closest *segment* rather than the closest vertex (and I think that this
181: //would be a good modification because it would snap lines to vertices
182: //rather than vice versa), remember to handle points and multipoints,
183: //which have no segments. Also, remember to count only the portion of
184: //the line segment lying inside the fence (actually I don't account for this
185: //in GeometryEditor#insertVertex (used by SnapVerticesOp); this omission
186: //could lead to some snaps occurring outside the fence in rare cases). [Jon Aquino]
187: }
188:
189: /**
190: * @return -1 if the feature has no vertices in the Envelope
191: */
192: private double distanceToVertices(Coordinate referenceCoordinate,
193: Feature feature, Envelope vertexFilter) {
194: double distanceToVertices = -1;
195:
196: for (Iterator i = VerticesInFencePlugIn.verticesInFence(
197: feature.getGeometry(),
198: EnvelopeUtil.toGeometry(vertexFilter), true)
199: .getCoordinates().iterator(); i.hasNext();) {
200: Coordinate vertex = (Coordinate) i.next();
201: double distanceToVertex = vertex
202: .distance(referenceCoordinate);
203:
204: if (distanceToVertices == -1
205: || distanceToVertex < distanceToVertices) {
206: distanceToVertices = distanceToVertex;
207: }
208: }
209:
210: return distanceToVertices;
211: }
212:
213: public Icon getIcon() {
214: return IconLoader.icon("QuickSnap.gif");
215: }
216:
217: public Cursor getCursor() {
218: return AbstractCursorTool.createCursor(IconLoader.icon(
219: "QuickSnapCursor.gif").getImage());
220: }
221:
222: protected Envelope getBoxInModelCoordinates()
223: throws NoninvertibleTransformException {
224: return EnvelopeUtil.expand(new Envelope(getModelSource(),
225: getModelDestination()), modelClickBuffer());
226: }
227:
228: protected Shape getShape() throws Exception {
229: return getPanel().getViewport().toViewRectangle(
230: getBoxInModelCoordinates());
231: }
232: }
|