/* * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI * for visualizing and manipulating spatial features with geometry and attributes. * * Copyright (C) 2003 Vivid Solutions * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * For more information, contact: * * Vivid Solutions * Suite #1A * 2328 Government Street * Victoria BC V8T 5G5 * Canada * * (250)385-6040 * www.vividsolutions.com */ package com.vividsolutions.jump.workbench.ui.cursortool.editing; import java.awt.Color; import java.awt.Cursor; import java.awt.geom.NoninvertibleTransformException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.swing.Icon; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LineSegment; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.util.Assert; import com.vividsolutions.jump.I18N; import com.vividsolutions.jump.feature.Feature; import com.vividsolutions.jump.geom.CoordUtil; import com.vividsolutions.jump.util.CoordinateArrays; import com.vividsolutions.jump.workbench.model.Layer; import com.vividsolutions.jump.workbench.plugin.EnableCheckFactory; import com.vividsolutions.jump.workbench.ui.EditTransaction; import com.vividsolutions.jump.workbench.ui.GeometryEditor; import com.vividsolutions.jump.workbench.ui.cursortool.Animations; import com.vividsolutions.jump.workbench.ui.cursortool.NClickTool; import com.vividsolutions.jump.workbench.ui.images.IconLoader; public class InsertVertexTool extends NClickTool { private static final int PIXEL_RANGE = 5; private EnableCheckFactory checkFactory; public InsertVertexTool(EnableCheckFactory checkFactory) { super(1); this.checkFactory = checkFactory; } private double modelRange() { return PIXEL_RANGE / getPanel().getViewport().getScale(); } private Collection featuresInRange(Coordinate modelClickCoordinate, Layer layer) { Point modelClickPoint = new GeometryFactory() .createPoint(modelClickCoordinate); Collection featuresWithSelectedItems = getPanel().getSelectionManager() .getFeaturesWithSelectedItems(layer); if (featuresWithSelectedItems.isEmpty()) { return new ArrayList(); } ArrayList featuresInRange = new ArrayList(); for (Iterator i = featuresWithSelectedItems.iterator(); i.hasNext();) { Feature candidate = (Feature) i.next(); if (modelClickPoint.distance(candidate.getGeometry()) <= modelRange()) { featuresInRange.add(candidate); } } return featuresInRange; } private Coordinate modelClickCoordinate() throws NoninvertibleTransformException { return (Coordinate) getCoordinates().get(0); } private LineSegment segmentInRange(Geometry geometry, Coordinate target) { //It's possible that the geometry may have no segments in range; for // example, if it //is empty, or if only has points in range. [Jon Aquino] LineSegment closest = null; List coordArrays = CoordinateArrays.toCoordinateArrays(geometry, false); for (Iterator i = coordArrays.iterator(); i.hasNext();) { Coordinate[] coordinates = (Coordinate[]) i.next(); for (int j = 1; j < coordinates.length; j++) { //1 LineSegment candidate = new LineSegment(coordinates[j - 1], coordinates[j]); if (candidate.distance(target) > modelRange()) { continue; } if ((closest == null) || (candidate.distance(target) < closest .distance(target))) { closest = candidate; } } } return closest; } private Coordinate newVertex(LineSegment segment, Coordinate target) { Coordinate closestPoint = segment.closestPoint(target); if (!closestPoint.equals(segment.p0) && !closestPoint.equals(segment.p1)) { if (Double.isNaN(segment.p0.z) || Double.isNaN(segment.p1.z)) return closestPoint; closestPoint.z = (segment.p0.z + segment.p1.z) / 2d; } //No good to make the new vertex one of the endpoints. If the segment // is //tiny (less than 6 pixels), pick the midpoint. [Jon Aquino] double threshold = 6 / getPanel().getViewport().getScale(); if (segment.getLength() < threshold) { return CoordUtil.average(segment.p0, segment.p1); } //Segment is not so tiny. Pick a point 3 pixels from the end. [Jon // Aquino] double offset = 3 / getPanel().getViewport().getScale(); Coordinate unitVector = closestPoint.equals(segment.p0) ? CoordUtil .divide(CoordUtil.subtract(segment.p1, segment.p0), segment .getLength()) : CoordUtil.divide(CoordUtil.subtract( segment.p0, segment.p1), segment.getLength()); return CoordUtil.add(closestPoint, CoordUtil.multiply(offset, unitVector)); } protected static class SegmentContext { public SegmentContext(Layer layer, Feature feature, LineSegment segment) { this.layer = layer; this.feature = feature; this.segment = segment; } private LineSegment segment; private Feature feature; private Layer layer; public Feature getFeature() { return feature; } public Layer getLayer() { return layer; } public LineSegment getSegment() { return segment; } } private SegmentContext findSegment(Layer layer, Collection features, Coordinate target) { Assert.isTrue(layer.isEditable()); for (Iterator i = features.iterator(); i.hasNext();) { Feature feature = (Feature) i.next(); for (Iterator j = getPanel().getSelectionManager() .getSelectedItems(layer, feature).iterator(); j.hasNext();) { Geometry selectedItem = (Geometry) j.next(); LineSegment segment = segmentInRange(selectedItem, target); if (segment != null) { return new SegmentContext(layer, feature, segment); } } } return null; } private SegmentContext findSegment(Map layerToFeaturesMap, Coordinate target) { for (Iterator i = layerToFeaturesMap.keySet().iterator(); i.hasNext();) { Layer layer = (Layer) i.next(); Collection features = (Collection) layerToFeaturesMap.get(layer); SegmentContext segmentContext = findSegment(layer, features, target); if (segmentContext != null) { return segmentContext; } } return null; } protected void gestureFinished() throws java.lang.Exception { reportNothingToUndoYet(); if (!check(checkFactory.createAtLeastNItemsMustBeSelectedCheck(1))) { return; } if (!check(checkFactory.createAtLeastNLayersMustBeEditableCheck(1))) { return; } HashMap layerToFeaturesInRangeMap = layerToFeaturesInRangeMap(); if (layerToFeaturesInRangeMap.isEmpty()) { getPanel().getContext().warnUser(I18N.get("ui.cursortool.editing.InsertVertexTool.no-selected-editable-items-here")); return; } SegmentContext segment = findSegment(layerToFeaturesInRangeMap, modelClickCoordinate()); if (segment == null) { getPanel().getContext().warnUser(I18N.get("ui.cursortool.editing.InsertVertexTool.no-selected-line-segments-here")); return; } final Coordinate newVertex = newVertex(segment.getSegment(), modelClickCoordinate()); Geometry newGeometry = new GeometryEditor().insertVertex(segment .getFeature().getGeometry(), segment.getSegment().p0, segment .getSegment().p1, newVertex); gestureFinished(newGeometry, newVertex, segment); } protected void gestureFinished(Geometry newGeometry, final Coordinate newVertex, SegmentContext segment) { EditTransaction transaction = new EditTransaction(Arrays .asList(new Feature[]{segment.getFeature()}), getName(), segment.getLayer(), isRollingBackInvalidEdits(), false, getPanel()); transaction.setGeometry(0, newGeometry); transaction.commit(new EditTransaction.SuccessAction() { public void run() { try { Animations.drawExpandingRing(getPanel().getViewport() .toViewPoint(newVertex), false, Color.green, getPanel(), null); } catch (Throwable t) { getPanel().getContext().warnUser(t.toString()); } } }); } private HashMap layerToFeaturesInRangeMap() throws NoninvertibleTransformException { HashMap layerToFeaturesInRangeMap = new HashMap(); for (Iterator i = getPanel().getLayerManager().getEditableLayers() .iterator(); i.hasNext();) { Layer editableLayer = (Layer) i.next(); Collection featuresInRange = featuresInRange( modelClickCoordinate(), editableLayer); if (!featuresInRange.isEmpty()) { layerToFeaturesInRangeMap.put(editableLayer, featuresInRange); } } return layerToFeaturesInRangeMap; } public Icon getIcon() { return IconLoader.icon("InsertVertex.gif"); } public Cursor getCursor() { return createCursor(IconLoader.icon("PlusCursor.gif").getImage()); } }