source: josm/trunk/src/org/openstreetmap/josm/actions/AlignInCircleAction.java@ 3747

Last change on this file since 3747 was 2323, checked in by Gubaer, 14 years ago

Added explicit help topics
See also current list of help topics with links to source files and to help pages

  • Property svn:eol-style set to native
File size: 9.9 KB
Line 
1//License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.actions;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
6
7import java.awt.event.ActionEvent;
8import java.awt.event.KeyEvent;
9import java.math.BigDecimal;
10import java.math.MathContext;
11import java.util.Collection;
12import java.util.LinkedList;
13import java.util.List;
14
15import javax.swing.JOptionPane;
16
17import org.openstreetmap.josm.Main;
18import org.openstreetmap.josm.command.Command;
19import org.openstreetmap.josm.command.MoveCommand;
20import org.openstreetmap.josm.command.SequenceCommand;
21import org.openstreetmap.josm.data.coor.EastNorth;
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.tools.Shortcut;
26
27/**
28 * Aligns all selected nodes within a circle. (Useful for roundabouts)
29 *
30 * @author Matthew Newton
31 * @author Petr Dlouhý
32 * @author Teemu Koskinen
33 */
34public final class AlignInCircleAction extends JosmAction {
35
36 public AlignInCircleAction() {
37 super(tr("Align Nodes in Circle"), "aligncircle", tr("Move the selected nodes into a circle."),
38 Shortcut.registerShortcut("tools:aligncircle", tr("Tool: {0}", tr("Align Nodes in Circle")),
39 KeyEvent.VK_O, Shortcut.GROUP_EDIT), true);
40 putValue("help", ht("/Action/AlignInCircle"));
41 }
42
43 public double distance(EastNorth n, EastNorth m) {
44 double easd, nord;
45 easd = n.east() - m.east();
46 nord = n.north() - m.north();
47 return Math.sqrt(easd * easd + nord * nord);
48 }
49
50 public class PolarCoor {
51 double radius;
52 double angle;
53 EastNorth origin = new EastNorth(0, 0);
54 double azimuth = 0;
55
56 PolarCoor(double radius, double angle) {
57 this(radius, angle, new EastNorth(0, 0), 0);
58 }
59
60 PolarCoor(double radius, double angle, EastNorth origin, double azimuth) {
61 this.radius = radius;
62 this.angle = angle;
63 this.origin = origin;
64 this.azimuth = azimuth;
65 }
66
67 PolarCoor(EastNorth en) {
68 this(en, new EastNorth(0, 0), 0);
69 }
70
71 PolarCoor(EastNorth en, EastNorth origin, double azimuth) {
72 radius = distance(en, origin);
73 angle = Math.atan2(en.north() - origin.north(), en.east() - origin.east());
74 this.origin = origin;
75 this.azimuth = azimuth;
76 }
77
78 public EastNorth toEastNorth() {
79 return new EastNorth(radius * Math.cos(angle - azimuth) + origin.east(), radius * Math.sin(angle - azimuth)
80 + origin.north());
81 }
82 }
83
84 public void actionPerformed(ActionEvent e) {
85 if (!isEnabled())
86 return;
87
88 Collection<OsmPrimitive> sel = getCurrentDataSet().getSelected();
89 List<Node> nodes = new LinkedList<Node>();
90 List<Way> ways = new LinkedList<Way>();
91 EastNorth center = null;
92 double radius = 0;
93 boolean regular = false;
94
95 for (OsmPrimitive osm : sel) {
96 if (osm instanceof Node) {
97 nodes.add((Node) osm);
98 } else if (osm instanceof Way) {
99 ways.add((Way) osm);
100 }
101 }
102
103 // special case if no single nodes are selected and exactly one way is:
104 // then use the way's nodes
105 if ((nodes.size() <= 2) && (ways.size() == 1)) {
106 Way way = ways.get(0);
107
108 // some more special combinations:
109 // When is selected node that is part of the way, then make a regular polygon, selected
110 // node doesn't move.
111 // I haven't got better idea, how to activate that function.
112 //
113 // When one way and one node is selected, set center to position of that node.
114 // When one more node, part of the way, is selected, set the radius equal to the
115 // distance between two nodes.
116 if (nodes.size() > 0) {
117 if (nodes.size() == 1 && way.containsNode(nodes.get(0))) {
118 regular = true;
119 } else {
120
121 center = nodes.get(way.containsNode(nodes.get(0)) ? 1 : 0).getEastNorth();
122 if (nodes.size() == 2) {
123 radius = distance(nodes.get(0).getEastNorth(), nodes.get(1).getEastNorth());
124 }
125 }
126 nodes = new LinkedList<Node>();
127 }
128
129 for (Node n : way.getNodes()) {
130 if (!nodes.contains(n)) {
131 nodes.add(n);
132 }
133 }
134 }
135
136 if (nodes.size() < 4) {
137 JOptionPane.showMessageDialog(
138 Main.parent,
139 tr("Please select at least four nodes."),
140 tr("Information"),
141 JOptionPane.INFORMATION_MESSAGE
142 );
143 return;
144 }
145
146 // Reorder the nodes if they didn't come from a single way
147 if (ways.size() != 1) {
148 // First calculate the average point
149
150 BigDecimal east = new BigDecimal(0);
151 BigDecimal north = new BigDecimal(0);
152
153 for (Node n : nodes) {
154 BigDecimal x = new BigDecimal(n.getEastNorth().east());
155 BigDecimal y = new BigDecimal(n.getEastNorth().north());
156 east = east.add(x, MathContext.DECIMAL128);
157 north = north.add(y, MathContext.DECIMAL128);
158 }
159 BigDecimal nodesSize = new BigDecimal(nodes.size());
160 east = east.divide(nodesSize, MathContext.DECIMAL128);
161 north = north.divide(nodesSize, MathContext.DECIMAL128);
162
163 EastNorth average = new EastNorth(east.doubleValue(), north.doubleValue());
164 List<Node> newNodes = new LinkedList<Node>();
165
166 // Then reorder them based on heading from the average point
167 while (!nodes.isEmpty()) {
168 double maxHeading = -1.0;
169 Node maxNode = null;
170 for (Node n : nodes) {
171 double heading = average.heading(n.getEastNorth());
172 if (heading > maxHeading) {
173 maxHeading = heading;
174 maxNode = n;
175 }
176 }
177 newNodes.add(maxNode);
178 nodes.remove(maxNode);
179 }
180
181 nodes = newNodes;
182 }
183
184 if (center == null) {
185 // Compute the centroid of nodes
186
187 BigDecimal area = new BigDecimal(0);
188 BigDecimal north = new BigDecimal(0);
189 BigDecimal east = new BigDecimal(0);
190
191 // See http://en.wikipedia.org/w/index.php?title=Centroid&oldid=294224857#Centroid_of_polygon for the equation used here
192 for (int i = 0; i < nodes.size(); i++) {
193 EastNorth n0 = nodes.get(i).getEastNorth();
194 EastNorth n1 = nodes.get((i+1) % nodes.size()).getEastNorth();
195
196 BigDecimal x0 = new BigDecimal(n0.east());
197 BigDecimal y0 = new BigDecimal(n0.north());
198 BigDecimal x1 = new BigDecimal(n1.east());
199 BigDecimal y1 = new BigDecimal(n1.north());
200
201 BigDecimal k = x0.multiply(y1, MathContext.DECIMAL128).subtract(y0.multiply(x1, MathContext.DECIMAL128));
202
203 area = area.add(k, MathContext.DECIMAL128);
204 east = east.add(k.multiply(x0.add(x1, MathContext.DECIMAL128), MathContext.DECIMAL128));
205 north = north.add(k.multiply(y0.add(y1, MathContext.DECIMAL128), MathContext.DECIMAL128));
206
207 }
208
209 BigDecimal d = new BigDecimal(3, MathContext.DECIMAL128); // 1/2 * 6 = 3
210 area = area.multiply(d, MathContext.DECIMAL128);
211 north = north.divide(area, MathContext.DECIMAL128);
212 east = east.divide(area, MathContext.DECIMAL128);
213
214 center = new EastNorth(east.doubleValue(), north.doubleValue());
215
216 }
217 // Node "center" now is central to all selected nodes.
218
219 // Now calculate the average distance to each node from the
220 // centre. This method is ok as long as distances are short
221 // relative to the distance from the N or S poles.
222 if (radius == 0) {
223 for (Node n : nodes) {
224 radius += distance(center, n.getEastNorth());
225 }
226 radius = radius / nodes.size();
227 }
228
229 Collection<Command> cmds = new LinkedList<Command>();
230
231 PolarCoor pc;
232
233 if (regular) { // Make a regular polygon
234 double angle = Math.PI * 2 / nodes.size();
235 pc = new PolarCoor(nodes.get(0).getEastNorth(), center, 0);
236
237 if (pc.angle > (new PolarCoor(nodes.get(1).getEastNorth(), center, 0).angle)) {
238 angle *= -1;
239 }
240
241 pc.radius = radius;
242 for (Node n : nodes) {
243 EastNorth no = pc.toEastNorth();
244 cmds.add(new MoveCommand(n, no.east() - n.getEastNorth().east(), no.north() - n.getEastNorth().north()));
245 pc.angle += angle;
246 }
247 } else { // Move each node to that distance from the centre.
248 for (Node n : nodes) {
249 pc = new PolarCoor(n.getEastNorth(), center, 0);
250 pc.radius = radius;
251 EastNorth no = pc.toEastNorth();
252 cmds.add(new MoveCommand(n, no.east() - n.getEastNorth().east(), no.north() - n.getEastNorth().north()));
253 }
254 }
255
256 Main.main.undoRedo.add(new SequenceCommand(tr("Align Nodes in Circle"), cmds));
257 Main.map.repaint();
258 }
259
260 @Override
261 protected void updateEnabledState() {
262 setEnabled(getCurrentDataSet() != null && !getCurrentDataSet().getSelected().isEmpty());
263 }
264
265 @Override
266 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
267 setEnabled(selection != null && !selection.isEmpty());
268 }
269}
Note: See TracBrowser for help on using the repository browser.