source: josm/trunk/src/org/openstreetmap/josm/actions/JoinNodeWayAction.java@ 8221

Last change on this file since 8221 was 7859, checked in by Don-vip, 9 years ago

fix various Sonar issues, improve Javadoc

  • Property svn:eol-style set to native
File size: 8.9 KB
Line 
1//License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.actions;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.event.ActionEvent;
8import java.awt.event.KeyEvent;
9import java.util.Collection;
10import java.util.Collections;
11import java.util.Comparator;
12import java.util.HashMap;
13import java.util.LinkedList;
14import java.util.List;
15import java.util.Map;
16import java.util.Set;
17import java.util.SortedSet;
18import java.util.TreeSet;
19
20import org.openstreetmap.josm.Main;
21import org.openstreetmap.josm.command.ChangeCommand;
22import org.openstreetmap.josm.command.Command;
23import org.openstreetmap.josm.command.MoveCommand;
24import org.openstreetmap.josm.command.SequenceCommand;
25import org.openstreetmap.josm.data.coor.EastNorth;
26import org.openstreetmap.josm.data.osm.Node;
27import org.openstreetmap.josm.data.osm.OsmPrimitive;
28import org.openstreetmap.josm.data.osm.Way;
29import org.openstreetmap.josm.data.osm.WaySegment;
30import org.openstreetmap.josm.data.projection.Projections;
31import org.openstreetmap.josm.tools.Geometry;
32import org.openstreetmap.josm.tools.MultiMap;
33import org.openstreetmap.josm.tools.Shortcut;
34
35/**
36 * Action allowing to join a node to a nearby way, operating on two modes:<ul>
37 * <li><b>Join Node to Way</b>: Include a node into the nearest way segments. The node does not move</li>
38 * <li><b>Move Node onto Way</b>: Move the node onto the nearest way segments and include it</li>
39 * </ul>
40 * @since 466
41 */
42public class JoinNodeWayAction extends JosmAction {
43
44 protected final boolean joinWayToNode;
45
46 protected JoinNodeWayAction(boolean joinWayToNode, String name, String iconName, String tooltip,
47 Shortcut shortcut, boolean registerInToolbar) {
48 super(name, iconName, tooltip, shortcut, registerInToolbar);
49 this.joinWayToNode = joinWayToNode;
50 }
51
52 /**
53 * Constructs a Join Node to Way action.
54 * @return the Join Node to Way action
55 */
56 public static JoinNodeWayAction createJoinNodeToWayAction() {
57 JoinNodeWayAction action = new JoinNodeWayAction(false,
58 tr("Join Node to Way"), /* ICON */ "joinnodeway",
59 tr("Include a node into the nearest way segments"),
60 Shortcut.registerShortcut("tools:joinnodeway", tr("Tool: {0}", tr("Join Node to Way")),
61 KeyEvent.VK_J, Shortcut.DIRECT), true);
62 action.putValue("help", ht("/Action/JoinNodeWay"));
63 return action;
64 }
65
66 /**
67 * Constructs a Move Node onto Way action.
68 * @return the Move Node onto Way action
69 */
70 public static JoinNodeWayAction createMoveNodeOntoWayAction() {
71 JoinNodeWayAction action = new JoinNodeWayAction(true,
72 tr("Move Node onto Way"), /* ICON*/ "movenodeontoway",
73 tr("Move the node onto the nearest way segments and include it"),
74 Shortcut.registerShortcut("tools:movenodeontoway", tr("Tool: {0}", tr("Move Node onto Way")),
75 KeyEvent.VK_N, Shortcut.DIRECT), true);
76 action.putValue("help", ht("/Action/MoveNodeWay"));
77 return action;
78 }
79
80 @Override
81 public void actionPerformed(ActionEvent e) {
82 if (!isEnabled())
83 return;
84 Collection<Node> selectedNodes = getCurrentDataSet().getSelectedNodes();
85 Collection<Command> cmds = new LinkedList<>();
86 Map<Way, MultiMap<Integer, Node>> data = new HashMap<>();
87
88 // If the user has selected some ways, only join the node to these.
89 boolean restrictToSelectedWays =
90 !getCurrentDataSet().getSelectedWays().isEmpty();
91
92 // Planning phase: decide where we'll insert the nodes and put it all in "data"
93 for (Node node : selectedNodes) {
94 List<WaySegment> wss = Main.map.mapView.getNearestWaySegments(
95 Main.map.mapView.getPoint(node), OsmPrimitive.isSelectablePredicate);
96
97 MultiMap<Way, Integer> insertPoints = new MultiMap<>();
98 for (WaySegment ws : wss) {
99 // Maybe cleaner to pass a "isSelected" predicate to getNearestWaySegments, but this is less invasive.
100 if (restrictToSelectedWays && !ws.way.isSelected()) {
101 continue;
102 }
103
104 if (!ws.getFirstNode().equals(node) && !ws.getSecondNode().equals(node)) {
105 insertPoints.put(ws.way, ws.lowerIndex);
106 }
107 }
108 for (Map.Entry<Way, Set<Integer>> entry : insertPoints.entrySet()) {
109 final Way w = entry.getKey();
110 final Set<Integer> insertPointsForWay = entry.getValue();
111 for (int i : pruneSuccs(insertPointsForWay)) {
112 MultiMap<Integer, Node> innerMap;
113 if (!data.containsKey(w)) {
114 innerMap = new MultiMap<>();
115 } else {
116 innerMap = data.get(w);
117 }
118 innerMap.put(i, node);
119 data.put(w, innerMap);
120 }
121 }
122 }
123
124 // Execute phase: traverse the structure "data" and finally put the nodes into place
125 for (Map.Entry<Way, MultiMap<Integer, Node>> entry : data.entrySet()) {
126 final Way w = entry.getKey();
127 final MultiMap<Integer, Node> innerEntry = entry.getValue();
128
129 List<Integer> segmentIndexes = new LinkedList<>();
130 segmentIndexes.addAll(innerEntry.keySet());
131 Collections.sort(segmentIndexes, Collections.reverseOrder());
132
133 List<Node> wayNodes = w.getNodes();
134 for (Integer segmentIndex : segmentIndexes) {
135 final Set<Node> nodesInSegment = innerEntry.get(segmentIndex);
136 if (joinWayToNode) {
137 for (Node node : nodesInSegment) {
138 EastNorth newPosition = Geometry.closestPointToSegment(w.getNode(segmentIndex).getEastNorth(),
139 w.getNode(segmentIndex+1).getEastNorth(),
140 node.getEastNorth());
141 cmds.add(new MoveCommand(node, Projections.inverseProject(newPosition)));
142 }
143 }
144 List<Node> nodesToAdd = new LinkedList<>();
145 nodesToAdd.addAll(nodesInSegment);
146 Collections.sort(nodesToAdd, new NodeDistanceToRefNodeComparator(
147 w.getNode(segmentIndex), w.getNode(segmentIndex+1), !joinWayToNode));
148 wayNodes.addAll(segmentIndex + 1, nodesToAdd);
149 }
150 Way wnew = new Way(w);
151 wnew.setNodes(wayNodes);
152 cmds.add(new ChangeCommand(w, wnew));
153 }
154
155 if (cmds.isEmpty()) return;
156 Main.main.undoRedo.add(new SequenceCommand(getValue(NAME).toString(), cmds));
157 Main.map.repaint();
158 }
159
160 private static SortedSet<Integer> pruneSuccs(Collection<Integer> is) {
161 SortedSet<Integer> is2 = new TreeSet<>();
162 for (int i : is) {
163 if (!is2.contains(i - 1) && !is2.contains(i + 1)) {
164 is2.add(i);
165 }
166 }
167 return is2;
168 }
169
170 /**
171 * Sorts collinear nodes by their distance to a common reference node.
172 */
173 private static class NodeDistanceToRefNodeComparator implements Comparator<Node> {
174 private final EastNorth refPoint;
175 private EastNorth refPoint2;
176 private final boolean projectToSegment;
177 NodeDistanceToRefNodeComparator(Node referenceNode, Node referenceNode2, boolean projectFirst) {
178 refPoint = referenceNode.getEastNorth();
179 refPoint2 = referenceNode2.getEastNorth();
180 projectToSegment = projectFirst;
181 }
182 @Override
183 public int compare(Node first, Node second) {
184 EastNorth firstPosition = first.getEastNorth();
185 EastNorth secondPosition = second.getEastNorth();
186
187 if (projectToSegment) {
188 firstPosition = Geometry.closestPointToSegment(refPoint, refPoint2, firstPosition);
189 secondPosition = Geometry.closestPointToSegment(refPoint, refPoint2, secondPosition);
190 }
191
192 double distanceFirst = firstPosition.distance(refPoint);
193 double distanceSecond = secondPosition.distance(refPoint);
194 double difference = distanceFirst - distanceSecond;
195
196 if (difference > 0.0) return 1;
197 if (difference < 0.0) return -1;
198 return 0;
199 }
200 }
201
202 @Override
203 protected void updateEnabledState() {
204 if (getCurrentDataSet() == null) {
205 setEnabled(false);
206 } else {
207 updateEnabledState(getCurrentDataSet().getSelected());
208 }
209 }
210
211 @Override
212 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
213 setEnabled(selection != null && !selection.isEmpty());
214 }
215}
Note: See TracBrowser for help on using the repository browser.