001    //License: GPL. Copyright 2007 by Immanuel Scholz and others
002    package org.openstreetmap.josm.actions;
003    
004    import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
005    import static org.openstreetmap.josm.tools.I18n.tr;
006    
007    import java.awt.event.ActionEvent;
008    import java.awt.event.KeyEvent;
009    import java.util.ArrayList;
010    import java.util.Collection;
011    import java.util.Collections;
012    import java.util.HashMap;
013    import java.util.HashSet;
014    import java.util.LinkedList;
015    import java.util.List;
016    import java.util.Map;
017    
018    import org.openstreetmap.josm.Main;
019    import org.openstreetmap.josm.command.ChangeCommand;
020    import org.openstreetmap.josm.command.Command;
021    import org.openstreetmap.josm.command.SequenceCommand;
022    import org.openstreetmap.josm.data.osm.Node;
023    import org.openstreetmap.josm.data.osm.OsmPrimitive;
024    import org.openstreetmap.josm.data.osm.Way;
025    import org.openstreetmap.josm.data.osm.WaySegment;
026    import org.openstreetmap.josm.tools.Shortcut;
027    
028    public class JoinNodeWayAction extends JosmAction {
029        public JoinNodeWayAction() {
030            super(tr("Join Node to Way"), "joinnodeway", tr("Include a node into the nearest way segments"),
031                    Shortcut.registerShortcut("tools:joinnodeway", tr("Tool: {0}", tr("Join Node to Way")), KeyEvent.VK_J, Shortcut.DIRECT), true);
032            putValue("help", ht("/Action/JoinNodeWay"));
033        }
034    
035        public void actionPerformed(ActionEvent e) {
036            if (!isEnabled())
037                return;
038            Collection<Node> selectedNodes = getCurrentDataSet().getSelectedNodes();
039            // Allow multiple selected nodes too?
040            if (selectedNodes.size() != 1) return;
041    
042            Node node = selectedNodes.iterator().next();
043    
044            Collection<Command> cmds = new LinkedList<Command>();
045    
046            // If the user has selected some ways, only join the node to these.
047            boolean restrictToSelectedWays =
048                getCurrentDataSet().getSelectedWays().size() > 0;
049    
050                List<WaySegment> wss = Main.map.mapView.getNearestWaySegments(
051                        Main.map.mapView.getPoint(node), OsmPrimitive.isSelectablePredicate);
052                HashMap<Way, List<Integer>> insertPoints = new HashMap<Way, List<Integer>>();
053                for (WaySegment ws : wss) {
054                    // Maybe cleaner to pass a "isSelected" predicate to getNearestWaySegements, but this is atm. less invasive.
055                    if(restrictToSelectedWays && !ws.way.isSelected()) {
056                        continue;
057                    }
058    
059                    List<Integer> is;
060                    if (insertPoints.containsKey(ws.way)) {
061                        is = insertPoints.get(ws.way);
062                    } else {
063                        is = new ArrayList<Integer>();
064                        insertPoints.put(ws.way, is);
065                    }
066    
067                    if (ws.way.getNode(ws.lowerIndex) != node
068                            && ws.way.getNode(ws.lowerIndex+1) != node) {
069                        is.add(ws.lowerIndex);
070                    }
071                }
072    
073                for (Map.Entry<Way, List<Integer>> insertPoint : insertPoints.entrySet()) {
074                    List<Integer> is = insertPoint.getValue();
075                    if (is.size() == 0) {
076                        continue;
077                    }
078    
079                    Way w = insertPoint.getKey();
080                    List<Node> nodesToAdd = w.getNodes();
081                    pruneSuccsAndReverse(is);
082                    for (int i : is) {
083                        nodesToAdd.add(i+1, node);
084                    }
085                    Way wnew = new Way(w);
086                    wnew.setNodes(nodesToAdd);
087                    cmds.add(new ChangeCommand(w, wnew));
088                }
089                if (cmds.size() == 0) return;
090                Main.main.undoRedo.add(new SequenceCommand(tr("Join Node and Line"), cmds));
091                Main.map.repaint();
092        }
093    
094        private static void pruneSuccsAndReverse(List<Integer> is) {
095            //if (is.size() < 2) return;
096    
097            HashSet<Integer> is2 = new HashSet<Integer>();
098            for (int i : is) {
099                if (!is2.contains(i - 1) && !is2.contains(i + 1)) {
100                    is2.add(i);
101                }
102            }
103            is.clear();
104            is.addAll(is2);
105            Collections.sort(is);
106            Collections.reverse(is);
107        }
108    
109        @Override
110        protected void updateEnabledState() {
111            if (getCurrentDataSet() == null) {
112                setEnabled(false);
113            } else {
114                updateEnabledState(getCurrentDataSet().getSelected());
115            }
116        }
117    
118        @Override
119        protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
120            setEnabled(selection != null && !selection.isEmpty());
121        }
122    }