source: josm/trunk/src/org/openstreetmap/josm/actions/DistributeAction.java@ 9242

Last change on this file since 9242 was 9226, checked in by Don-vip, 8 years ago

attempt to fix shortcut parse issue on DevelopersGuide/ShortcutsList

  • Property svn:eol-style set to native
File size: 10.9 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.marktr;
6import static org.openstreetmap.josm.tools.I18n.tr;
7
8import java.awt.event.ActionEvent;
9import java.awt.event.KeyEvent;
10import java.util.Collection;
11import java.util.HashSet;
12import java.util.Iterator;
13import java.util.LinkedList;
14import java.util.List;
15import java.util.Set;
16
17import javax.swing.JOptionPane;
18
19import org.openstreetmap.josm.Main;
20import org.openstreetmap.josm.command.Command;
21import org.openstreetmap.josm.command.MoveCommand;
22import org.openstreetmap.josm.command.SequenceCommand;
23import org.openstreetmap.josm.data.osm.Node;
24import org.openstreetmap.josm.data.osm.OsmPrimitive;
25import org.openstreetmap.josm.data.osm.Way;
26import org.openstreetmap.josm.gui.Notification;
27import org.openstreetmap.josm.tools.Shortcut;
28
29/**
30 * Distributes the selected nodes to equal distances along a line.
31 *
32 * @author Teemu Koskinen
33 */
34public final class DistributeAction extends JosmAction {
35
36 private static final String ACTION_SHORT_NAME = marktr("Distribute Nodes");
37
38 /**
39 * Constructs a new {@code DistributeAction}.
40 */
41 public DistributeAction() {
42 super(tr(ACTION_SHORT_NAME), "distribute",
43 tr("Distribute the selected nodes to equal distances along a line."),
44 Shortcut.registerShortcut("tools:distribute", tr("Tool: {0}", tr(ACTION_SHORT_NAME)), KeyEvent.VK_B, Shortcut.SHIFT),
45 true);
46 putValue("help", ht("/Action/DistributeNodes"));
47 }
48
49 /**
50 * Perform action.
51 * Select method according to user selection.
52 * Case 1: One Way (no self-crossing) and at most 2 nodes contains by this way:
53 * Distribute nodes keeping order along the way
54 * Case 2: Other
55 * Distribute nodes
56 */
57 @Override
58 public void actionPerformed(ActionEvent e) {
59 if (!isEnabled())
60 return;
61
62 // Collect user selected objects
63 Collection<OsmPrimitive> selected = getCurrentDataSet().getSelected();
64 Collection<Way> ways = new LinkedList<>();
65 Collection<Node> nodes = new HashSet<>();
66 for (OsmPrimitive osm : selected) {
67 if (osm instanceof Node) {
68 nodes.add((Node) osm);
69 } else if (osm instanceof Way) {
70 ways.add((Way) osm);
71 }
72 }
73
74 Set<Node> ignoredNodes = removeNodesWithoutCoordinates(nodes);
75 if (!ignoredNodes.isEmpty()) {
76 Main.warn(tr("Ignoring {0} nodes with null coordinates", ignoredNodes.size()));
77 ignoredNodes.clear();
78 }
79
80 // Switch between algorithms
81 Collection<Command> cmds;
82 if (checkDistributeWay(ways, nodes)) {
83 cmds = distributeWay(ways, nodes);
84 } else if (checkDistributeNodes(ways, nodes)) {
85 cmds = distributeNodes(nodes);
86 } else {
87 new Notification(
88 tr("Please select :\n" +
89 "* One no self-crossing way with at most two of its nodes;\n" +
90 "* Three nodes."))
91 .setIcon(JOptionPane.INFORMATION_MESSAGE)
92 .setDuration(Notification.TIME_SHORT)
93 .show();
94 return;
95 }
96
97 if (cmds.isEmpty()) {
98 return;
99 }
100
101 // Do it!
102 Main.main.undoRedo.add(new SequenceCommand(tr(ACTION_SHORT_NAME), cmds));
103 Main.map.repaint();
104 }
105
106 /**
107 * Test if one way, no self-crossing, is selected with at most two of its nodes.
108 * @param ways Selected ways
109 * @param nodes Selected nodes
110 * @return true in this case
111 */
112 private static boolean checkDistributeWay(Collection<Way> ways, Collection<Node> nodes) {
113 if (ways.size() == 1 && nodes.size() <= 2) {
114 Way w = ways.iterator().next();
115 Set<Node> unduplicated = new HashSet<>(w.getNodes());
116 if (unduplicated.size() != w.getNodesCount()) {
117 // No self crossing way
118 return false;
119 }
120 for (Node node: nodes) {
121 if (!w.containsNode(node)) {
122 return false;
123 }
124 }
125 return true;
126 }
127 return false;
128 }
129
130 /**
131 * Distribute nodes contained by a way, keeping nodes order.
132 * If one or two nodes are selected, keep these nodes in place.
133 * @param ways Selected ways, must be collection of size 1.
134 * @param nodes Selected nodes, at most two nodes.
135 * @return Collection of command to be executed.
136 */
137 private static Collection<Command> distributeWay(Collection<Way> ways,
138 Collection<Node> nodes) {
139 Way w = ways.iterator().next();
140 Collection<Command> cmds = new LinkedList<>();
141
142 if (w.getNodesCount() == nodes.size() || w.getNodesCount() <= 2) {
143 // Nothing to do
144 return cmds;
145 }
146
147 double xa, ya; // Start point
148 double dx, dy; // Segment increment
149 if (nodes.isEmpty()) {
150 Node na = w.firstNode();
151 nodes.add(na);
152 Node nb = w.lastNode();
153 nodes.add(nb);
154 xa = na.getEastNorth().east();
155 ya = na.getEastNorth().north();
156 dx = (nb.getEastNorth().east() - xa) / (w.getNodesCount() - 1);
157 dy = (nb.getEastNorth().north() - ya) / (w.getNodesCount() - 1);
158 } else if (nodes.size() == 1) {
159 Node n = nodes.iterator().next();
160 int nIdx = w.getNodes().indexOf(n);
161 Node na = w.firstNode();
162 Node nb = w.lastNode();
163 dx = (nb.getEastNorth().east() - na.getEastNorth().east()) /
164 (w.getNodesCount() - 1);
165 dy = (nb.getEastNorth().north() - na.getEastNorth().north()) /
166 (w.getNodesCount() - 1);
167 xa = n.getEastNorth().east() - dx * nIdx;
168 ya = n.getEastNorth().north() - dy * nIdx;
169 } else {
170 Iterator<Node> it = nodes.iterator();
171 Node na = it.next();
172 Node nb = it.next();
173 List<Node> wayNodes = w.getNodes();
174 int naIdx = wayNodes.indexOf(na);
175 int nbIdx = wayNodes.indexOf(nb);
176 dx = (nb.getEastNorth().east() - na.getEastNorth().east()) / (nbIdx - naIdx);
177 dy = (nb.getEastNorth().north() - na.getEastNorth().north()) / (nbIdx - naIdx);
178 xa = na.getEastNorth().east() - dx * naIdx;
179 ya = na.getEastNorth().north() - dy * naIdx;
180 }
181
182 for (int i = 0; i < w.getNodesCount(); i++) {
183 Node n = w.getNode(i);
184 if (!n.isLatLonKnown() || nodes.contains(n)) {
185 continue;
186 }
187 double x = xa + i * dx;
188 double y = ya + i * dy;
189 cmds.add(new MoveCommand(n, x - n.getEastNorth().east(),
190 y - n.getEastNorth().north()));
191 }
192 return cmds;
193 }
194
195 /**
196 * Test if nodes oriented algorithm applies to the selection.
197 * @param ways Selected ways
198 * @param nodes Selected nodes
199 * @return true in this case
200 */
201 private static Boolean checkDistributeNodes(Collection<Way> ways, Collection<Node> nodes) {
202 return ways.isEmpty() && nodes.size() >= 3;
203 }
204
205 /**
206 * Distribute nodes when only nodes are selected.
207 * The general algorithm here is to find the two selected nodes
208 * that are furthest apart, and then to distribute all other selected
209 * nodes along the straight line between these nodes.
210 * @param nodes nodes to distribute
211 * @return Commands to execute to perform action
212 * @throws IllegalArgumentException if nodes is empty
213 */
214 private static Collection<Command> distributeNodes(Collection<Node> nodes) {
215 // Find from the selected nodes two that are the furthest apart.
216 // Let's call them A and B.
217 double distance = 0;
218
219 Node nodea = null;
220 Node nodeb = null;
221
222 Collection<Node> itnodes = new LinkedList<>(nodes);
223 for (Node n : nodes) {
224 itnodes.remove(n);
225 for (Node m : itnodes) {
226 double dist = Math.sqrt(n.getEastNorth().distance(m.getEastNorth()));
227 if (dist > distance) {
228 nodea = n;
229 nodeb = m;
230 distance = dist;
231 }
232 }
233 }
234
235 if (nodea == null || nodeb == null) {
236 throw new IllegalArgumentException();
237 }
238
239 // Remove the nodes A and B from the list of nodes to move
240 nodes.remove(nodea);
241 nodes.remove(nodeb);
242
243 // Find out co-ords of A and B
244 double ax = nodea.getEastNorth().east();
245 double ay = nodea.getEastNorth().north();
246 double bx = nodeb.getEastNorth().east();
247 double by = nodeb.getEastNorth().north();
248
249 // A list of commands to do
250 Collection<Command> cmds = new LinkedList<>();
251
252 // Amount of nodes between A and B plus 1
253 int num = nodes.size()+1;
254
255 // Current number of node
256 int pos = 0;
257 while (!nodes.isEmpty()) {
258 pos++;
259 Node s = null;
260
261 // Find the node that is furthest from B (i.e. closest to A)
262 distance = 0.0;
263 for (Node n : nodes) {
264 double dist = Math.sqrt(nodeb.getEastNorth().distance(n.getEastNorth()));
265 if (dist > distance) {
266 s = n;
267 distance = dist;
268 }
269 }
270
271 if (s != null) {
272 // First move the node to A's position, then move it towards B
273 double dx = ax - s.getEastNorth().east() + (bx-ax)*pos/num;
274 double dy = ay - s.getEastNorth().north() + (by-ay)*pos/num;
275
276 cmds.add(new MoveCommand(s, dx, dy));
277
278 //remove moved node from the list
279 nodes.remove(s);
280 }
281 }
282
283 return cmds;
284 }
285
286 /**
287 * Remove nodes without knowned coordinates from a collection.
288 * @param col Collection of nodes to check
289 * @return Set of nodes without coordinates
290 */
291 private static Set<Node> removeNodesWithoutCoordinates(Collection<Node> col) {
292 Set<Node> result = new HashSet<>();
293 for (Iterator<Node> it = col.iterator(); it.hasNext();) {
294 Node n = it.next();
295 if (!n.isLatLonKnown()) {
296 it.remove();
297 result.add(n);
298 }
299 }
300 return result;
301 }
302
303 @Override
304 protected void updateEnabledState() {
305 if (getCurrentDataSet() == null) {
306 setEnabled(false);
307 } else {
308 updateEnabledState(getCurrentDataSet().getSelected());
309 }
310 }
311
312 @Override
313 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
314 setEnabled(selection != null && !selection.isEmpty());
315 }
316}
Note: See TracBrowser for help on using the repository browser.