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    }