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 java.awt.Color;
035: import java.awt.Cursor;
036: import java.awt.geom.NoninvertibleTransformException;
037: import java.util.*;
038: import javax.swing.Icon;
039: import com.vividsolutions.jts.geom.*;
040: import com.vividsolutions.jts.util.Assert;
041: import com.vividsolutions.jump.I18N;
042: import com.vividsolutions.jump.feature.Feature;
043: import com.vividsolutions.jump.geom.CoordUtil;
044: import com.vividsolutions.jump.util.CoordinateArrays;
045: import com.vividsolutions.jump.workbench.model.Layer;
046: import com.vividsolutions.jump.workbench.plugin.EnableCheckFactory;
047: import com.vividsolutions.jump.workbench.ui.EditTransaction;
048: import com.vividsolutions.jump.workbench.ui.GeometryEditor;
049: import com.vividsolutions.jump.workbench.ui.cursortool.Animations;
050: import com.vividsolutions.jump.workbench.ui.cursortool.NClickTool;
051: import com.vividsolutions.jump.workbench.ui.images.IconLoader;
052:
053: public class InsertVertexTool extends NClickTool {
054: private static final int PIXEL_RANGE = 5;
055: private EnableCheckFactory checkFactory;
056:
057: public InsertVertexTool(EnableCheckFactory checkFactory) {
058: super (1);
059: this .checkFactory = checkFactory;
060: }
061:
062: private double modelRange() {
063: return PIXEL_RANGE / getPanel().getViewport().getScale();
064: }
065:
066: private Collection featuresInRange(Coordinate modelClickCoordinate,
067: Layer layer) {
068: Point modelClickPoint = new GeometryFactory()
069: .createPoint(modelClickCoordinate);
070: Collection featuresWithSelectedItems = getPanel()
071: .getSelectionManager().getFeaturesWithSelectedItems(
072: layer);
073: if (featuresWithSelectedItems.isEmpty()) {
074: return new ArrayList();
075: }
076: ArrayList featuresInRange = new ArrayList();
077: for (Iterator i = featuresWithSelectedItems.iterator(); i
078: .hasNext();) {
079: Feature candidate = (Feature) i.next();
080: if (modelClickPoint.distance(candidate.getGeometry()) <= modelRange()) {
081: featuresInRange.add(candidate);
082: }
083: }
084: return featuresInRange;
085: }
086:
087: private Coordinate modelClickCoordinate()
088: throws NoninvertibleTransformException {
089: return (Coordinate) getCoordinates().get(0);
090: }
091:
092: private LineSegment segmentInRange(Geometry geometry,
093: Coordinate target) {
094: //It's possible that the geometry may have no segments in range; for
095: // example, if it
096: //is empty, or if only has points in range. [Jon Aquino]
097: LineSegment closest = null;
098: List coordArrays = CoordinateArrays.toCoordinateArrays(
099: geometry, false);
100: for (Iterator i = coordArrays.iterator(); i.hasNext();) {
101: Coordinate[] coordinates = (Coordinate[]) i.next();
102: for (int j = 1; j < coordinates.length; j++) { //1
103: LineSegment candidate = new LineSegment(
104: coordinates[j - 1], coordinates[j]);
105: if (candidate.distance(target) > modelRange()) {
106: continue;
107: }
108: if ((closest == null)
109: || (candidate.distance(target) < closest
110: .distance(target))) {
111: closest = candidate;
112: }
113: }
114: }
115: return closest;
116: }
117:
118: private Coordinate newVertex(LineSegment segment, Coordinate target) {
119: Coordinate closestPoint = segment.closestPoint(target);
120: if (!closestPoint.equals(segment.p0)
121: && !closestPoint.equals(segment.p1)) {
122: return closestPoint;
123: }
124: //No good to make the new vertex one of the endpoints. If the segment
125: // is
126: //tiny (less than 6 pixels), pick the midpoint. [Jon Aquino]
127: double threshold = 6 / getPanel().getViewport().getScale();
128: if (segment.getLength() < threshold) {
129: return CoordUtil.average(segment.p0, segment.p1);
130: }
131: //Segment is not so tiny. Pick a point 3 pixels from the end. [Jon
132: // Aquino]
133: double offset = 3 / getPanel().getViewport().getScale();
134: Coordinate unitVector = closestPoint.equals(segment.p0) ? CoordUtil
135: .divide(CoordUtil.subtract(segment.p1, segment.p0),
136: segment.getLength())
137: : CoordUtil.divide(CoordUtil.subtract(segment.p0,
138: segment.p1), segment.getLength());
139: return CoordUtil.add(closestPoint, CoordUtil.multiply(offset,
140: unitVector));
141: }
142:
143: protected static class SegmentContext {
144: public SegmentContext(Layer layer, Feature feature,
145: LineSegment segment) {
146: this .layer = layer;
147: this .feature = feature;
148: this .segment = segment;
149: }
150:
151: private LineSegment segment;
152: private Feature feature;
153: private Layer layer;
154:
155: public Feature getFeature() {
156: return feature;
157: }
158:
159: public Layer getLayer() {
160: return layer;
161: }
162:
163: public LineSegment getSegment() {
164: return segment;
165: }
166: }
167:
168: private SegmentContext findSegment(Layer layer,
169: Collection features, Coordinate target) {
170: Assert.isTrue(layer.isEditable());
171: for (Iterator i = features.iterator(); i.hasNext();) {
172: Feature feature = (Feature) i.next();
173: for (Iterator j = getPanel().getSelectionManager()
174: .getSelectedItems(layer, feature).iterator(); j
175: .hasNext();) {
176: Geometry selectedItem = (Geometry) j.next();
177: LineSegment segment = segmentInRange(selectedItem,
178: target);
179: if (segment != null) {
180: return new SegmentContext(layer, feature, segment);
181: }
182: }
183: }
184: return null;
185: }
186:
187: private SegmentContext findSegment(Map layerToFeaturesMap,
188: Coordinate target) {
189: for (Iterator i = layerToFeaturesMap.keySet().iterator(); i
190: .hasNext();) {
191: Layer layer = (Layer) i.next();
192: Collection features = (Collection) layerToFeaturesMap
193: .get(layer);
194: SegmentContext segmentContext = findSegment(layer,
195: features, target);
196: if (segmentContext != null) {
197: return segmentContext;
198: }
199: }
200: return null;
201: }
202:
203: protected void gestureFinished() throws java.lang.Exception {
204: reportNothingToUndoYet();
205: if (!check(checkFactory
206: .createAtLeastNItemsMustBeSelectedCheck(1))) {
207: return;
208: }
209: if (!check(checkFactory
210: .createAtLeastNLayersMustBeEditableCheck(1))) {
211: return;
212: }
213: HashMap layerToFeaturesInRangeMap = layerToFeaturesInRangeMap();
214: if (layerToFeaturesInRangeMap.isEmpty()) {
215: getPanel()
216: .getContext()
217: .warnUser(
218: I18N
219: .get("ui.cursortool.editing.InsertVertexTool.no-selected-editable-items-here"));
220: return;
221: }
222: SegmentContext segment = findSegment(layerToFeaturesInRangeMap,
223: modelClickCoordinate());
224: if (segment == null) {
225: getPanel()
226: .getContext()
227: .warnUser(
228: I18N
229: .get("ui.cursortool.editing.InsertVertexTool.no-selected-line-segments-here"));
230: return;
231: }
232: final Coordinate newVertex = newVertex(segment.getSegment(),
233: modelClickCoordinate());
234: Geometry newGeometry = new GeometryEditor().insertVertex(
235: segment.getFeature().getGeometry(), segment
236: .getSegment().p0, segment.getSegment().p1,
237: newVertex);
238: gestureFinished(newGeometry, newVertex, segment);
239: }
240:
241: protected void gestureFinished(Geometry newGeometry,
242: final Coordinate newVertex, SegmentContext segment) {
243: EditTransaction transaction = new EditTransaction(Arrays
244: .asList(new Feature[] { segment.getFeature() }),
245: getName(), segment.getLayer(),
246: isRollingBackInvalidEdits(), false, getPanel());
247: transaction.setGeometry(0, newGeometry);
248: transaction.commit(new EditTransaction.SuccessAction() {
249: public void run() {
250: try {
251: Animations.drawExpandingRing(getPanel()
252: .getViewport().toViewPoint(newVertex),
253: false, Color.green, getPanel(), null);
254: } catch (Throwable t) {
255: getPanel().getContext().warnUser(t.toString());
256: }
257: }
258: });
259: }
260:
261: private HashMap layerToFeaturesInRangeMap()
262: throws NoninvertibleTransformException {
263: HashMap layerToFeaturesInRangeMap = new HashMap();
264: for (Iterator i = getPanel().getLayerManager()
265: .getEditableLayers().iterator(); i.hasNext();) {
266: Layer editableLayer = (Layer) i.next();
267: Collection featuresInRange = featuresInRange(
268: modelClickCoordinate(), editableLayer);
269: if (!featuresInRange.isEmpty()) {
270: layerToFeaturesInRangeMap.put(editableLayer,
271: featuresInRange);
272: }
273: }
274: return layerToFeaturesInRangeMap;
275: }
276:
277: public Icon getIcon() {
278: return IconLoader.icon("InsertVertex.gif");
279: }
280:
281: public Cursor getCursor() {
282: return createCursor(IconLoader.icon("PlusCursor.gif")
283: .getImage());
284: }
285: }
|