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

Last change on this file since 2612 was 2565, checked in by Gubaer, 14 years ago

Removed BackReferenceDataSet and CollectBackReferenceVisitor because we now have referrer support in OsmPrimitive.
This could cause some problems in the next few days. I'm sure I didn't test every possibly affected feature.

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