source: josm/trunk/src/org/openstreetmap/josm/actions/CreateCircleAction.java@ 1750

Last change on this file since 1750 was 1741, checked in by stoecker, 15 years ago

fixed #2847 - patch by Landwirt - better circle creation using two point to specify diameter

File size: 10.4 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.AddCommand;
16import org.openstreetmap.josm.command.DeleteCommand;
17import org.openstreetmap.josm.command.ChangeCommand;
18import org.openstreetmap.josm.command.SequenceCommand;
19import org.openstreetmap.josm.data.coor.EastNorth;
20import org.openstreetmap.josm.data.osm.Node;
21import org.openstreetmap.josm.data.osm.OsmPrimitive;
22import org.openstreetmap.josm.data.osm.Way;
23import org.openstreetmap.josm.tools.Shortcut;
24
25import org.openstreetmap.josm.data.osm.visitor.CollectBackReferencesVisitor;
26
27/**
28 * - Create a new circle from two selected nodes or a way with 2 nodes which represent the diameter of the circle.
29 * - Create a new circle from three selected nodes--or a way with 3 nodes.
30 * - Useful for roundabouts
31 *
32 * Note: If a way is selected, it is changed. If nodes are selected a new way is created.
33 * So if you've got a way with nodes it makes a difference between running this on the way or the nodes!
34 *
35 * BTW: Someone might want to implement projection corrections for this...
36 *
37 * @author Henry Loenwind, based on much copy&Paste from other Actions.
38 * @author Sebastian Masch
39 */
40public final class CreateCircleAction extends JosmAction {
41
42 public CreateCircleAction() {
43 super(tr("Create Circle"), "createcircle", tr("Create a circle from three selected nodes."),
44 Shortcut.registerShortcut("tools:createcircle", tr("Tool: {0}", tr("Create Circle")), KeyEvent.VK_O, Shortcut.GROUP_EDIT, Shortcut.SHIFT_DEFAULT), true);
45 }
46
47 private double calcang(double xc, double yc, double x, double y) {
48 // calculate the angle from xc|yc to x|y
49 if (xc == x && yc == y)
50 return 0; // actually invalid, but we won't have this case in this context
51 double yd = Math.abs(y - yc);
52 if (yd == 0 && xc < x)
53 return 0;
54 if (yd == 0 && xc > x)
55 return Math.PI;
56 double xd = Math.abs(x - xc);
57 double a = Math.atan2(xd, yd);
58 if (y > yc) {
59 a = Math.PI - a;
60 }
61 if (x < xc) {
62 a = -a;
63 }
64 a = 1.5*Math.PI + a;
65 if (a < 0) {
66 a += 2*Math.PI;
67 }
68 if (a >= 2*Math.PI) {
69 a -= 2*Math.PI;
70 }
71 return a;
72 }
73
74 public void actionPerformed(ActionEvent e) {
75 int numberOfNodesInCircle = Main.pref.getInteger("createcircle.nodecount", 8);
76 if (numberOfNodesInCircle < 1) {
77 numberOfNodesInCircle = 1;
78 } else if (numberOfNodesInCircle > 100) {
79 numberOfNodesInCircle = 100;
80 }
81
82 Collection<OsmPrimitive> sel = Main.ds.getSelected();
83 Collection<Node> nodes = new LinkedList<Node>();
84 Way existingWay = null;
85
86 for (OsmPrimitive osm : sel)
87 if (osm instanceof Node) {
88 nodes.add((Node)osm);
89 }
90
91 // special case if no single nodes are selected and exactly one way is:
92 // then use the way's nodes
93 if ((nodes.size() == 0) && (sel.size() == 1)) {
94 for (OsmPrimitive osm : sel)
95 if (osm instanceof Way) {
96 existingWay = ((Way)osm);
97 for (Node n : ((Way)osm).nodes)
98 {
99 if(!nodes.contains(n)) {
100 nodes.add(n);
101 }
102 }
103 }
104 }
105
106 // now we can start doing things to OSM data
107 Collection<Command> cmds = new LinkedList<Command>();
108
109 if (nodes.size() == 2) {
110 // diameter: two single nodes needed or a way with two nodes
111
112 Node n1 = ((Node)nodes.toArray()[0]);
113 double x1 = n1.getEastNorth().east();
114 double y1 = n1.getEastNorth().north();
115 Node n2 = ((Node)nodes.toArray()[1]);
116 double x2 = n2.getEastNorth().east();
117 double y2 = n2.getEastNorth().north();
118
119 // calculate the center (xc/yc)
120 double xc = 0.5 * (x1 + x2);
121 double yc = 0.5 * (y1 + y2);
122
123 // calculate the radius (r)
124 double r = Math.sqrt(Math.pow(xc-x1,2) + Math.pow(yc-y1,2));
125
126 // find where to put the existing nodes
127 double a1 = calcang(xc, yc, x1, y1);
128 double a2 = calcang(xc, yc, x2, y2);
129 if (a1 < a2) { double at = a1; Node nt = n1; a1 = a2; n1 = n2; a2 = at; n2 = nt; }
130
131 // build a way for the circle
132 Way wayToAdd;
133 if (existingWay == null) {
134 wayToAdd = new Way();
135 } else {
136 // re-use existing way if it was selected
137 wayToAdd = new Way(existingWay);
138 wayToAdd.nodes.clear();
139 }
140
141 for (int i = 1; i <= numberOfNodesInCircle; i++) {
142 double a = a2 + 2*Math.PI*(1.0 - i/(double)numberOfNodesInCircle); // "1-" to get it clock-wise
143
144 // insert existing nodes if they fit before this new node (999 means "already added this node")
145 if ((a1 < 999) && (a1 > a - 1E-9) && (a1 < a + 1E-9)) {
146 wayToAdd.nodes.add(n1);
147 a1 = 999;
148 }
149 else if ((a2 < 999) && (a2 > a - 1E-9) && (a2 < a + 1E-9)) {
150 wayToAdd.nodes.add(n2);
151 a2 = 999;
152 }
153 else {
154 // get the position of the new node and insert it
155 double x = xc + r*Math.cos(a);
156 double y = yc + r*Math.sin(a);
157 Node n = new Node(Main.proj.eastNorth2latlon(new EastNorth(x,y)));
158 wayToAdd.nodes.add(n);
159 cmds.add(new AddCommand(n));
160 }
161 }
162 wayToAdd.nodes.add(wayToAdd.nodes.get(0)); // close the circle
163 if (existingWay == null) {
164 cmds.add(new AddCommand(wayToAdd));
165 } else {
166 cmds.add(new ChangeCommand(existingWay, wayToAdd));
167 }
168
169 // the first node may be unused/abandoned if createcircle.nodecount is odd
170 if (a1 < 999) {
171 // if it is, delete it
172 CollectBackReferencesVisitor refs = new CollectBackReferencesVisitor(Main.ds);
173 refs.visit(n1);
174 if (refs.data.isEmpty() || ((refs.data.size() == 1) && (refs.data.contains(existingWay)))) {
175 cmds.add(new DeleteCommand(n1));
176 }
177
178 // or insert it
179 // wayToAdd.nodes.add((numberOfNodesInCircle - 1) / 2, n1);
180 }
181
182 } else if (nodes.size() == 3) {
183 // triangle: three single nodes needed or a way with three nodes
184
185 // let's get some shorter names
186 Node n1 = ((Node)nodes.toArray()[0]);
187 double x1 = n1.getEastNorth().east();
188 double y1 = n1.getEastNorth().north();
189 Node n2 = ((Node)nodes.toArray()[1]);
190 double x2 = n2.getEastNorth().east();
191 double y2 = n2.getEastNorth().north();
192 Node n3 = ((Node)nodes.toArray()[2]);
193 double x3 = n3.getEastNorth().east();
194 double y3 = n3.getEastNorth().north();
195
196 // calculate the center (xc/yc)
197 double s = 0.5*((x2 - x3)*(x1 - x3) - (y2 - y3)*(y3 - y1));
198 double sUnder = (x1 - x2)*(y3 - y1) - (y2 - y1)*(x1 - x3);
199
200 if (sUnder == 0) {
201 JOptionPane.showMessageDialog(Main.parent, tr("Those nodes are not in a circle."));
202 return;
203 }
204
205 s /= sUnder;
206
207 double xc = 0.5*(x1 + x2) + s*(y2 - y1);
208 double yc = 0.5*(y1 + y2) + s*(x1 - x2);
209
210 // calculate the radius (r)
211 double r = Math.sqrt(Math.pow(xc-x1,2) + Math.pow(yc-y1,2));
212
213 // find where to put the existing nodes
214 double a1 = calcang(xc, yc, x1, y1);
215 double a2 = calcang(xc, yc, x2, y2);
216 double a3 = calcang(xc, yc, x3, y3);
217 if (a1 < a2) { double at = a1; Node nt = n1; a1 = a2; n1 = n2; a2 = at; n2 = nt; }
218 if (a2 < a3) { double at = a2; Node nt = n2; a2 = a3; n2 = n3; a3 = at; n3 = nt; }
219 if (a1 < a2) { double at = a1; Node nt = n1; a1 = a2; n1 = n2; a2 = at; n2 = nt; }
220
221 // build a way for the circle
222 Way wayToAdd;
223 if (existingWay == null) {
224 wayToAdd = new Way();
225 } else {
226 // re-use existing way if it was selected
227 wayToAdd = new Way(existingWay);
228 wayToAdd.nodes.clear();
229 }
230 for (int i = 1; i <= numberOfNodesInCircle; i++) {
231 double a = 2*Math.PI*(1.0 - i/(double)numberOfNodesInCircle); // "1-" to get it clock-wise
232 // insert existing nodes if they fit before this new node (999 means "already added this node")
233 if (a1 < 999 && a1 > a) {
234 wayToAdd.nodes.add(n1);
235 a1 = 999;
236 }
237 if (a2 < 999 && a2 > a) {
238 wayToAdd.nodes.add(n2);
239 a2 = 999;
240 }
241 if (a3 < 999 && a3 > a) {
242 wayToAdd.nodes.add(n3);
243 a3 = 999;
244 }
245 // get the position of the new node and insert it
246 double x = xc + r*Math.cos(a);
247 double y = yc + r*Math.sin(a);
248 Node n = new Node(Main.proj.eastNorth2latlon(new EastNorth(x,y)));
249 wayToAdd.nodes.add(n);
250 cmds.add(new AddCommand(n));
251 }
252 wayToAdd.nodes.add(wayToAdd.nodes.get(0)); // close the circle
253 if (existingWay == null) {
254 cmds.add(new AddCommand(wayToAdd));
255 } else {
256 cmds.add(new ChangeCommand(existingWay, wayToAdd));
257 }
258
259 } else {
260 JOptionPane.showMessageDialog(Main.parent, tr("Please select exactly two or three nodes or one way with exactly two or three nodes."));
261 return;
262 }
263
264 Main.main.undoRedo.add(new SequenceCommand(tr("Create Circle"), cmds));
265 Main.map.repaint();
266 }
267}
Note: See TracBrowser for help on using the repository browser.