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

Last change on this file since 8207 was 7269, checked in by Don-vip, 10 years ago

fix #10168 - double default value of createcircle.nodecount from 8 to 16

  • Property svn:eol-style set to native
File size: 9.5 KB
RevLine 
[6380]1// License: GPL. For details, see LICENSE file.
[996]2package org.openstreetmap.josm.actions;
3
[2565]4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
[996]5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.event.ActionEvent;
8import java.awt.event.KeyEvent;
[1910]9import java.util.ArrayList;
[6942]10import java.util.Arrays;
[996]11import java.util.Collection;
[6942]12import java.util.Comparator;
[996]13import java.util.LinkedList;
[1846]14import java.util.List;
[996]15
16import javax.swing.JOptionPane;
17
18import org.openstreetmap.josm.Main;
[1846]19import org.openstreetmap.josm.command.AddCommand;
20import org.openstreetmap.josm.command.ChangeCommand;
[996]21import org.openstreetmap.josm.command.Command;
22import org.openstreetmap.josm.command.SequenceCommand;
23import org.openstreetmap.josm.data.coor.EastNorth;
[6672]24import org.openstreetmap.josm.data.coor.LatLon;
[996]25import org.openstreetmap.josm.data.osm.Node;
26import org.openstreetmap.josm.data.osm.OsmPrimitive;
27import org.openstreetmap.josm.data.osm.Way;
[6124]28import org.openstreetmap.josm.gui.Notification;
[6942]29import org.openstreetmap.josm.tools.Geometry;
[1084]30import org.openstreetmap.josm.tools.Shortcut;
[996]31
32/**
[1741]33 * - Create a new circle from two selected nodes or a way with 2 nodes which represent the diameter of the circle.
34 * - Create a new circle from three selected nodes--or a way with 3 nodes.
35 * - Useful for roundabouts
[1023]36 *
[6942]37 * Notes:
38 * * If a way is selected, it is changed. If nodes are selected a new way is created.
39 * So if you've got a way with nodes it makes a difference between running this on the way or the nodes!
40 * * The existing nodes are retained, and additional nodes are inserted regularly
41 * to achieve the desired number of nodes (`createcircle.nodecount`).
[996]42 * BTW: Someone might want to implement projection corrections for this...
43 *
[6814]44 * @since 996
45 *
[6830]46 * @author Henry Loenwind
[1741]47 * @author Sebastian Masch
[6942]48 * @author Alain Delplanque
[996]49 */
50public final class CreateCircleAction extends JosmAction {
51
[6296]52 /**
53 * Constructs a new {@code CreateCircleAction}.
54 */
[1169]55 public CreateCircleAction() {
[6814]56 super(tr("Create Circle"), "aligncircle", tr("Create a circle from three selected nodes."),
[4958]57 Shortcut.registerShortcut("tools:createcircle", tr("Tool: {0}", tr("Create Circle")),
[6814]58 KeyEvent.VK_O, Shortcut.SHIFT), true, "createcircle", true);
[2323]59 putValue("help", ht("/Action/CreateCircle"));
[1169]60 }
[996]61
[6942]62 /**
63 * Distributes nodes according to the algorithm of election with largest remainder.
64 * @param angles Array of PolarNode ordered by increasing angles
65 * @param nodesCount Number of nodes to be distributed
66 * @return Array of number of nodes to put in each arc
67 */
68 private int[] distributeNodes(PolarNode[] angles, int nodesCount) {
69 int[] count = new int[angles.length];
70 double[] width = new double[angles.length];
71 double[] remainder = new double[angles.length];
72 for(int i = 0; i < angles.length; i++) {
73 width[i] = angles[(i+1) % angles.length].a - angles[i].a;
74 if(width[i] < 0)
75 width[i] += 2*Math.PI;
[1169]76 }
[6942]77 int assign = 0;
78 for(int i = 0; i < angles.length; i++) {
79 double part = width[i] / 2.0 / Math.PI * nodesCount;
80 count[i] = (int) Math.floor(part);
81 remainder[i] = part - count[i];
82 assign += count[i];
[1169]83 }
[6942]84 while(assign < nodesCount) {
85 int imax = 0;
86 for(int i = 1; i < angles.length; i++)
87 if(remainder[i] > remainder[imax])
88 imax = i;
89 count[imax]++;
90 remainder[imax] = 0;
91 assign++;
[1169]92 }
[6942]93 return count;
94 }
95
96 /**
97 * Class designed to create a couple between a node and its angle relative to the center of the circle.
98 */
[6977]99 private static class PolarNode {
[6942]100 double a;
101 Node node;
[7269]102
[6942]103 PolarNode(EastNorth center, Node n) {
104 EastNorth pt = n.getEastNorth();
105 this.a = Math.atan2(pt.north() - center.north(), pt.east() - center.east());
106 this.node = n;
[1169]107 }
108 }
[996]109
[6942]110 /**
111 * Comparator used to order PolarNode relative to their angle.
112 */
[6977]113 private static class PolarNodeComparator implements Comparator<PolarNode> {
[6942]114
115 @Override
116 public int compare(PolarNode pc1, PolarNode pc2) {
117 if(pc1.a < pc2.a)
118 return -1;
119 else if(pc1.a == pc2.a)
120 return 0;
121 else
122 return 1;
123 }
124 }
[7269]125
[6084]126 @Override
[1169]127 public void actionPerformed(ActionEvent e) {
[1820]128 if (!isEnabled())
129 return;
130
[7269]131 int numberOfNodesInCircle = Main.pref.getInteger("createcircle.nodecount", 16);
[1169]132 if (numberOfNodesInCircle < 1) {
133 numberOfNodesInCircle = 1;
134 } else if (numberOfNodesInCircle > 100) {
135 numberOfNodesInCircle = 100;
136 }
[996]137
[1814]138 Collection<OsmPrimitive> sel = getCurrentDataSet().getSelected();
[7005]139 List<Node> nodes = new LinkedList<>();
[1169]140 Way existingWay = null;
[996]141
[1169]142 for (OsmPrimitive osm : sel)
[1741]143 if (osm instanceof Node) {
[1169]144 nodes.add((Node)osm);
[1741]145 }
[996]146
[1169]147 // special case if no single nodes are selected and exactly one way is:
148 // then use the way's nodes
[6093]149 if (nodes.isEmpty() && (sel.size() == 1)) {
[1169]150 for (OsmPrimitive osm : sel)
151 if (osm instanceof Way) {
152 existingWay = ((Way)osm);
[1862]153 for (Node n : ((Way)osm).getNodes())
[1169]154 {
[1741]155 if(!nodes.contains(n)) {
[1169]156 nodes.add(n);
[1741]157 }
[1169]158 }
159 }
160 }
[996]161
[6942]162 if (nodes.size() < 2 || nodes.size() > 3) {
163 new Notification(
164 tr("Please select exactly two or three nodes or one way with exactly two or three nodes."))
165 .setIcon(JOptionPane.INFORMATION_MESSAGE)
166 .setDuration(Notification.TIME_LONG)
167 .show();
168 return;
169 }
170
[1741]171 // now we can start doing things to OSM data
[7005]172 Collection<Command> cmds = new LinkedList<>();
[6942]173 EastNorth center = null;
[7269]174
[1741]175 if (nodes.size() == 2) {
176 // diameter: two single nodes needed or a way with two nodes
[1846]177 Node n1 = nodes.get(0);
[1741]178 double x1 = n1.getEastNorth().east();
179 double y1 = n1.getEastNorth().north();
[1846]180 Node n2 = nodes.get(1);
[1741]181 double x2 = n2.getEastNorth().east();
182 double y2 = n2.getEastNorth().north();
[996]183
[1741]184 // calculate the center (xc/yc)
185 double xc = 0.5 * (x1 + x2);
186 double yc = 0.5 * (y1 + y2);
[6942]187 center = new EastNorth(xc, yc);
[6977]188 } else {
[1741]189 // triangle: three single nodes needed or a way with three nodes
[6942]190 center = Geometry.getCenter(nodes);
191 if (center == null) {
[6672]192 notifyNodesNotOnCircle();
[1741]193 return;
194 }
[6942]195 }
[1741]196
[6942]197 // calculate the radius (r)
198 EastNorth n1 = nodes.get(0).getEastNorth();
199 double r = Math.sqrt(Math.pow(center.east()-n1.east(),2) +
200 Math.pow(center.north()-n1.north(),2));
[1741]201
[6942]202 // Order nodes by angle
203 PolarNode[] angles = new PolarNode[nodes.size()];
204 for(int i = 0; i < nodes.size(); i++) {
205 angles[i] = new PolarNode(center, nodes.get(i));
206 }
207 Arrays.sort(angles, new PolarNodeComparator());
208 int[] count = distributeNodes(angles,
209 numberOfNodesInCircle >= nodes.size() ? numberOfNodesInCircle - nodes.size() : 0);
[1741]210
[6942]211 // build a way for the circle
[7005]212 List<Node> wayToAdd = new ArrayList<>();
[6942]213 for(int i = 0; i < nodes.size(); i++) {
214 wayToAdd.add(angles[i].node);
215 double delta = angles[(i+1) % nodes.size()].a - angles[i].a;
216 if(delta < 0)
217 delta += 2*Math.PI;
218 for(int j = 0; j < count[i]; j++) {
219 double alpha = angles[i].a + (j+1)*delta/(count[i]+1);
220 double x = center.east() + r*Math.cos(alpha);
221 double y = center.north() + r*Math.sin(alpha);
[6672]222 LatLon ll = Main.getProjection().eastNorth2latlon(new EastNorth(x,y));
223 if (ll.isOutSideWorld()) {
224 notifyNodesNotOnCircle();
225 return;
226 }
227 Node n = new Node(ll);
[1910]228 wayToAdd.add(n);
[1741]229 cmds.add(new AddCommand(n));
230 }
[6942]231 }
232 wayToAdd.add(wayToAdd.get(0)); // close the circle
233 if (existingWay == null) {
234 Way newWay = new Way();
235 newWay.setNodes(wayToAdd);
236 cmds.add(new AddCommand(newWay));
[1169]237 } else {
[6942]238 Way newWay = new Way(existingWay);
239 newWay.setNodes(wayToAdd);
240 cmds.add(new ChangeCommand(existingWay, newWay));
[1169]241 }
[996]242
[1169]243 Main.main.undoRedo.add(new SequenceCommand(tr("Create Circle"), cmds));
244 Main.map.repaint();
245 }
[6814]246
[6672]247 private static void notifyNodesNotOnCircle() {
248 new Notification(
249 tr("Those nodes are not in a circle. Aborting."))
250 .setIcon(JOptionPane.WARNING_MESSAGE)
251 .show();
252 }
[1820]253
254 @Override
255 protected void updateEnabledState() {
[2256]256 if (getCurrentDataSet() == null) {
257 setEnabled(false);
258 } else {
259 updateEnabledState(getCurrentDataSet().getSelected());
260 }
[1820]261 }
[2256]262
263 @Override
264 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
265 setEnabled(selection != null && !selection.isEmpty());
266 }
[996]267}
Note: See TracBrowser for help on using the repository browser.