source: josm/trunk/src/org/openstreetmap/josm/actions/AlignInLineAction.java @ 4920

Revision 4632, 8.0 KB checked in by Don-vip, 2 months ago (diff)

fix #7111 - JOSM crashes when "aligning nodes" is used and only one node is focussed before

  • Property svn:eol-style set to native
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
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.ArrayList;
10import java.util.Collection;
11import java.util.List;
12
13import javax.swing.JOptionPane;
14
15import org.openstreetmap.josm.Main;
16import org.openstreetmap.josm.command.Command;
17import org.openstreetmap.josm.command.MoveCommand;
18import org.openstreetmap.josm.command.SequenceCommand;
19import org.openstreetmap.josm.data.osm.Node;
20import org.openstreetmap.josm.data.osm.OsmPrimitive;
21import org.openstreetmap.josm.data.osm.Way;
22import org.openstreetmap.josm.tools.Shortcut;
23
24/**
25 * Aligns all selected nodes into a straight line (useful for
26 * roads that should be straight, but have side roads and
27 * therefore need multiple nodes)
28 *
29 * @author Matthew Newton
30 */
31public final class AlignInLineAction extends JosmAction {
32
33    public AlignInLineAction() {
34        super(tr("Align Nodes in Line"), "alignline", tr("Move the selected nodes in to a line."),
35                Shortcut.registerShortcut("tools:alignline", tr("Tool: {0}", tr("Align Nodes in Line")), KeyEvent.VK_L, Shortcut.GROUP_EDIT), true);
36        putValue("help", ht("/Action/AlignInLine"));
37    }
38
39    // the joy of single return values only...
40    private void nodePairFurthestApart(ArrayList<Node> nodes, Node[] resultOut) {
41        if(resultOut.length < 2)
42            throw new IllegalArgumentException();
43        // Find from the selected nodes two that are the furthest apart.
44        // Let's call them A and B.
45        double distance = 0;
46
47        Node nodea = null;
48        Node nodeb = null;
49
50        for (int i = 0; i < nodes.size()-1; i++) {
51            Node n = nodes.get(i);
52            for (int j = i+1; j < nodes.size(); j++) {
53                Node m = nodes.get(j);
54                double dist = Math.sqrt(n.getEastNorth().distance(m.getEastNorth()));
55                if (dist > distance) {
56                    nodea = n;
57                    nodeb = m;
58                    distance = dist;
59                }
60            }
61        }
62        resultOut[0] = nodea;
63        resultOut[1] = nodeb;
64    }
65
66    private void showWarning() {
67        JOptionPane.showMessageDialog(
68                Main.parent,
69                tr("Please select at least three nodes."),
70                tr("Information"),
71                JOptionPane.INFORMATION_MESSAGE
72        );
73        return;
74    }
75
76    private static int indexWrap(int size, int i) {
77        i = i % size; // -2 % 5 = -2, -7 % 5 = -2, -5 % 5 = 0
78        if (i < 0) {
79            i = size + i;
80        }
81        return i;
82    }
83    // get the node in w at index i relative to refI
84    private static Node getNodeRelative(Way w, int refI, int i) {
85        int absI = indexWrap(w.getNodesCount(), refI + i);
86        if(w.isClosed() && refI + i < 0) {
87            absI--;  // node duplicated in closed ways
88        }
89        return w.getNode(absI);
90    }
91
92    /**
93     * The general algorithm here is to find the two selected nodes
94     * that are furthest apart, and then to align all other selected
95     * nodes onto the straight line between these nodes.
96     */
97
98
99    /**
100     * Operation depends on the selected objects:
101     */
102    public void actionPerformed(ActionEvent e) {
103        if (!isEnabled())
104            return;
105
106        Node[] anchors = new Node[2]; // oh, java I love you so much..
107
108        List<Node> selectedNodes = new ArrayList<Node>(getCurrentDataSet().getSelectedNodes());
109        Collection<Way> selectedWays = getCurrentDataSet().getSelectedWays();
110        ArrayList<Node> nodes = new ArrayList<Node>();
111
112        //// Decide what to align based on selection:
113
114        /// Only ways selected -> Align their nodes.
115        if ((selectedNodes.size() == 0) && (selectedWays.size() == 1)) { // TODO: handle multiple ways
116            for (Way way : selectedWays) {
117                nodes.addAll(way.getNodes());
118            }
119            // use the nodes furthest apart as anchors
120            nodePairFurthestApart(nodes, anchors);
121        }
122        /// More than 3 nodes selected -> align those nodes
123        else if(selectedNodes.size() >= 3) {
124            nodes.addAll(selectedNodes);
125            // use the nodes furthest apart as anchors
126            nodePairFurthestApart(nodes, anchors);
127        }
128        /// One node selected -> align that node to the relevant neighbors
129        else if (selectedNodes.size() == 1) {
130            Node n = selectedNodes.iterator().next();
131
132            Way w = null;
133            if(selectedWays.size() == 1) {
134                w = selectedWays.iterator().next();
135                if(w.containsNode(n) == false)
136                    // warning
137                    return;
138            } else {
139                List<Way> refWays = OsmPrimitive.getFilteredList(n.getReferrers(), Way.class);
140                if (refWays.size() == 1) { // node used in only one way
141                    w = refWays.iterator().next();
142                }
143            }
144            if (w == null || w.getNodesCount() < 3)
145                // warning, need at least 3 nodes
146                return;
147
148            // Find anchors
149            int nodeI = w.getNodes().indexOf(n);
150            // End-node in non-circular way selected: align this node with the two neighbors.
151            if ((nodeI == 0 || nodeI == w.getNodesCount()-1) && !w.isClosed()) {
152                int direction = nodeI == 0 ? 1 : -1;
153                anchors[0] = w.getNode(nodeI + direction);
154                anchors[1] = w.getNode(nodeI + direction*2);
155            } else {
156                // o---O---o
157                anchors[0] = getNodeRelative(w, nodeI, 1);
158                anchors[1] = getNodeRelative(w, nodeI, -1);
159            }
160            nodes.add(n);
161        }
162
163        if (anchors[0] == null || anchors[1] == null) {
164            showWarning();
165            return;
166        }
167
168
169        Collection<Command> cmds = new ArrayList<Command>(nodes.size());
170
171        createAlignNodesCommands(anchors, nodes, cmds);
172
173        // Do it!
174        Main.main.undoRedo.add(new SequenceCommand(tr("Align Nodes in Line"), cmds));
175        Main.map.repaint();
176    }
177
178    private void createAlignNodesCommands(Node[] anchors, Collection<Node> nodes, Collection<Command> cmds) {
179        Node nodea = anchors[0];
180        Node nodeb = anchors[1];
181
182        // The anchors are aligned per definition
183        nodes.remove(nodea);
184        nodes.remove(nodeb);
185
186        // Find out co-ords of A and B
187        double ax = nodea.getEastNorth().east();
188        double ay = nodea.getEastNorth().north();
189        double bx = nodeb.getEastNorth().east();
190        double by = nodeb.getEastNorth().north();
191
192        // OK, for each node to move, work out where to move it!
193        for (Node n : nodes) {
194            // Get existing co-ords of node to move
195            double nx = n.getEastNorth().east();
196            double ny = n.getEastNorth().north();
197
198            if (ax == bx) {
199                // Special case if AB is vertical...
200                nx = ax;
201            } else if (ay == by) {
202                // ...or horizontal
203                ny = ay;
204            } else {
205                // Otherwise calculate position by solving y=mx+c
206                double m1 = (by - ay) / (bx - ax);
207                double c1 = ay - (ax * m1);
208                double m2 = (-1) / m1;
209                double c2 = n.getEastNorth().north() - (n.getEastNorth().east() * m2);
210
211                nx = (c2 - c1) / (m1 - m2);
212                ny = (m1 * nx) + c1;
213            }
214            double newX = nx - n.getEastNorth().east();
215            double newY = ny - n.getEastNorth().north();
216            // Add the command to move the node to its new position.
217            cmds.add(new MoveCommand(n, newX, newY));
218        }
219    }
220
221    @Override
222    protected void updateEnabledState() {
223        setEnabled(getCurrentDataSet() != null && !getCurrentDataSet().getSelected().isEmpty());
224    }
225
226    @Override
227    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
228        setEnabled(selection != null && !selection.isEmpty());
229    }
230}
Note: See TracBrowser for help on using the repository browser.