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

Last change on this file since 14693 was 14397, checked in by Don-vip, 5 years ago

fix #16935 - simplify/cleanup help topics of ToggleDialog/ToggleDialogAction

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