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

Last change on this file since 11217 was 10716, checked in by simon04, 8 years ago

see #11390, see #12890 - Deprecate predicates in OsmPrimitive class

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