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