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

Last change on this file since 6053 was 5719, checked in by Don-vip, 11 years ago

see #8431 - Align in circle: Do not allow "regular polygon" mode for way nodes being referred by another primitives. The initial problem (way being reverted) remains.

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