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

Last change on this file since 1102 was 1090, checked in by framm, 15 years ago
  • Property svn:eol-style set to native
File size: 9.3 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;
5
6import java.awt.event.ActionEvent;
7import java.awt.event.KeyEvent;
8import java.util.Collection;
9import java.util.LinkedList;
10
11import javax.swing.JOptionPane;
12
13import org.openstreetmap.josm.Main;
14import org.openstreetmap.josm.command.Command;
15import org.openstreetmap.josm.command.MoveCommand;
16import org.openstreetmap.josm.command.SequenceCommand;
17import org.openstreetmap.josm.data.coor.EastNorth;
18import org.openstreetmap.josm.data.coor.LatLon;
19import org.openstreetmap.josm.data.osm.Node;
20import org.openstreetmap.josm.data.osm.OsmPrimitive;
21import org.openstreetmap.josm.data.osm.Way;
22import org.openstreetmap.josm.tools.Shortcut;
23
24/**
25 * Aligns all selected nodes within a circle. (Useful for roundabouts)
26 *
27 * @author Matthew Newton
28 * @author Petr Dlouhý
29 */
30public final class AlignInCircleAction extends JosmAction {
31
32 public AlignInCircleAction() {
33 super(tr("Align Nodes in Circle"), "aligncircle", tr("Move the selected nodes into a circle."),
34 Shortcut.registerShortcut("tools:aligncircle", tr("Tool: {0}", tr("Align Nodes in Circle")),
35 KeyEvent.VK_O, Shortcut.GROUP_EDIT), true);
36 }
37
38 public double determinant(double[][] mat) {
39 double result = 0;
40
41 if (mat.length == 1) {
42 result = mat[0][0];
43 return result;
44 }
45
46 if (mat.length == 2) {
47 result = mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0];
48 return result;
49 }
50
51 for (int i = 0; i < mat[0].length; i++) {
52 double temp[][] = new double[mat.length - 1][mat[0].length - 1];
53 for (int j = 1; j < mat.length; j++) {
54 for (int k = 0; k < mat[0].length; k++) {
55 if (k < i) {
56 temp[j - 1][k] = mat[j][k];
57 } else if (k > i) {
58 temp[j - 1][k - 1] = mat[j][k];
59 }
60 }
61 }
62 result += mat[0][i] * Math.pow(-1, (double) i) * determinant(temp);
63 }
64 return result;
65 }
66
67 public double distance(EastNorth n, EastNorth m) {
68 double easd, nord;
69 easd = n.east() - m.east();
70 nord = n.north() - m.north();
71 return Math.sqrt(easd * easd + nord * nord);
72 }
73
74 public EastNorth circumcenter(EastNorth i, EastNorth j, EastNorth k) {
75 // move to 0,0, to eliminate numeric errors
76 double ie = i.east() - i.east();
77 double in = i.north() - i.north();
78 double je = j.east() - i.east();
79 double jn = j.north() - i.north();
80 double ke = k.east() - i.east();
81 double kn = k.north() - i.north();
82 double[][] ma = { { ie, in, 1 }, { je, jn, 1 }, { ke, kn, 1 } };
83 double[][] mbx = { { (ie * ie + in * in), in, 1 }, { (je * je + jn * jn), jn, 1 },
84 { (ke * ke + kn * kn), kn, 1 } };
85 double[][] mby = { { ie * ie + in * in, ie, 1 }, { je * je + jn * jn, je, 1 }, { ke * ke + kn * kn, ke, 1 } };
86 double a = determinant(ma);
87 double bx = determinant(mbx);
88 double by = determinant(mby);
89 EastNorth result = new EastNorth(bx / (2 * a) + i.east(), -by / (2 * a) + i.north());
90
91 Node n = new Node(Main.proj.eastNorth2latlon(result));
92 if (n.coor.isOutSideWorld()) {
93 JOptionPane.showMessageDialog(Main.parent, tr("Some of the nodes are (almost) in the line"));
94 return null;
95 }
96 return result;
97 }
98
99 public class PolarCoor {
100 double radius;
101 double angle;
102 EastNorth origin = new EastNorth(0, 0);
103 double azimuth = 0;
104
105 PolarCoor(double radius, double angle) {
106 this(radius, angle, new EastNorth(0, 0), 0);
107 }
108
109 PolarCoor(double radius, double angle, EastNorth origin, double azimuth) {
110 this.radius = radius;
111 this.angle = angle;
112 this.origin = origin;
113 this.azimuth = azimuth;
114 }
115
116 PolarCoor(EastNorth en) {
117 this(en, new EastNorth(0, 0), 0);
118 }
119
120 PolarCoor(EastNorth en, EastNorth origin, double azimuth) {
121 radius = distance(en, origin);
122 angle = Math.atan2(en.north() - origin.north(), en.east() - origin.east());
123 this.origin = origin;
124 this.azimuth = azimuth;
125 }
126
127 public EastNorth toEastNorth() {
128 return new EastNorth(radius * Math.cos(angle - azimuth) + origin.east(), radius * Math.sin(angle - azimuth)
129 + origin.north());
130 }
131 }
132
133 public void actionPerformed(ActionEvent e) {
134 Collection<OsmPrimitive> sel = Main.ds.getSelected();
135 Collection<Node> nodes = new LinkedList<Node>();
136 Collection<Way> ways = new LinkedList<Way>();
137 Node center = null;
138 Node node = null;
139 double radius = 0;
140 boolean regular = false;
141
142 for (OsmPrimitive osm : sel) {
143 if (osm instanceof Node)
144 nodes.add((Node) osm);
145 else if (osm instanceof Way)
146 ways.add((Way) osm);
147 }
148
149 // special case if no single nodes are selected and exactly one way is:
150 // then use the way's nodes
151 if ((nodes.size() <= 2) && (ways.size() == 1)) {
152 Way way = (Way) ways.toArray()[0];
153
154 // some more special combinations:
155 // When is selected node that is part of the way, then make a regular polygon, selected
156 // node doesn't move.
157 // I haven't got better idea, how to activate that function.
158 //
159 // When one way and one node is selected, set center to position of that node.
160 // When one more node, part of the way, is selected, set the radius equal to the
161 // distance between two nodes.
162 if (nodes.size() > 0) {
163 if (nodes.size() == 1 && way.nodes.contains((Node) nodes.toArray()[0])) {
164 node = (Node) nodes.toArray()[0];
165 regular = true;
166 } else {
167
168 center = (Node) nodes.toArray()[way.nodes.contains((Node) nodes.toArray()[0]) ? 1 : 0];
169 if (nodes.size() == 2)
170 radius = distance(((Node) nodes.toArray()[0]).eastNorth, ((Node) nodes.toArray()[1]).eastNorth);
171 }
172 nodes = new LinkedList<Node>();
173 }
174
175 for (Node n : way.nodes) {
176 if (!nodes.contains(n))
177 nodes.add(n);
178 }
179 }
180
181 if (nodes.size() < 4) {
182 JOptionPane.showMessageDialog(Main.parent, tr("Please select at least four nodes."));
183 return;
184 }
185
186 // Get average position of circumcircles of the triangles of all triplets of neighbour nodes
187 if (center == null) {
188 center = new Node(new LatLon(0, 0));
189 Node n0, n1, n2, prvni, druhy;
190 n0 = (Node) nodes.toArray()[nodes.size() - 1];
191 n1 = (Node) nodes.toArray()[nodes.size() - 2];
192 for (Node n : nodes) {
193 n2 = n1;
194 n1 = n0;
195 n0 = n;
196 EastNorth cc = circumcenter(n0.eastNorth, n1.eastNorth, n2.eastNorth);
197 if (cc == null)
198 return;
199 center.eastNorth = new EastNorth(center.eastNorth.east() + cc.east(), center.eastNorth.north()
200 + cc.north());
201 }
202
203 center.eastNorth = new EastNorth(center.eastNorth.east() / nodes.size(), center.eastNorth.north()
204 / nodes.size());
205 center.coor = Main.proj.eastNorth2latlon(center.eastNorth);
206 }
207
208 // Node "center" now is central to all selected nodes.
209
210 // Now calculate the average distance to each node from the
211 // centre. This method is ok as long as distances are short
212 // relative to the distance from the N or S poles.
213 if (radius == 0) {
214 for (Node n : nodes) {
215 radius += distance(center.eastNorth, n.eastNorth);
216 }
217 radius = radius / nodes.size();
218 }
219
220 Collection<Command> cmds = new LinkedList<Command>();
221
222 PolarCoor pc;
223
224 if (regular) { // Make a regular polygon
225 double angle = Math.PI * 2 / nodes.size();
226 pc = new PolarCoor(((Node) nodes.toArray()[0]).eastNorth, center.eastNorth, 0);
227
228 if (pc.angle > (new PolarCoor(((Node) nodes.toArray()[1]).eastNorth, center.eastNorth, 0).angle))
229 angle *= -1;
230
231 pc.radius = radius;
232 for (Node n : nodes) {
233 EastNorth no = pc.toEastNorth();
234 cmds.add(new MoveCommand(n, no.east() - n.eastNorth.east(), no.north() - n.eastNorth.north()));
235 pc.angle += angle;
236 }
237 } else { // Move each node to that distance from the centre.
238 for (Node n : nodes) {
239 pc = new PolarCoor(n.eastNorth, center.eastNorth, 0);
240 pc.radius = radius;
241 EastNorth no = pc.toEastNorth();
242 cmds.add(new MoveCommand(n, no.east() - n.eastNorth.east(), no.north() - n.eastNorth.north()));
243 }
244 }
245
246 Main.main.undoRedo.add(new SequenceCommand(tr("Align Nodes in Circle"), cmds));
247 Main.map.repaint();
248 }
249}
Note: See TracBrowser for help on using the repository browser.