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

Revision 2323, 9.9 KB checked in by Gubaer, 2 years ago (diff)

Added explicit help topics
See also current list of help topics with links to source files and to help pages

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