001 // License: GPL. Copyright 2009 by Immanuel Scholz and others
002 package org.openstreetmap.josm.actions;
003
004 import static org.openstreetmap.josm.tools.I18n.tr;
005 import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
006
007 import java.awt.event.ActionEvent;
008 import java.awt.event.KeyEvent;
009 import java.util.Collection;
010 import java.util.LinkedList;
011
012 import javax.swing.JOptionPane;
013
014 import org.openstreetmap.josm.Main;
015 import org.openstreetmap.josm.command.Command;
016 import org.openstreetmap.josm.command.MoveCommand;
017 import org.openstreetmap.josm.command.SequenceCommand;
018 import org.openstreetmap.josm.data.osm.Node;
019 import org.openstreetmap.josm.data.osm.OsmPrimitive;
020 import org.openstreetmap.josm.data.osm.Way;
021 import org.openstreetmap.josm.tools.Shortcut;
022
023 /**
024 * Distributes the selected nodes to equal distances along a line.
025 *
026 * @author Teemu Koskinen
027 */
028 public final class DistributeAction extends JosmAction {
029
030 public DistributeAction() {
031 super(tr("Distribute Nodes"), "distribute", tr("Distribute the selected nodes to equal distances along a line."),
032 Shortcut.registerShortcut("tools:distribute", tr("Tool: {0}", tr("Distribute Nodes")), KeyEvent.VK_B,
033 Shortcut.SHIFT), true);
034 putValue("help", ht("/Action/DistributeNodes"));
035 }
036
037 /**
038 * The general algorithm here is to find the two selected nodes
039 * that are furthest apart, and then to distribute all other selected
040 * nodes along the straight line between these nodes.
041 */
042 public void actionPerformed(ActionEvent e) {
043 if (!isEnabled())
044 return;
045 Collection<OsmPrimitive> sel = getCurrentDataSet().getSelected();
046 Collection<Node> nodes = new LinkedList<Node>();
047 Collection<Node> itnodes = new LinkedList<Node>();
048 for (OsmPrimitive osm : sel)
049 if (osm instanceof Node) {
050 nodes.add((Node)osm);
051 itnodes.add((Node)osm);
052 }
053 // special case if no single nodes are selected and exactly one way is:
054 // then use the way's nodes
055 if ((nodes.size() == 0) && (sel.size() == 1)) {
056 for (OsmPrimitive osm : sel)
057 if (osm instanceof Way) {
058 nodes.addAll(((Way)osm).getNodes());
059 itnodes.addAll(((Way)osm).getNodes());
060 }
061 }
062
063 if (nodes.size() < 3) {
064 JOptionPane.showMessageDialog(
065 Main.parent,
066 tr("Please select at least three nodes."),
067 tr("Information"),
068 JOptionPane.INFORMATION_MESSAGE
069 );
070 return;
071 }
072
073 // Find from the selected nodes two that are the furthest apart.
074 // Let's call them A and B.
075 double distance = 0;
076
077 Node nodea = null;
078 Node nodeb = null;
079
080 for (Node n : nodes) {
081 itnodes.remove(n);
082 for (Node m : itnodes) {
083 double dist = Math.sqrt(n.getEastNorth().distance(m.getEastNorth()));
084 if (dist > distance) {
085 nodea = n;
086 nodeb = m;
087 distance = dist;
088 }
089 }
090 }
091
092 // Remove the nodes A and B from the list of nodes to move
093 nodes.remove(nodea);
094 nodes.remove(nodeb);
095
096 // Find out co-ords of A and B
097 double ax = nodea.getEastNorth().east();
098 double ay = nodea.getEastNorth().north();
099 double bx = nodeb.getEastNorth().east();
100 double by = nodeb.getEastNorth().north();
101
102 // A list of commands to do
103 Collection<Command> cmds = new LinkedList<Command>();
104
105 // Amount of nodes between A and B plus 1
106 int num = nodes.size()+1;
107
108 // Current number of node
109 int pos = 0;
110 while (nodes.size() > 0) {
111 pos++;
112 Node s = null;
113
114 // Find the node that is furthest from B (i.e. closest to A)
115 distance = 0.0;
116 for (Node n : nodes) {
117 double dist = Math.sqrt(nodeb.getEastNorth().distance(n.getEastNorth()));
118 if (dist > distance) {
119 s = n;
120 distance = dist;
121 }
122 }
123
124 // First move the node to A's position, then move it towards B
125 double dx = ax - s.getEastNorth().east() + (bx-ax)*pos/num;
126 double dy = ay - s.getEastNorth().north() + (by-ay)*pos/num;
127
128 cmds.add(new MoveCommand(s, dx, dy));
129
130 //remove moved node from the list
131 nodes.remove(s);
132 }
133
134 // Do it!
135 Main.main.undoRedo.add(new SequenceCommand(tr("Distribute Nodes"), cmds));
136 Main.map.repaint();
137 }
138
139 @Override
140 protected void updateEnabledState() {
141 if (getCurrentDataSet() == null) {
142 setEnabled(false);
143 } else {
144 updateEnabledState(getCurrentDataSet().getSelected());
145 }
146 }
147
148 @Override
149 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
150 setEnabled(selection != null && !selection.isEmpty());
151 }
152 }