source: subversion/applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/AlignWayNodesAction.java

Last change on this file was 34812, checked in by gerdp, 3 months ago

see #17187:

  • Replace deprecated methods
  • Remove dead code
  • Make Shift+Z (Select last modified nodes) work again + Repated Shift+Z cycles through the commands available in the undo tree + Ignore changes in other layers
  • Make Alt+Shift+Z (Select last modified Ways) work again (same logic as with Shift+Z)
  • fix some javadoc errors


File size: 7.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.plugins.utilsplugin2.actions;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.event.ActionEvent;
7import java.awt.event.KeyEvent;
8import java.util.ArrayList;
9import java.util.Collection;
10import java.util.HashSet;
11import java.util.List;
12import java.util.Set;
13import java.util.stream.Collectors;
14
15import javax.swing.JOptionPane;
16
17import org.openstreetmap.josm.actions.JosmAction;
18import org.openstreetmap.josm.command.Command;
19import org.openstreetmap.josm.command.MoveCommand;
20import org.openstreetmap.josm.command.SequenceCommand;
21import org.openstreetmap.josm.data.UndoRedoHandler;
22import org.openstreetmap.josm.data.osm.Node;
23import org.openstreetmap.josm.data.osm.OsmPrimitive;
24import org.openstreetmap.josm.data.osm.Way;
25import org.openstreetmap.josm.gui.Notification;
26import org.openstreetmap.josm.tools.Shortcut;
27
28/**
29 * Pastes relation membership from objects in the paste buffer onto selected object(s).
30 *
31 * @author Zverik
32 */
33public class AlignWayNodesAction extends JosmAction {
34    private static final String TITLE = tr("Align Way Nodes");
35    private static final double MOVE_THRESHOLD = 1e-9;
36
37    /**
38     * Constructs a new {@code AlignWayNodesAction}.
39     */
40    public AlignWayNodesAction() {
41        super(TITLE, "dumbutils/alignwaynodes", tr("Align nodes in a way"),
42                Shortcut.registerShortcut("tools:alignwaynodes", tr("Tool: {0}", tr("Align Way Nodes")), KeyEvent.VK_L, Shortcut.SHIFT),
43                true);
44    }
45
46    @Override
47    public void actionPerformed(ActionEvent e) {
48        Collection<OsmPrimitive> selection = getLayerManager().getEditDataSet().getSelected();
49        Set<Node> selectedNodes = filterNodes(selection);
50        int selectedNodesCount = selectedNodes.size();
51        Set<Way> ways = findCommonWays(selectedNodes);
52        if (ways == null || ways.size() != 1 || selectedNodesCount == 0)
53            return;
54        Way way = ways.iterator().next();
55        if (way.getNodesCount() < (way.isClosed() ? 4 : 3)) {
56            new Notification(tr("The way with selected nodes can not be straightened."))
57            .setIcon(JOptionPane.ERROR_MESSAGE).show();
58            return;
59        }
60
61        // Prepare a list of nodes to align
62        int firstNodePos = findFirstNode(way, selectedNodes);
63        int lastNodePos = way.isClosed() ? firstNodePos : way.getNodesCount();
64        List<Node> nodes = new ArrayList<>();
65        int i = firstNodePos;
66        boolean iterated = false;
67        while (!iterated || i != lastNodePos) {
68            Node node = way.getNode(i);
69            if (selectedNodes.contains(node)) {
70                nodes.add(node);
71                selectedNodes.remove(node);
72                if (selectedNodesCount == 1) {
73                    nodes.add(0, way.getNode(i > 0 ? i - 1 : way.isClosed() ? way.getNodesCount() - 2 : i + 2));
74                    nodes.add(way.getNode(i + 1 < way.getNodesCount() ? i + 1 : way.isClosed() ? 1 : i - 2));
75                }
76                if (selectedNodes.isEmpty())
77                    break;
78            } else if (selectedNodesCount == 2 && selectedNodes.size() == 1)
79                nodes.add(node);
80            i++;
81            if (i >= way.getNodesCount() && way.isClosed())
82                i = 0;
83            iterated = true;
84        }
85
86        if (nodes.size() < 3) {
87            new Notification(tr("Internal error: number of nodes is {0}.", nodes.size()))
88            .setIcon(JOptionPane.ERROR_MESSAGE).show();
89            return;
90        }
91
92        // Now, we have an ordered list of nodes, of which idx 0 and N-1 serve as guides
93        // and 1..N-2 should be aligned with them
94        List<Command> commands = new ArrayList<>();
95        double ax = nodes.get(0).getEastNorth().east();
96        double ay = nodes.get(0).getEastNorth().north();
97        double bx = nodes.get(nodes.size() - 1).getEastNorth().east();
98        double by = nodes.get(nodes.size() - 1).getEastNorth().north();
99
100        for (i = 1; i + 1 < nodes.size(); i++) {
101            Node n = nodes.get(i);
102
103            // Algorithm is copied from org.openstreetmap.josm.actions.AlignInLineAction
104            double nx = n.getEastNorth().east();
105            double ny = n.getEastNorth().north();
106
107            if (ax == bx) {
108                // Special case if AB is vertical...
109                nx = ax;
110            } else if (ay == by) {
111                // ...or horizontal
112                ny = ay;
113            } else {
114                // Otherwise calculate position by solving y=mx+c (simplified)
115                double m1 = (by - ay) / (bx - ax);
116                double c1 = ay - (ax * m1);
117                double m2 = -1.0 / m1;
118                double c2 = ny - (nx * m2);
119
120                nx = (c2 - c1) / (m1 - m2);
121                ny = (m1 * nx) + c1;
122            }
123
124            // Add the command to move the node to its new position.
125            if (Math.abs(nx - n.getEastNorth().east()) > MOVE_THRESHOLD && Math.abs(ny - n.getEastNorth().north()) > MOVE_THRESHOLD)
126                commands.add(new MoveCommand(n, nx - n.getEastNorth().east(), ny - n.getEastNorth().north()));
127        }
128
129        if (!commands.isEmpty())
130            UndoRedoHandler.getInstance().add(new SequenceCommand(TITLE, commands));
131    }
132
133    @Override
134    protected void updateEnabledState() {
135        updateEnabledStateOnCurrentSelection();
136    }
137
138    @Override
139    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
140        Set<Node> nodes = filterNodes(selection);
141        Set<Way> ways = findCommonWays(nodes);
142        setEnabled(ways != null && ways.size() == 1 && !nodes.isEmpty());
143    }
144
145    private Set<Way> findCommonWays(Set<Node> nodes) {
146        Set<Way> ways = null;
147        for (Node n : nodes.stream().filter(n -> n.getDataSet() != null).collect(Collectors.toList())) {
148            if (ways == null)
149                ways = new HashSet<>(n.getParentWays());
150            else {
151                ways.retainAll(n.getParentWays());
152            }
153        }
154        return ways;
155    }
156
157    private Set<Node> filterNodes(Collection<? extends OsmPrimitive> selection) {
158        Set<Node> result = new HashSet<>();
159        if (selection != null) {
160            for (OsmPrimitive p : selection) {
161                if (p instanceof Node)
162                    result.add((Node) p);
163            }
164        }
165        return result;
166    }
167
168    /**
169     * Find the largest empty span between nodes and returns the index of the node right after it.
170     *
171     * TODO: not the maximum node count, but maximum distance!
172     * @param way the way
173     * @param nodes the selected nodes
174     * @return the index of the node right after the largest empty span
175     */
176    private int findFirstNode(Way way, Set<Node> nodes) {
177        int pos = 0;
178        while (pos < way.getNodesCount() && !nodes.contains(way.getNode(pos))) {
179            pos++;
180        }
181        if (pos >= way.getNodesCount())
182            return 0;
183        if (!way.isClosed() || nodes.size() <= 1)
184            return pos;
185
186        // now, way is closed
187        boolean fullCircle = false;
188        int maxLength = 0;
189        int lastPos = 0;
190        while (!fullCircle) {
191            int length = 0;
192            boolean skippedFirst = false;
193            while (!(skippedFirst && nodes.contains(way.getNode(pos)))) {
194                skippedFirst = true;
195                length++;
196                pos++;
197                if (pos >= way.getNodesCount()) {
198                    pos = 0;
199                    fullCircle = true;
200                }
201            }
202            if (length > maxLength) {
203                maxLength = length;
204                lastPos = pos;
205            }
206        }
207        return lastPos;
208    }
209}
Note: See TracBrowser for help on using the repository browser.