- Timestamp:
- 2014-03-29T00:45:51+01:00 (11 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/actions/CreateCircleAction.java
r6830 r6942 8 8 import java.awt.event.KeyEvent; 9 9 import java.util.ArrayList; 10 import java.util.Arrays; 10 11 import java.util.Collection; 12 import java.util.Comparator; 11 13 import java.util.LinkedList; 12 14 import java.util.List; … … 18 20 import org.openstreetmap.josm.command.ChangeCommand; 19 21 import org.openstreetmap.josm.command.Command; 20 import org.openstreetmap.josm.command.DeleteCommand;21 22 import org.openstreetmap.josm.command.SequenceCommand; 22 23 import org.openstreetmap.josm.data.coor.EastNorth; … … 26 27 import org.openstreetmap.josm.data.osm.Way; 27 28 import org.openstreetmap.josm.gui.Notification; 29 import org.openstreetmap.josm.tools.Geometry; 28 30 import org.openstreetmap.josm.tools.Shortcut; 29 31 … … 33 35 * - Useful for roundabouts 34 36 * 35 * Note: If a way is selected, it is changed. If nodes are selected a new way is created. 36 * So if you've got a way with nodes it makes a difference between running this on the way or the nodes! 37 * 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`). 38 42 * BTW: Someone might want to implement projection corrections for this... 39 43 * … … 42 46 * @author Henry Loenwind 43 47 * @author Sebastian Masch 48 * @author Alain Delplanque 44 49 */ 45 50 public final class CreateCircleAction extends JosmAction { … … 55 60 } 56 61 57 private double calcang(double xc, double yc, double x, double y) { 58 // calculate the angle from xc|yc to x|y 59 if (xc == x && yc == y) 60 return 0; // actually invalid, but we won't have this case in this context 61 double yd = Math.abs(y - yc); 62 if (yd == 0 && xc < x) 63 return 0; 64 if (yd == 0 && xc > x) 65 return Math.PI; 66 double xd = Math.abs(x - xc); 67 double a = Math.atan2(xd, yd); 68 if (y > yc) { 69 a = Math.PI - a; 70 } 71 if (x < xc) { 72 a = -a; 73 } 74 a = 1.5*Math.PI + a; 75 if (a < 0) { 76 a += 2*Math.PI; 77 } 78 if (a >= 2*Math.PI) { 79 a -= 2*Math.PI; 80 } 81 return a; 82 } 83 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; 76 } 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]; 83 } 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++; 92 } 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 */ 99 private class PolarNode { 100 double a; 101 Node node; 102 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; 107 } 108 } 109 110 /** 111 * Comparator used to order PolarNode relative to their angle. 112 */ 113 private class PolarNodeComparator implements Comparator<PolarNode> { 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 } 125 84 126 @Override 85 127 public void actionPerformed(ActionEvent e) { … … 118 160 } 119 161 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 120 171 // now we can start doing things to OSM data 121 172 Collection<Command> cmds = new LinkedList<Command>(); 122 173 EastNorth center = null; 174 123 175 if (nodes.size() == 2) { 124 176 // diameter: two single nodes needed or a way with two nodes 125 126 177 Node n1 = nodes.get(0); 127 178 double x1 = n1.getEastNorth().east(); … … 134 185 double xc = 0.5 * (x1 + x2); 135 186 double yc = 0.5 * (y1 + y2); 136 137 // calculate the radius (r) 138 double r = Math.sqrt(Math.pow(xc-x1,2) + Math.pow(yc-y1,2)); 139 140 // find where to put the existing nodes 141 double a1 = calcang(xc, yc, x1, y1); 142 double a2 = calcang(xc, yc, x2, y2); 143 if (a1 < a2) { double at = a1; Node nt = n1; a1 = a2; n1 = n2; a2 = at; n2 = nt; } 144 145 // build a way for the circle 146 List<Node> wayToAdd = new ArrayList<Node>(numberOfNodesInCircle + 1); 147 148 for (int i = 1; i <= numberOfNodesInCircle; i++) { 149 double a = a2 + 2*Math.PI*(1.0 - i/(double)numberOfNodesInCircle); // "1-" to get it clock-wise 150 151 // insert existing nodes if they fit before this new node (999 means "already added this node") 152 if ((a1 < 999) && (a1 > a - 1E-9) && (a1 < a + 1E-9)) { 153 wayToAdd.add(n1); 154 a1 = 999; 155 } 156 else if ((a2 < 999) && (a2 > a - 1E-9) && (a2 < a + 1E-9)) { 157 wayToAdd.add(n2); 158 a2 = 999; 159 } 160 else { 161 // get the position of the new node and insert it 162 double x = xc + r*Math.cos(a); 163 double y = yc + r*Math.sin(a); 164 Node n = new Node(Main.getProjection().eastNorth2latlon(new EastNorth(x,y))); 165 wayToAdd.add(n); 166 cmds.add(new AddCommand(n)); 167 } 168 } 169 wayToAdd.add(wayToAdd.get(0)); // close the circle 170 if (existingWay == null) { 171 Way newWay = new Way(); 172 newWay.setNodes(wayToAdd); 173 cmds.add(new AddCommand(newWay)); 174 } else { 175 Way newWay = new Way(existingWay); 176 newWay.setNodes(wayToAdd); 177 cmds.add(new ChangeCommand(existingWay, newWay)); 178 } 179 180 // the first node may be unused/abandoned if createcircle.nodecount is odd 181 if (a1 < 999) { 182 // if it is, delete it 183 List<OsmPrimitive> parents = n1.getReferrers(); 184 if (parents.isEmpty() || ((parents.size() == 1) && (parents.contains(existingWay)))) { 185 cmds.add(new DeleteCommand(n1)); 186 } 187 } 188 187 center = new EastNorth(xc, yc); 189 188 } else if (nodes.size() == 3) { 190 189 // triangle: three single nodes needed or a way with three nodes 191 192 // let's get some shorter names 193 Node n1 = nodes.get(0); 194 double x1 = n1.getEastNorth().east(); 195 double y1 = n1.getEastNorth().north(); 196 Node n2 = nodes.get(1); 197 double x2 = n2.getEastNorth().east(); 198 double y2 = n2.getEastNorth().north(); 199 Node n3 = nodes.get(2); 200 double x3 = n3.getEastNorth().east(); 201 double y3 = n3.getEastNorth().north(); 202 203 // calculate the center (xc/yc) 204 double s = 0.5*((x2 - x3)*(x1 - x3) - (y2 - y3)*(y3 - y1)); 205 double sUnder = (x1 - x2)*(y3 - y1) - (y2 - y1)*(x1 - x3); 206 207 if (sUnder == 0) { 190 center = Geometry.getCenter(nodes); 191 if (center == null) { 208 192 notifyNodesNotOnCircle(); 209 193 return; 210 194 } 211 212 s /= sUnder; 213 214 double xc = 0.5*(x1 + x2) + s*(y2 - y1); 215 double yc = 0.5*(y1 + y2) + s*(x1 - x2); 216 217 // calculate the radius (r) 218 double r = Math.sqrt(Math.pow(xc-x1,2) + Math.pow(yc-y1,2)); 219 220 // find where to put the existing nodes 221 double a1 = calcang(xc, yc, x1, y1); 222 double a2 = calcang(xc, yc, x2, y2); 223 double a3 = calcang(xc, yc, x3, y3); 224 if (a1 < a2) { double at = a1; Node nt = n1; a1 = a2; n1 = n2; a2 = at; n2 = nt; } 225 if (a2 < a3) { double at = a2; Node nt = n2; a2 = a3; n2 = n3; a3 = at; n3 = nt; } 226 if (a1 < a2) { double at = a1; Node nt = n1; a1 = a2; n1 = n2; a2 = at; n2 = nt; } 227 228 // build a way for the circle 229 List<Node> wayToAdd = new ArrayList<Node>(); 230 for (int i = 1; i <= numberOfNodesInCircle; i++) { 231 double a = 2*Math.PI*(1.0 - i/(double)numberOfNodesInCircle); // "1-" to get it clock-wise 232 // insert existing nodes if they fit before this new node (999 means "already added this node") 233 if (a1 < 999 && a1 > a) { 234 wayToAdd.add(n1); 235 a1 = 999; 236 } 237 if (a2 < 999 && a2 > a) { 238 wayToAdd.add(n2); 239 a2 = 999; 240 } 241 if (a3 < 999 && a3 > a) { 242 wayToAdd.add(n3); 243 a3 = 999; 244 } 245 // get the position of the new node and insert it 246 double x = xc + r*Math.cos(a); 247 double y = yc + r*Math.sin(a); 195 } 196 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)); 201 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); 210 211 // build a way for the circle 212 List<Node> wayToAdd = new ArrayList<Node>(); 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); 248 222 LatLon ll = Main.getProjection().eastNorth2latlon(new EastNorth(x,y)); 249 223 if (ll.isOutSideWorld()) { … … 255 229 cmds.add(new AddCommand(n)); 256 230 } 257 wayToAdd.add(wayToAdd.get(0)); // close the circle 258 if (existingWay == null) { 259 Way newWay = new Way(); 260 newWay.setNodes(wayToAdd); 261 cmds.add(new AddCommand(newWay)); 262 } else { 263 Way newWay = new Way(existingWay); 264 newWay.setNodes(wayToAdd); 265 cmds.add(new ChangeCommand(existingWay, newWay)); 266 } 267 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)); 268 237 } else { 269 new Notification( 270 tr("Please select exactly two or three nodes or one way with exactly two or three nodes.")) 271 .setIcon(JOptionPane.INFORMATION_MESSAGE) 272 .setDuration(Notification.TIME_LONG) 273 .show(); 274 return; 238 Way newWay = new Way(existingWay); 239 newWay.setNodes(wayToAdd); 240 cmds.add(new ChangeCommand(existingWay, newWay)); 275 241 } 276 242
Note:
See TracChangeset
for help on using the changeset viewer.