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

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

sonar - fix some errors, mainly NPEs

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