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

Last change on this file since 6102 was 6093, checked in by akks, 11 years ago

see #8902 - collection size ==/!= 0 -> isEmpty()/!isEmpty() (patch by shinigami)

  • Property svn:eol-style set to native
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")),
46 KeyEvent.VK_O, Shortcut.SHIFT), true);
47 putValue("help", ht("/Action/CreateCircle"));
48 }
49
50 private double calcang(double xc, double yc, double x, double y) {
51 // calculate the angle from xc|yc to x|y
52 if (xc == x && yc == y)
53 return 0; // actually invalid, but we won't have this case in this context
54 double yd = Math.abs(y - yc);
55 if (yd == 0 && xc < x)
56 return 0;
57 if (yd == 0 && xc > x)
58 return Math.PI;
59 double xd = Math.abs(x - xc);
60 double a = Math.atan2(xd, yd);
61 if (y > yc) {
62 a = Math.PI - a;
63 }
64 if (x < xc) {
65 a = -a;
66 }
67 a = 1.5*Math.PI + a;
68 if (a < 0) {
69 a += 2*Math.PI;
70 }
71 if (a >= 2*Math.PI) {
72 a -= 2*Math.PI;
73 }
74 return a;
75 }
76
77 @Override
78 public void actionPerformed(ActionEvent e) {
79 if (!isEnabled())
80 return;
81
82 int numberOfNodesInCircle = Main.pref.getInteger("createcircle.nodecount", 8);
83 if (numberOfNodesInCircle < 1) {
84 numberOfNodesInCircle = 1;
85 } else if (numberOfNodesInCircle > 100) {
86 numberOfNodesInCircle = 100;
87 }
88
89 Collection<OsmPrimitive> sel = getCurrentDataSet().getSelected();
90 List<Node> nodes = new LinkedList<Node>();
91 Way existingWay = null;
92
93 for (OsmPrimitive osm : sel)
94 if (osm instanceof Node) {
95 nodes.add((Node)osm);
96 }
97
98 // special case if no single nodes are selected and exactly one way is:
99 // then use the way's nodes
100 if (nodes.isEmpty() && (sel.size() == 1)) {
101 for (OsmPrimitive osm : sel)
102 if (osm instanceof Way) {
103 existingWay = ((Way)osm);
104 for (Node n : ((Way)osm).getNodes())
105 {
106 if(!nodes.contains(n)) {
107 nodes.add(n);
108 }
109 }
110 }
111 }
112
113 // now we can start doing things to OSM data
114 Collection<Command> cmds = new LinkedList<Command>();
115
116 if (nodes.size() == 2) {
117 // diameter: two single nodes needed or a way with two nodes
118
119 Node n1 = nodes.get(0);
120 double x1 = n1.getEastNorth().east();
121 double y1 = n1.getEastNorth().north();
122 Node n2 = nodes.get(1);
123 double x2 = n2.getEastNorth().east();
124 double y2 = n2.getEastNorth().north();
125
126 // calculate the center (xc/yc)
127 double xc = 0.5 * (x1 + x2);
128 double yc = 0.5 * (y1 + y2);
129
130 // calculate the radius (r)
131 double r = Math.sqrt(Math.pow(xc-x1,2) + Math.pow(yc-y1,2));
132
133 // find where to put the existing nodes
134 double a1 = calcang(xc, yc, x1, y1);
135 double a2 = calcang(xc, yc, x2, y2);
136 if (a1 < a2) { double at = a1; Node nt = n1; a1 = a2; n1 = n2; a2 = at; n2 = nt; }
137
138 // build a way for the circle
139 List<Node> wayToAdd = new ArrayList<Node>();
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.add(n1);
147 a1 = 999;
148 }
149 else if ((a2 < 999) && (a2 > a - 1E-9) && (a2 < a + 1E-9)) {
150 wayToAdd.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.getProjection().eastNorth2latlon(new EastNorth(x,y)));
158 wayToAdd.add(n);
159 cmds.add(new AddCommand(n));
160 }
161 }
162 wayToAdd.add(wayToAdd.get(0)); // close the circle
163 if (existingWay == null) {
164 Way newWay = new Way();
165 newWay.setNodes(wayToAdd);
166 cmds.add(new AddCommand(newWay));
167 } else {
168 Way newWay = new Way(existingWay);
169 newWay.setNodes(wayToAdd);
170 cmds.add(new ChangeCommand(existingWay, newWay));
171 }
172
173 // the first node may be unused/abandoned if createcircle.nodecount is odd
174 if (a1 < 999) {
175 // if it is, delete it
176 List<OsmPrimitive> parents = n1.getReferrers();
177 if (parents.isEmpty() || ((parents.size() == 1) && (parents.contains(existingWay)))) {
178 cmds.add(new DeleteCommand(n1));
179 }
180
181 // or insert it
182 // wayToAdd.nodes.add((numberOfNodesInCircle - 1) / 2, n1);
183 }
184
185 } else if (nodes.size() == 3) {
186 // triangle: three single nodes needed or a way with three nodes
187
188 // let's get some shorter names
189 Node n1 = nodes.get(0);
190 double x1 = n1.getEastNorth().east();
191 double y1 = n1.getEastNorth().north();
192 Node n2 = nodes.get(1);
193 double x2 = n2.getEastNorth().east();
194 double y2 = n2.getEastNorth().north();
195 Node n3 = nodes.get(2);
196 double x3 = n3.getEastNorth().east();
197 double y3 = n3.getEastNorth().north();
198
199 // calculate the center (xc/yc)
200 double s = 0.5*((x2 - x3)*(x1 - x3) - (y2 - y3)*(y3 - y1));
201 double sUnder = (x1 - x2)*(y3 - y1) - (y2 - y1)*(x1 - x3);
202
203 if (sUnder == 0) {
204 JOptionPane.showMessageDialog(
205 Main.parent,
206 tr("Those nodes are not in a circle. Aborting."),
207 tr("Warning"),
208 JOptionPane.WARNING_MESSAGE
209 );
210 return;
211 }
212
213 s /= sUnder;
214
215 double xc = 0.5*(x1 + x2) + s*(y2 - y1);
216 double yc = 0.5*(y1 + y2) + s*(x1 - x2);
217
218 // calculate the radius (r)
219 double r = Math.sqrt(Math.pow(xc-x1,2) + Math.pow(yc-y1,2));
220
221 // find where to put the existing nodes
222 double a1 = calcang(xc, yc, x1, y1);
223 double a2 = calcang(xc, yc, x2, y2);
224 double a3 = calcang(xc, yc, x3, y3);
225 if (a1 < a2) { double at = a1; Node nt = n1; a1 = a2; n1 = n2; a2 = at; n2 = nt; }
226 if (a2 < a3) { double at = a2; Node nt = n2; a2 = a3; n2 = n3; a3 = at; n3 = nt; }
227 if (a1 < a2) { double at = a1; Node nt = n1; a1 = a2; n1 = n2; a2 = at; n2 = nt; }
228
229 // build a way for the circle
230 List<Node> wayToAdd = new ArrayList<Node>();
231 for (int i = 1; i <= numberOfNodesInCircle; i++) {
232 double a = 2*Math.PI*(1.0 - i/(double)numberOfNodesInCircle); // "1-" to get it clock-wise
233 // insert existing nodes if they fit before this new node (999 means "already added this node")
234 if (a1 < 999 && a1 > a) {
235 wayToAdd.add(n1);
236 a1 = 999;
237 }
238 if (a2 < 999 && a2 > a) {
239 wayToAdd.add(n2);
240 a2 = 999;
241 }
242 if (a3 < 999 && a3 > a) {
243 wayToAdd.add(n3);
244 a3 = 999;
245 }
246 // get the position of the new node and insert it
247 double x = xc + r*Math.cos(a);
248 double y = yc + r*Math.sin(a);
249 Node n = new Node(Main.getProjection().eastNorth2latlon(new EastNorth(x,y)));
250 wayToAdd.add(n);
251 cmds.add(new AddCommand(n));
252 }
253 wayToAdd.add(wayToAdd.get(0)); // close the circle
254 if (existingWay == null) {
255 Way newWay = new Way();
256 newWay.setNodes(wayToAdd);
257 cmds.add(new AddCommand(newWay));
258 } else {
259 Way newWay = new Way(existingWay);
260 newWay.setNodes(wayToAdd);
261 cmds.add(new ChangeCommand(existingWay, newWay));
262 }
263
264 } else {
265 JOptionPane.showMessageDialog(
266 Main.parent,
267 tr("Please select exactly two or three nodes or one way with exactly two or three nodes."),
268 tr("Information"),
269 JOptionPane.INFORMATION_MESSAGE
270 );
271 return;
272 }
273
274 Main.main.undoRedo.add(new SequenceCommand(tr("Create Circle"), cmds));
275 Main.map.repaint();
276 }
277
278 @Override
279 protected void updateEnabledState() {
280 if (getCurrentDataSet() == null) {
281 setEnabled(false);
282 } else {
283 updateEnabledState(getCurrentDataSet().getSelected());
284 }
285 }
286
287 @Override
288 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
289 setEnabled(selection != null && !selection.isEmpty());
290 }
291}
Note: See TracBrowser for help on using the repository browser.