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

Revision 4126, 11.0 KB checked in by bastiK, 8 months ago (diff)

memory optimizations for Node & WayPoint (Patch by Gubaer, modified)

The field 'proj' in CachedLatLon is a waste of memory. For the 2 classes where this has the greatest impact, the cache for the projected coordinates is replaced by 2 simple double fields (east & north). On projection change, they have to be invalidated explicitly. This is handled by the DataSet & the GpxLayer.

  • 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.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.getProjection().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.getProjection().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.