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

Last change on this file since 12778 was 12778, checked in by bastiK, 2 weeks ago

see #15229 - deprecate Projections#project and Projections#inverseProject

replacement is a bit more verbose, but the fact that Main.proj is
involved need not be hidden

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