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

Last change on this file since 8447 was 8377, checked in by Don-vip, 9 years ago

performance - remove useless boxing of boolean constants

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