001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.actions; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.event.ActionEvent; 007import java.awt.event.KeyEvent; 008import java.util.Collection; 009import java.util.List; 010import java.util.Set; 011 012import org.openstreetmap.josm.Main; 013import org.openstreetmap.josm.actions.mapmode.DrawAction; 014import org.openstreetmap.josm.command.ChangeCommand; 015import org.openstreetmap.josm.data.osm.Node; 016import org.openstreetmap.josm.data.osm.OsmPrimitive; 017import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 018import org.openstreetmap.josm.data.osm.Way; 019import org.openstreetmap.josm.gui.layer.OsmDataLayer; 020import org.openstreetmap.josm.tools.Shortcut; 021 022/** 023 * Follow line action - Makes easier to draw a line that shares points with another line 024 * 025 * Aimed at those who want to draw two or more lines related with 026 * each other, but carry different information (i.e. a river acts as boundary at 027 * some part of its course. It preferable to have a separated boundary line than to 028 * mix totally different kind of features in one single way). 029 * 030 * @author Germán Márquez Mejía 031 */ 032public class FollowLineAction extends JosmAction { 033 034 public FollowLineAction() { 035 super( 036 tr("Follow line"), 037 "followline.png", 038 tr("Continues drawing a line that shares nodes with another line."), 039 Shortcut.registerShortcut("tools:followline", tr( 040 "Tool: {0}", tr("Follow")), 041 KeyEvent.VK_F, Shortcut.DIRECT), true); 042 } 043 044 @Override 045 protected void updateEnabledState() { 046 if (getCurrentDataSet() == null) { 047 setEnabled(false); 048 } else { 049 updateEnabledState(getCurrentDataSet().getSelected()); 050 } 051 } 052 053 @Override 054 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) { 055 setEnabled(selection != null && !selection.isEmpty()); 056 } 057 058 @Override 059 public void actionPerformed(ActionEvent evt) { 060 OsmDataLayer osmLayer = Main.main.getEditLayer(); 061 if (osmLayer == null) 062 return; 063 if (!(Main.map.mapMode instanceof DrawAction)) return; // We are not on draw mode 064 065 Collection<Node> selectedPoints = osmLayer.data.getSelectedNodes(); 066 Collection<Way> selectedLines = osmLayer.data.getSelectedWays(); 067 if ((selectedPoints.size() > 1) || (selectedLines.size() != 1)) // Unsuitable selection 068 return; 069 070 Node last = ((DrawAction) Main.map.mapMode).getCurrentBaseNode(); 071 if (last == null) 072 return; 073 Way follower = selectedLines.iterator().next(); 074 if (follower.isClosed()) /* Don't loop until OOM */ 075 return; 076 Node prev = follower.getNode(1); 077 boolean reversed = true; 078 if (follower.lastNode().equals(last)) { 079 prev = follower.getNode(follower.getNodesCount() - 2); 080 reversed = false; 081 } 082 List<OsmPrimitive> referrers = last.getReferrers(); 083 if (referrers.size() < 2) return; // There's nothing to follow 084 085 Node newPoint = null; 086 for (OsmPrimitive referrer : referrers) { 087 if (!referrer.getType().equals(OsmPrimitiveType.WAY)) { // Can't follow points or relations 088 continue; 089 } 090 Way toFollow = (Way) referrer; 091 if (toFollow.equals(follower)) { 092 continue; 093 } 094 Set<Node> points = toFollow.getNeighbours(last); 095 if (!points.remove(prev) || points.isEmpty()) 096 continue; 097 if (points.size() > 1) // Ambiguous junction? 098 return; 099 100 Node newPointCandidate = points.iterator().next(); 101 102 if ((newPoint != null) && (newPoint != newPointCandidate)) 103 return; // Ambiguous junction, force to select next 104 105 newPoint = newPointCandidate; 106 } 107 if (newPoint != null) { 108 Way newFollower = new Way(follower); 109 if (reversed) { 110 newFollower.addNode(0, newPoint); 111 } else { 112 newFollower.addNode(newPoint); 113 } 114 Main.main.undoRedo.add(new ChangeCommand(follower, newFollower)); 115 osmLayer.data.clearSelection(); 116 osmLayer.data.addSelected(newFollower); 117 osmLayer.data.addSelected(newPoint); 118 // "viewport following" mode for tracing long features 119 // from aerial imagery or GPS tracks. 120 if (Main.map.mapView.viewportFollowing) { 121 Main.map.mapView.smoothScrollTo(newPoint.getEastNorth()); 122 } 123 } 124 } 125}