/* Spatial Operations & Editing Tools for uDig * * Axios Engineering under a funding contract with: * Diputación Foral de Gipuzkoa, Ordenación Territorial * * http://b5m.gipuzkoa.net * http://www.axios.es * * (C) 2006, Diputación Foral de Gipuzkoa, Ordenación Territorial (DFG-OT). * DFG-OT agrees to licence under Lesser General Public License (LGPL). * * You can redistribute it and/or modify it under the terms of the * GNU Lesser General Public License as published by the Free Software * Foundation; version 2.1 of the License. * * This library 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 * Lesser General Public License for more details. */ package es.axios.udig.ui.editingtools.internal.behaviour; import java.awt.Color; import java.awt.Rectangle; import java.awt.geom.Line2D; import net.refractions.udig.core.IProvider; import net.refractions.udig.project.IBlackboard; import net.refractions.udig.project.ILayer; import net.refractions.udig.project.command.UndoableMapCommand; import net.refractions.udig.project.render.displayAdapter.IMapDisplay; import net.refractions.udig.project.ui.AnimationUpdater; import net.refractions.udig.project.ui.IAnimation; import net.refractions.udig.project.ui.commands.AbstractDrawCommand; import net.refractions.udig.project.ui.render.displayAdapter.MapMouseEvent; import net.refractions.udig.project.ui.tool.IToolContext; import net.refractions.udig.tools.edit.EditState; import net.refractions.udig.tools.edit.EditToolHandler; import net.refractions.udig.tools.edit.EventBehaviour; import net.refractions.udig.tools.edit.EventType; import net.refractions.udig.tools.edit.LockingBehaviour; import net.refractions.udig.tools.edit.preferences.PreferenceUtil; import net.refractions.udig.tools.edit.support.EditBlackboard; import net.refractions.udig.tools.edit.support.Point; import net.refractions.udig.tools.edit.support.SnapBehaviour; import org.eclipse.core.runtime.IProgressMonitor; import org.opengis.referencing.crs.CoordinateReferenceSystem; import com.vividsolutions.jts.geom.LineSegment; import es.axios.udig.ui.commons.util.GeoToolsUtils; /** * Locking event behaviour to find out the closest line to a mouse click. * <p> * On mouse release, searches for the closest point over the closest line segment following the * {@link SnapBehaviour} defined as a preference. * </p> * <p> * Requirements: * <ul> * <li>EventType == RELEASED * <li>EditState == CREATING * </ul> * </p> * * @author Gabriel Roldan (www.axios.es) * @author Mauricio Pazos (www.axios.es) * @since 0.2.0 */ public class SelectLineSegmentBehaviour implements LockingBehaviour { public static final String SELECTED_SEGMENT_BBOARD_KEY = "SELECTED_LINE_SEGMENT"; /** * Used together with lastEditLayer to avoid repeating snap calculations between isValid and * getCommand */ private Point lastPoint; /** * Used together with lastPoint to avoid repeating snap calculations between isValid and * getCommand */ private ILayer lastEditLayer; private LineSegment lastSelectedSegment; /** * @author gabriel */ public static class BlackboardSegmentProvider implements IProvider<LineSegment> { private IBlackboard blackboard; public BlackboardSegmentProvider( IBlackboard blackboard ) { this.blackboard = blackboard; } /** * @return the selected line segment stored in the blackboard, if any, or * <code>null<code> otherwise. */ public LineSegment get( Object...params) { LineSegment segment = (LineSegment) blackboard.get(SELECTED_SEGMENT_BBOARD_KEY); return segment; } } /** * @return <code>this</code> * @see LockingBehaviour#getKey(EditToolHandler) */ public Object getKey( EditToolHandler handler ) { return this; } /** * @see EventBehaviour#isValid(EditToolHandler, MapMouseEvent, EventType) */ public boolean isValid( EditToolHandler handler, MapMouseEvent e, EventType eventType ) { boolean validMode = EventType.RELEASED == eventType; boolean validState = EditState.CREATING == handler.getCurrentState(); boolean isValid = validMode && validState; if (isValid) { LineSegment closestSegment = getSnapSegmentInLayerCrs(handler, e); isValid = closestSegment != null; } return isValid; } /** * Finds out the closest segment to the point indicated by the mouse event following the * snapping behaviour set as preference. * <p> * The snap segment lookup is made in the current <b>map</b>'s CRS and the resulting * {@link LineSegment} is stored in the <b>edit layer</b>'s CRS. * </p> * <p> * If the lookup is successful (i.e. a snap segment is found) two objects are stored in the * map's {@link IBlackboard blackboard}: * <ul> * <li>the snapping segment in layer CRS with the {@link #SELECTED_SEGMENT_BBOARD_KEY} key * <li>a new {@link IEditPointProvider} which returns a point parallel to the given segment * taking in count the mouse location and the last edit shape point, with the * {@link IEditPointProvider#BLACKBOARD_KEY} key * </ul> * If the lookup fails (i.e. no snap segment is found) <b>no modification</b> is made to the * map blackboard. * </p> * <p> * Shall be called only if isValid(...) == true * </p> * * @see EventBehaviour#getCommand(EditToolHandler, MapMouseEvent, EventType) * @return <code>null</code> */ public UndoableMapCommand getCommand( EditToolHandler handler, MapMouseEvent e, EventType eventType ) { handler.lock(this); try { LineSegment snapSegmentInLayerCrs = getSnapSegmentInLayerCrs(handler, e); final IBlackboard mapBlackboard = handler.getContext().getMap().getBlackboard(); mapBlackboard.put(SELECTED_SEGMENT_BBOARD_KEY, snapSegmentInLayerCrs); if (snapSegmentInLayerCrs != null) { runAnimation(handler, snapSegmentInLayerCrs); IProvider<LineSegment> lineProvider = new BlackboardSegmentProvider(mapBlackboard); IEditPointProvider provider = new ParallelEditPointProvider(lineProvider); mapBlackboard.put(IEditPointProvider.BLACKBOARD_KEY, provider); } } finally { handler.unlock(this); } return null; } /** * Performs the lookup of the closest segment to the point given by the mouse location and * following the snap behaviour set as a preference. * <p> * The lookup is made in the <b>map</b>'s CRS, but the returned segment is transformed to the * <b>edit layer</code>'s CRS * </p> * * @param handler * @param e * @return */ private LineSegment getSnapSegmentInLayerCrs( EditToolHandler handler, MapMouseEvent e ) { final ILayer editLayer = handler.getEditLayer(); final Point mouseLocation = Point.valueOf(e.x, e.y); if (lastPoint != null && lastEditLayer == editLayer && lastPoint.equals(mouseLocation)) { return lastSelectedSegment; } final EditBlackboard layerBlackboard = handler.getEditBlackboard(editLayer); final boolean includeSegmentsInCurrent = true; final SnapBehaviour snapBehaviour = PreferenceUtil.instance().getSnapBehaviour(); final CoordinateReferenceSystem mapCrs = handler.getContext().getCRS(); final int snappingRadius = PreferenceUtil.instance().getSnappingRadius(); final SnapSegmentFinder segmentFinder = new SnapSegmentFinder(mapCrs); LineSegment closestSnapSegment; closestSnapSegment = segmentFinder.getClosestSnapSegment(handler, layerBlackboard, mouseLocation, includeSegmentsInCurrent, snapBehaviour, snappingRadius); if (closestSnapSegment != null) { CoordinateReferenceSystem layerCrs = editLayer.getCRS(); closestSnapSegment = GeoToolsUtils.reproject(closestSnapSegment, mapCrs, layerCrs); } lastPoint = mouseLocation; lastEditLayer = editLayer; lastSelectedSegment = closestSnapSegment; return closestSnapSegment; } /** * @see EventBehaviour#handleError(EditToolHandler, Throwable, UndoableMapCommand) */ public void handleError( EditToolHandler handler, Throwable error, UndoableMapCommand command ) { } private void runAnimation( EditToolHandler handler, LineSegment segmentInLayerCrs ) { EditBlackboard bboard = handler.getEditBlackboard(handler.getEditLayer()); Point p1 = bboard.toPoint(segmentInLayerCrs.p0); Point p2 = bboard.toPoint(segmentInLayerCrs.p1); Line2D line = new Line2D.Float(p1.getX(), p1.getY(), p2.getX(), p2.getY()); SegmentAnimation animation = new SegmentAnimation(line); IToolContext context = handler.getContext(); IMapDisplay mapDisplay = context.getMapDisplay(); AnimationUpdater.runTimer(mapDisplay, animation); } private class SegmentAnimation extends AbstractDrawCommand implements IAnimation { private int runs = 0; private final int maxRuns = 6; private Line2D line; public SegmentAnimation( Line2D line ) { this.line = line; } public short getFrameInterval() { return 200; } public void nextFrame() { runs++; } public boolean hasNext() { return runs < maxRuns; } public void run( IProgressMonitor monitor ) throws Exception { if (runs < maxRuns && runs % 2 == 0) { graphics.setColor(Color.YELLOW); graphics.draw(line); } } /** * TODO */ public Rectangle getValidArea() { return null; } } }