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}