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