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 }