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    }