| | 1 | // License: GPL. Copyright 2007 by Immanuel Scholz and others |
| | 2 | package org.openstreetmap.josm.actions.mapmode; |
| | 3 | |
| | 4 | import static org.openstreetmap.josm.tools.I18n.marktr; |
| | 5 | import static org.openstreetmap.josm.tools.I18n.tr; |
| | 6 | |
| | 7 | import java.awt.BasicStroke; |
| | 8 | import java.awt.Color; |
| | 9 | import java.awt.Cursor; |
| | 10 | import java.awt.Graphics; |
| | 11 | import java.awt.Graphics2D; |
| | 12 | import java.awt.Point; |
| | 13 | import java.awt.event.ActionEvent; |
| | 14 | import java.awt.event.InputEvent; |
| | 15 | import java.awt.event.KeyEvent; |
| | 16 | import java.awt.event.MouseEvent; |
| | 17 | import java.awt.geom.GeneralPath; |
| | 18 | import java.util.ArrayList; |
| | 19 | import java.util.Collection; |
| | 20 | import java.util.LinkedList; |
| | 21 | import java.util.List; |
| | 22 | import java.math.*; |
| | 23 | |
| | 24 | import javax.swing.JOptionPane; |
| | 25 | |
| | 26 | import org.openstreetmap.josm.Main; |
| | 27 | import org.openstreetmap.josm.command.AddCommand; |
| | 28 | import org.openstreetmap.josm.command.ChangeCommand; |
| | 29 | import org.openstreetmap.josm.command.Command; |
| | 30 | import org.openstreetmap.josm.command.MoveCommand; |
| | 31 | import org.openstreetmap.josm.command.RotateCommand; |
| | 32 | import org.openstreetmap.josm.command.SequenceCommand; |
| | 33 | import org.openstreetmap.josm.data.coor.EastNorth; |
| | 34 | import org.openstreetmap.josm.data.osm.Node; |
| | 35 | import org.openstreetmap.josm.data.osm.OsmPrimitive; |
| | 36 | import org.openstreetmap.josm.data.osm.Way; |
| | 37 | import org.openstreetmap.josm.data.osm.WaySegment; |
| | 38 | import org.openstreetmap.josm.data.osm.visitor.AllNodesVisitor; |
| | 39 | import org.openstreetmap.josm.gui.MapFrame; |
| | 40 | import org.openstreetmap.josm.gui.MapView; |
| | 41 | import org.openstreetmap.josm.gui.layer.MapViewPaintable; |
| | 42 | import org.openstreetmap.josm.gui.layer.Layer; |
| | 43 | import org.openstreetmap.josm.gui.layer.OsmDataLayer; |
| | 44 | import org.openstreetmap.josm.tools.ImageProvider; |
| | 45 | import org.openstreetmap.josm.tools.Shortcut; |
| | 46 | |
| | 47 | /** |
| | 48 | * Makes a circle from a point or way |
| | 49 | * |
| | 50 | */ |
| | 51 | public class CircleAction extends MapMode implements MapViewPaintable { |
| | 52 | |
| | 53 | public static boolean needMouseMove = false; |
| | 54 | enum Mode { circle, rotate, select } |
| | 55 | private Mode mode = null; |
| | 56 | private long mouseDownTime = 0; |
| | 57 | private Node selectedNode = null; |
| | 58 | private Way selectedWay = null; |
| | 59 | private WaySegment selectedSegment = null; |
| | 60 | private Color selectedColor; |
| | 61 | private boolean ctrl; |
| | 62 | private boolean alt; |
| | 63 | private boolean shift; |
| | 64 | |
| | 65 | double xoff; |
| | 66 | double yoff; |
| | 67 | double distance; |
| | 68 | int NodeCount; |
| | 69 | |
| | 70 | private Cursor oldCursor; |
| | 71 | private Point mousePos; |
| | 72 | private Point oldMousePos; |
| | 73 | private Point initialMousePos; |
| | 74 | private int initialMoveDelay = 200; |
| | 75 | |
| | 76 | /** |
| | 77 | * Create a new SelectAction |
| | 78 | * @param mapFrame The MapFrame this action belongs to. |
| | 79 | */ |
| | 80 | public CircleAction(MapFrame mapFrame) { |
| | 81 | super(tr("Create circle"), "circle/circle", tr("Create circle"), |
| | 82 | Shortcut.registerShortcut("mapmode:circle", tr("Mode: {0}", tr("Create circle")), KeyEvent.VK_C, Shortcut.GROUP_EDIT), |
| | 83 | mapFrame, |
| | 84 | getCursor("normal", "circle", Cursor.DEFAULT_CURSOR)); |
| | 85 | putValue("help", "Action/Extrude/Circle"); |
| | 86 | initialMoveDelay = Main.pref.getInteger("edit.initial-move-delay",200); |
| | 87 | selectedColor = Main.pref.getColor(marktr("selected"), Color.red); |
| | 88 | |
| | 89 | NodeCount = Main.pref.getInteger("createcircle.nodecount", 12); // TODO: re-use createcircle.nodecount or rename? |
| | 90 | if (NodeCount < 4) { |
| | 91 | NodeCount = 4; |
| | 92 | } |
| | 93 | if (NodeCount > 128) { |
| | 94 | NodeCount = 128; |
| | 95 | } |
| | 96 | if (NodeCount % 2 != 0) { |
| | 97 | // allow only even numbers |
| | 98 | NodeCount += 1; |
| | 99 | } |
| | 100 | // TODO: update preference accordingly? |
| | 101 | } |
| | 102 | |
| | 103 | private static Cursor getCursor(String name, String mod, int def) { |
| | 104 | try { |
| | 105 | return ImageProvider.getCursor(name, mod); |
| | 106 | } catch (Exception e) { |
| | 107 | } |
| | 108 | return Cursor.getPredefinedCursor(def); |
| | 109 | } |
| | 110 | |
| | 111 | private void setCursor(Cursor c) { |
| | 112 | if (oldCursor == null) { |
| | 113 | oldCursor = Main.map.mapView.getCursor(); |
| | 114 | Main.map.mapView.setCursor(c); |
| | 115 | } |
| | 116 | } |
| | 117 | |
| | 118 | private void restoreCursor() { |
| | 119 | if (oldCursor != null) { |
| | 120 | Main.map.mapView.setCursor(oldCursor); |
| | 121 | oldCursor = null; |
| | 122 | } |
| | 123 | } |
| | 124 | |
| | 125 | @Override public void enterMode() { |
| | 126 | super.enterMode(); |
| | 127 | Main.map.mapView.addMouseListener(this); |
| | 128 | Main.map.mapView.addMouseMotionListener(this); |
| | 129 | } |
| | 130 | |
| | 131 | @Override public void exitMode() { |
| | 132 | super.exitMode(); |
| | 133 | Main.map.mapView.removeMouseListener(this); |
| | 134 | Main.map.mapView.removeMouseMotionListener(this); |
| | 135 | Main.map.mapView.removeTemporaryLayer(this); |
| | 136 | |
| | 137 | } |
| | 138 | |
| | 139 | @Override public void mouseMoved(MouseEvent e) { |
| | 140 | if(!Main.map.mapView.isActiveLayerDrawable()) |
| | 141 | return; |
| | 142 | if (needMouseMove && mode == Mode.rotate) { |
| | 143 | mouseDragged(e); |
| | 144 | } |
| | 145 | |
| | 146 | updateKeyModifiers(e); |
| | 147 | mousePos = e.getPoint(); |
| | 148 | } |
| | 149 | |
| | 150 | private void updateKeyModifiers(MouseEvent e) { |
| | 151 | ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0; |
| | 152 | alt = (e.getModifiers() & (ActionEvent.ALT_MASK|InputEvent.ALT_GRAPH_MASK)) != 0; |
| | 153 | shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0; |
| | 154 | } |
| | 155 | |
| | 156 | @Override public void mouseDragged(MouseEvent e) { |
| | 157 | if(!Main.map.mapView.isActiveLayerVisible()) |
| | 158 | return; |
| | 159 | |
| | 160 | if (mode == Mode.select) return; |
| | 161 | |
| | 162 | // do not count anything as a move if it lasts less than 100 milliseconds. |
| | 163 | if (((mode == Mode.circle) || (mode == Mode.rotate)) && (System.currentTimeMillis() - mouseDownTime < initialMoveDelay)) return; |
| | 164 | |
| | 165 | if ((e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) == 0) |
| | 166 | return; |
| | 167 | |
| | 168 | if (mousePos == null) { |
| | 169 | mousePos = e.getPoint(); |
| | 170 | return; |
| | 171 | } |
| | 172 | |
| | 173 | if (mode == Mode.rotate) { |
| | 174 | EastNorth mouseEN = Main.map.mapView.getEastNorth(e.getX(), e.getY()); |
| | 175 | EastNorth mouseStartEN = Main.map.mapView.getEastNorth(mousePos.x, mousePos.y); |
| | 176 | double dx = mouseEN.east() - mouseStartEN.east(); |
| | 177 | double dy = mouseEN.north() - mouseStartEN.north(); |
| | 178 | if (dx == 0 && dy == 0) |
| | 179 | return; |
| | 180 | |
| | 181 | Collection<OsmPrimitive> selection = Main.ds.getSelectedNodesAndWays(); |
| | 182 | Collection<Node> affectedNodes = AllNodesVisitor.getAllNodes(selection); |
| | 183 | |
| | 184 | if (affectedNodes.size() < 2) return; |
| | 185 | |
| | 186 | Command c = !Main.main.undoRedo.commands.isEmpty() ? Main.main.undoRedo.commands.getLast() : null; |
| | 187 | if (c instanceof SequenceCommand) { |
| | 188 | c = ((SequenceCommand)c).getLastCommand(); |
| | 189 | } |
| | 190 | |
| | 191 | if (c instanceof RotateCommand && affectedNodes.equals(((RotateCommand)c).getRotatedNodes())) { |
| | 192 | ((RotateCommand)c).rotateAgain(mouseStartEN, mouseEN); |
| | 193 | } else { |
| | 194 | Main.main.undoRedo.add(new RotateCommand(selection, mouseStartEN, mouseEN)); |
| | 195 | } |
| | 196 | } |
| | 197 | |
| | 198 | Main.map.mapView.repaint(); |
| | 199 | mousePos = e.getPoint(); |
| | 200 | |
| | 201 | } |
| | 202 | |
| | 203 | @Override public void mousePressed(MouseEvent e) { |
| | 204 | if(!Main.map.mapView.isActiveLayerVisible()) |
| | 205 | return; |
| | 206 | if (!(Boolean)this.getValue("active")) return; |
| | 207 | if (e.getButton() != MouseEvent.BUTTON1) |
| | 208 | return; |
| | 209 | |
| | 210 | ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0; |
| | 211 | alt = (e.getModifiers() & (ActionEvent.ALT_MASK|InputEvent.ALT_GRAPH_MASK)) != 0; |
| | 212 | shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0; |
| | 213 | |
| | 214 | mouseDownTime = System.currentTimeMillis(); |
| | 215 | |
| | 216 | if (e.getClickCount() > 1 && mousePos != null && mousePos.equals(oldMousePos)) { |
| | 217 | // double-click, work with the selected way right here |
| | 218 | |
| | 219 | List<OsmPrimitive> sel = new ArrayList<OsmPrimitive>(Main.ds.getSelected()); |
| | 220 | if (sel.size() == 1 && sel.get(0) instanceof Way) { |
| | 221 | selectedWay = (Way) sel.get(0); |
| | 222 | } else { |
| | 223 | selectedWay = null; |
| | 224 | } |
| | 225 | |
| | 226 | if (selectedWay != null) { |
| | 227 | if (selectedWay.nodes.get(0).equals(selectedWay.nodes.get(selectedWay.nodes.size()-1))) { |
| | 228 | // we have a closed way |
| | 229 | Collection<Command> cmds = new LinkedList<Command>(); |
| | 230 | |
| | 231 | makeCircle(null, selectedWay, cmds); |
| | 232 | |
| | 233 | if (selectedWay.nodes.size() < NodeCount) { |
| | 234 | Main.main.undoRedo.add(new SequenceCommand(tr("Adding nodes and align circle"), cmds)); |
| | 235 | } else { |
| | 236 | Main.main.undoRedo.add(new SequenceCommand(tr("Align circle"), cmds)); |
| | 237 | } |
| | 238 | } else { |
| | 239 | // we have an open way |
| | 240 | // check if segment is part of a longer way |
| | 241 | if (selectedWay.nodes.size() == 2) { |
| | 242 | Collection<Command> cmds = new LinkedList<Command>(); |
| | 243 | |
| | 244 | makeCircle(null, selectedWay, cmds); |
| | 245 | |
| | 246 | Main.main.undoRedo.add(new SequenceCommand(tr("Convert way into a circle"), cmds)); |
| | 247 | } |
| | 248 | } |
| | 249 | } |
| | 250 | } else { |
| | 251 | // single-click |
| | 252 | |
| | 253 | if (ctrl && shift) { |
| | 254 | // start rotating |
| | 255 | |
| | 256 | if (!Main.ds.getSelected().isEmpty()) { |
| | 257 | mode = Mode.rotate; |
| | 258 | setCursor(ImageProvider.getCursor("rotate", null)); |
| | 259 | } |
| | 260 | } else { |
| | 261 | List<OsmPrimitive> sel = new ArrayList<OsmPrimitive>(Main.ds.getSelectedNodesAndWays()); |
| | 262 | if (sel.size() == 1) { |
| | 263 | if (sel.get(0) instanceof Node) { |
| | 264 | selectedNode = (Node) sel.get(0); |
| | 265 | } |
| | 266 | if (sel.get(0) instanceof Way) { |
| | 267 | selectedWay = (Way) sel.get(0); |
| | 268 | } |
| | 269 | } |
| | 270 | |
| | 271 | Node nearestNode = Main.map.mapView.getNearestNode(e.getPoint()); |
| | 272 | Way nearestWay = Main.map.mapView.getNearestWay(e.getPoint()); |
| | 273 | |
| | 274 | if (nearestNode == null && nearestWay == null) { |
| | 275 | // nothing selected: deselect |
| | 276 | mode = Mode.select; |
| | 277 | |
| | 278 | selectedNode = null; |
| | 279 | selectedWay = null; |
| | 280 | |
| | 281 | Main.ds.setSelected(); |
| | 282 | } else { |
| | 283 | if (nearestNode == null) { |
| | 284 | // only way select |
| | 285 | selectedNode = null; |
| | 286 | |
| | 287 | if (selectedWay != nearestWay) { |
| | 288 | // change selection |
| | 289 | mode = Mode.select; |
| | 290 | |
| | 291 | selectedWay = nearestWay; |
| | 292 | |
| | 293 | Main.ds.setSelected(nearestWay); |
| | 294 | |
| | 295 | } |
| | 296 | } else { |
| | 297 | // only node select |
| | 298 | selectedWay = null; |
| | 299 | |
| | 300 | if (selectedNode != nearestNode) { |
| | 301 | // change selection |
| | 302 | mode = Mode.select; |
| | 303 | |
| | 304 | selectedNode = nearestNode; |
| | 305 | |
| | 306 | Main.ds.setSelected(nearestNode); |
| | 307 | } else { |
| | 308 | // switch to circle mode |
| | 309 | mode = Mode.circle; |
| | 310 | } |
| | 311 | } |
| | 312 | } |
| | 313 | oldCursor = Main.map.mapView.getCursor(); |
| | 314 | } |
| | 315 | |
| | 316 | } |
| | 317 | oldMousePos = mousePos; |
| | 318 | |
| | 319 | updateStatusLine(); |
| | 320 | Main.map.mapView.addTemporaryLayer(this); |
| | 321 | Main.map.mapView.repaint(); |
| | 322 | |
| | 323 | mousePos = e.getPoint(); |
| | 324 | initialMousePos = e.getPoint(); |
| | 325 | } |
| | 326 | |
| | 327 | @Override public void mouseReleased(MouseEvent e) { |
| | 328 | if(!Main.map.mapView.isActiveLayerVisible()) |
| | 329 | return; |
| | 330 | |
| | 331 | if (mode == Mode.circle) { |
| | 332 | if (selectedNode == null) { |
| | 333 | mode = null; |
| | 334 | return; |
| | 335 | } |
| | 336 | if (mousePos.distance(initialMousePos) > 10) { |
| | 337 | // Create the Circle |
| | 338 | |
| | 339 | Collection<Command> cmds = new LinkedList<Command>(); |
| | 340 | Collection<Node> nodes = new LinkedList<Node>(); |
| | 341 | |
| | 342 | nodes.add(selectedNode); |
| | 343 | Node n2 = Main.map.mapView.getNearestNode(e.getPoint()); |
| | 344 | if (n2 == null) { |
| | 345 | n2 = new Node(Main.proj.eastNorth2latlon(Main.map.mapView.getEastNorth(mousePos.x, mousePos.y))); |
| | 346 | cmds.add(new AddCommand(n2)); |
| | 347 | } |
| | 348 | nodes.add(n2); |
| | 349 | |
| | 350 | makeCircle(nodes, null, cmds); |
| | 351 | |
| | 352 | Main.main.undoRedo.add(new SequenceCommand(tr("Create Circle"), cmds)); |
| | 353 | |
| | 354 | } |
| | 355 | selectedNode = null; |
| | 356 | } |
| | 357 | |
| | 358 | restoreCursor(); |
| | 359 | |
| | 360 | Main.map.mapView.removeTemporaryLayer(this); |
| | 361 | mode = null; |
| | 362 | updateStatusLine(); |
| | 363 | Main.map.mapView.repaint(); |
| | 364 | } |
| | 365 | |
| | 366 | private EastNorth calcCenter(Collection<Node> nodes) { |
| | 367 | // this is a more precise version of the one introduced in Rev.1713 of the AlignInCircle action |
| | 368 | |
| | 369 | BigDecimal area = new BigDecimal("0"); |
| | 370 | BigDecimal north = new BigDecimal("0"); |
| | 371 | BigDecimal east = new BigDecimal("0"); |
| | 372 | |
| | 373 | // Integrate the area, east and north centroid, we'll compute the final value based on the result of integration |
| | 374 | for (int i = 0; i < nodes.size() - 1; i++) { |
| | 375 | EastNorth n0 = ((Node) nodes.toArray()[i]).getEastNorth(); |
| | 376 | EastNorth n1 = ((Node) nodes.toArray()[i+1]).getEastNorth(); |
| | 377 | |
| | 378 | BigDecimal x0 = new BigDecimal(n0.east()); |
| | 379 | BigDecimal y0 = new BigDecimal(n0.north()); |
| | 380 | BigDecimal x1 = new BigDecimal(n1.east()); |
| | 381 | BigDecimal y1 = new BigDecimal(n1.north()); |
| | 382 | |
| | 383 | BigDecimal k = x0.multiply(y1, MathContext.DECIMAL128).subtract(y0.multiply(x1, MathContext.DECIMAL128)); |
| | 384 | |
| | 385 | area = area.add(k, MathContext.DECIMAL128); |
| | 386 | east = east.add(k.multiply(x0.add(x1, MathContext.DECIMAL128), MathContext.DECIMAL128)); |
| | 387 | north = north.add(k.multiply(y0.add(y1, MathContext.DECIMAL128), MathContext.DECIMAL128)); |
| | 388 | |
| | 389 | } |
| | 390 | |
| | 391 | BigDecimal d = new BigDecimal("2"); |
| | 392 | area = area.divide(d, MathContext.DECIMAL128); |
| | 393 | d = new BigDecimal("6"); |
| | 394 | north = north.divide(d.multiply(area, MathContext.DECIMAL128), MathContext.DECIMAL128); |
| | 395 | east = east.divide(d.multiply(area, MathContext.DECIMAL128), MathContext.DECIMAL128); |
| | 396 | |
| | 397 | EastNorth center = new EastNorth(east.doubleValue(), north.doubleValue()); |
| | 398 | |
| | 399 | return center; |
| | 400 | } |
| | 401 | |
| | 402 | private double calcAngle(double xc, double yc, double x, double y) { |
| | 403 | // calculate the angle from xc|yc to x|y |
| | 404 | if (xc == x && yc == y) |
| | 405 | return 0; // actually invalid, but we won't have this case in this context |
| | 406 | double yd = Math.abs(y - yc); |
| | 407 | if (yd == 0 && xc < x) |
| | 408 | return 0; |
| | 409 | if (yd == 0 && xc > x) |
| | 410 | return Math.PI; |
| | 411 | double xd = Math.abs(x - xc); |
| | 412 | double a = Math.atan2(xd, yd); |
| | 413 | if (y > yc) { |
| | 414 | a = Math.PI - a; |
| | 415 | } |
| | 416 | if (x < xc) { |
| | 417 | a = -a; |
| | 418 | } |
| | 419 | a = 1.5*Math.PI + a; |
| | 420 | if (a < 0) { |
| | 421 | a += 2*Math.PI; |
| | 422 | } |
| | 423 | if (a >= 2*Math.PI) { |
| | 424 | a -= 2*Math.PI; |
| | 425 | } |
| | 426 | return a; |
| | 427 | } |
| | 428 | |
| | 429 | private void fixAngleAndRadius(Collection<Command> cmds, Way way, EastNorth center, double r, int direction, boolean ignoreAngle) { |
| | 430 | // run through all nodes and set radius and angles |
| | 431 | |
| | 432 | double step = direction * 2 * Math.PI / (way.nodes.size() - 1); |
| | 433 | double a = 0; |
| | 434 | for (int i = 0; i < way.nodes.size() - 1; i++) { |
| | 435 | Node n = ((Node)way.nodes.toArray()[i]); |
| | 436 | |
| | 437 | if (i == 0 || ignoreAngle) { |
| | 438 | a = calcAngle(center.east(), center.north(), n.getEastNorth().east(), n.getEastNorth().north()); |
| | 439 | } |
| | 440 | Node p = newNode(center, r, a); |
| | 441 | if (p.getCoor().isOutSideWorld()) { |
| | 442 | JOptionPane.showMessageDialog(Main.parent, tr("Cannot move objects outside of the world.")); |
| | 443 | return; |
| | 444 | } |
| | 445 | cmds.add(new MoveCommand(n, p.getEastNorth().east() - n.getEastNorth().east(), p.getEastNorth().north() - n.getEastNorth().north())); |
| | 446 | |
| | 447 | a += step; |
| | 448 | } |
| | 449 | |
| | 450 | } |
| | 451 | |
| | 452 | private Collection<Node> findNodes(Collection<Node> nodes, EastNorth nc, double ma, double mb) { |
| | 453 | |
| | 454 | Collection<Node> result = new LinkedList<Node>(); |
| | 455 | double min = ma; |
| | 456 | double max = mb; |
| | 457 | if (min > max) { |
| | 458 | min = mb; |
| | 459 | max = ma; |
| | 460 | } |
| | 461 | |
| | 462 | for (Node n : nodes) { |
| | 463 | double a = calcAngle(nc.east(), nc.north(), n.getEastNorth().east(), n.getEastNorth().north()); |
| | 464 | if (a >= min && a <= max) { |
| | 465 | result.add(n); |
| | 466 | } |
| | 467 | } |
| | 468 | |
| | 469 | return result; |
| | 470 | } |
| | 471 | |
| | 472 | private Node newNode(EastNorth center, double r, double a) { |
| | 473 | double x = center.east() + r * Math.cos(a); |
| | 474 | double y = center.north() + r * Math.sin(a); |
| | 475 | Node p = new Node(Main.proj.eastNorth2latlon(new EastNorth(x, y))); |
| | 476 | |
| | 477 | return p; |
| | 478 | } |
| | 479 | |
| | 480 | private void makeCircle(Collection<Node> nodes, Way way, Collection<Command> cmds) { |
| | 481 | |
| | 482 | // sanity checks |
| | 483 | if (way == null && nodes == null) return; |
| | 484 | if (way != null) { |
| | 485 | // if the way has more than 2 nodes, it should be closed |
| | 486 | if ((way.nodes.size() > 2) && !way.nodes.get(0).equals(way.nodes.get(way.nodes.size()-1))) return; |
| | 487 | // use nodes of the way for the process |
| | 488 | if (nodes == null) { |
| | 489 | nodes = new LinkedList<Node>(); |
| | 490 | nodes.addAll(way.nodes); |
| | 491 | } |
| | 492 | } |
| | 493 | if (nodes.size() < 2) return; // we need at least 2 nodes |
| | 494 | |
| | 495 | |
| | 496 | // now make a circle |
| | 497 | if (nodes.size() == 2) { |
| | 498 | // 2 nodes or 1 segment for diameter |
| | 499 | |
| | 500 | Node n1 = ((Node)nodes.toArray()[0]); |
| | 501 | Node n2 = ((Node)nodes.toArray()[1]); |
| | 502 | |
| | 503 | // calculate the center |
| | 504 | EastNorth center = new EastNorth(0.5 * (n1.getEastNorth().east() + n2.getEastNorth().east()), 0.5 * (n1.getEastNorth().north() + n2.getEastNorth().north())); |
| | 505 | |
| | 506 | // calculate the radius (r) |
| | 507 | double r = Math.sqrt(Math.pow(center.east() - n1.getEastNorth().east(), 2) + Math.pow(center.north() - n1.getEastNorth().north(), 2)); |
| | 508 | |
| | 509 | // find direction |
| | 510 | double a1 = calcAngle(center.east(), center.north(), n1.getEastNorth().east(), n1.getEastNorth().north()); |
| | 511 | double a2 = calcAngle(center.east(), center.north(), n2.getEastNorth().east(), n2.getEastNorth().north()); |
| | 512 | int direction = -1; |
| | 513 | if (a1 < a2) { direction = -direction; } |
| | 514 | |
| | 515 | double step = direction * 2 * Math.PI / NodeCount; |
| | 516 | |
| | 517 | Way newWay; |
| | 518 | if (way == null) { |
| | 519 | newWay = new Way(); |
| | 520 | } else { |
| | 521 | newWay = new Way(way); |
| | 522 | newWay.nodes.clear(); |
| | 523 | } |
| | 524 | |
| | 525 | // no need for gizmos here - create step by step |
| | 526 | |
| | 527 | // add first node |
| | 528 | newWay.nodes.add(((Node)nodes.toArray()[0])); |
| | 529 | double a = a1; |
| | 530 | |
| | 531 | // add first bunch of nodes |
| | 532 | while (newWay.nodes.size() < NodeCount / 2 ) { |
| | 533 | Node n = newNode(center, r, a + step); |
| | 534 | if (n.getCoor().isOutSideWorld()) { |
| | 535 | JOptionPane.showMessageDialog(Main.parent, tr("Cannot add a node outside of the world.")); |
| | 536 | return; |
| | 537 | } |
| | 538 | newWay.nodes.add(n); |
| | 539 | cmds.add(new AddCommand(n)); |
| | 540 | |
| | 541 | a += step; |
| | 542 | } |
| | 543 | |
| | 544 | // add second node the hard way |
| | 545 | newWay.nodes.add(((Node)nodes.toArray()[1])); |
| | 546 | a += step; |
| | 547 | |
| | 548 | // add second bunch of nodes |
| | 549 | while (newWay.nodes.size() < NodeCount) { |
| | 550 | Node n = newNode(center, r, a + step); |
| | 551 | if (n.getCoor().isOutSideWorld()) { |
| | 552 | JOptionPane.showMessageDialog(Main.parent, tr("Cannot add a node outside of the world.")); |
| | 553 | return; |
| | 554 | } |
| | 555 | newWay.nodes.add(n); |
| | 556 | cmds.add(new AddCommand(n)); |
| | 557 | |
| | 558 | a += step; |
| | 559 | } |
| | 560 | |
| | 561 | // close the circle |
| | 562 | newWay.nodes.add(newWay.nodes.get(0)); |
| | 563 | |
| | 564 | fixAngleAndRadius(cmds, newWay, center, r, direction, false); // not needed actually |
| | 565 | |
| | 566 | if (way == null) { |
| | 567 | cmds.add(new AddCommand(newWay)); |
| | 568 | } else { |
| | 569 | cmds.add(new ChangeCommand(way, newWay)); |
| | 570 | } |
| | 571 | |
| | 572 | } else { |
| | 573 | // more than 2 nodes or closed way |
| | 574 | |
| | 575 | // calculate center and direction |
| | 576 | EastNorth center = calcCenter(nodes); |
| | 577 | double area = 0; |
| | 578 | for (int i=0; i<nodes.size(); i++) { |
| | 579 | EastNorth n0 = ((Node) nodes.toArray()[i]).getEastNorth(); |
| | 580 | EastNorth n1 = ((Node) nodes.toArray()[(i + 1) % nodes.size()]).getEastNorth(); |
| | 581 | area += n0.east() * n1.north() - n1.east() * n0.north(); |
| | 582 | } |
| | 583 | int direction = -1; |
| | 584 | if (area > 0) { direction = -direction; } |
| | 585 | |
| | 586 | // calculate radius |
| | 587 | double r = 0; |
| | 588 | for (Node n : nodes) { |
| | 589 | r += Math.sqrt(Math.pow(center.east() - n.getEastNorth().east(), 2) + Math.pow(center.north() - n.getEastNorth().north(), 2)); |
| | 590 | } |
| | 591 | r /= nodes.size(); |
| | 592 | |
| | 593 | // set start angle and step size |
| | 594 | double step = direction * 2 * Math.PI / NodeCount; |
| | 595 | double a = 0; |
| | 596 | if (direction < 0) { |
| | 597 | a = 2 * Math.PI; |
| | 598 | } |
| | 599 | |
| | 600 | if (!ctrl && nodes.size() < NodeCount) { |
| | 601 | // pressing CTRL will not add new nodes to the circle |
| | 602 | // add nodes if needed |
| | 603 | |
| | 604 | Way newWay; |
| | 605 | if (way == null) { |
| | 606 | newWay = new Way(); |
| | 607 | } else { |
| | 608 | newWay = new Way(way); |
| | 609 | newWay.nodes.clear(); |
| | 610 | } |
| | 611 | |
| | 612 | while (newWay.nodes.size() < NodeCount) { |
| | 613 | Collection<Node> oldNodes = findNodes(nodes, center, a, a + step); |
| | 614 | |
| | 615 | if (oldNodes.isEmpty()) { |
| | 616 | // no existing nodes in sector, create a new one |
| | 617 | Node n = newNode(center, r, a + 0.5 * step); |
| | 618 | if (n.getCoor().isOutSideWorld()) { |
| | 619 | JOptionPane.showMessageDialog(Main.parent, tr("Cannot add a node outside of the world.")); |
| | 620 | return; |
| | 621 | } |
| | 622 | newWay.nodes.add(n); |
| | 623 | cmds.add(new AddCommand(n)); |
| | 624 | } else { |
| | 625 | for (Node en : oldNodes) { |
| | 626 | if (!newWay.nodes.contains(en)) { |
| | 627 | newWay.nodes.add(en); |
| | 628 | } |
| | 629 | } |
| | 630 | } |
| | 631 | |
| | 632 | a += step; |
| | 633 | } |
| | 634 | |
| | 635 | newWay.nodes.add(newWay.nodes.get(0)); // close the circle |
| | 636 | |
| | 637 | fixAngleAndRadius(cmds, newWay, center, r, direction, alt); |
| | 638 | // pressing ALT will not change the angles |
| | 639 | |
| | 640 | if (way == null) { |
| | 641 | cmds.add(new AddCommand(newWay)); |
| | 642 | } else { |
| | 643 | cmds.add(new ChangeCommand(way, newWay)); |
| | 644 | } |
| | 645 | |
| | 646 | } else { |
| | 647 | // just fix circle |
| | 648 | if (way != null) { |
| | 649 | fixAngleAndRadius(cmds, way, center, r, direction, alt); |
| | 650 | // pressing ALT will not change the angles |
| | 651 | } |
| | 652 | } |
| | 653 | } |
| | 654 | |
| | 655 | return; |
| | 656 | } |
| | 657 | |
| | 658 | public void paint(Graphics g, MapView mv) { |
| | 659 | if (mode == Mode.circle) { |
| | 660 | // Draw a simple circle-placeholder |
| | 661 | if (selectedNode == null) return; |
| | 662 | |
| | 663 | // find the coordinates |
| | 664 | double x1 = selectedNode.getEastNorth().east(); |
| | 665 | double y1 = selectedNode.getEastNorth().north(); |
| | 666 | |
| | 667 | double x2 = mv.getEastNorth(mousePos.x, mousePos.y).east(); |
| | 668 | double y2 = mv.getEastNorth(mousePos.x, mousePos.y).north(); |
| | 669 | |
| | 670 | double xc = 0.5 * (x1 + x2); |
| | 671 | double yc = 0.5 * (y1 + y2); |
| | 672 | |
| | 673 | double a = calcAngle(xc, yc, x1, y1); |
| | 674 | double r = Math.sqrt(Math.pow(xc - x1, 2) + Math.pow(yc - y1, 2)); |
| | 675 | |
| | 676 | // show length of diameter in status |
| | 677 | distance = Main.proj.eastNorth2latlon(new EastNorth(x2, y2)).greatCircleDistance(Main.proj.eastNorth2latlon(new EastNorth(x1, y1))); |
| | 678 | Main.map.statusLine.setDist(distance); |
| | 679 | updateStatusLine(); |
| | 680 | |
| | 681 | // draw a circle |
| | 682 | Graphics2D g2 = (Graphics2D)g; |
| | 683 | g2.setColor(selectedColor); |
| | 684 | g2.setStroke(new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); |
| | 685 | GeneralPath b = new GeneralPath(); |
| | 686 | |
| | 687 | for (int i = 0; i <= NodeCount; i++) { |
| | 688 | double a1 = a + 2 * Math.PI * (1.0 - i / (double)NodeCount); |
| | 689 | |
| | 690 | double x = xc + r * Math.cos(a1); |
| | 691 | double y = yc + r * Math.sin(a1); |
| | 692 | Point p = mv.getPoint(new EastNorth(x, y)); |
| | 693 | if (i == 0) { |
| | 694 | b.moveTo(p.x, p.y); |
| | 695 | } else { |
| | 696 | b.lineTo(p.x, p.y); |
| | 697 | } |
| | 698 | |
| | 699 | } |
| | 700 | g2.draw(b); |
| | 701 | |
| | 702 | // draw the center point |
| | 703 | Point p = mv.getPoint(new EastNorth(xc, yc)); |
| | 704 | g2.drawArc(p.x, p.y, 2, 2, 0, 360); |
| | 705 | |
| | 706 | g2.setStroke(new BasicStroke(1)); |
| | 707 | } else { |
| | 708 | List<OsmPrimitive> sel = new ArrayList<OsmPrimitive>(Main.ds.getSelected()); |
| | 709 | if (sel.size() == 1 && sel.get(0) instanceof Way) { |
| | 710 | Way selWay = (Way) sel.get(0); |
| | 711 | |
| | 712 | if ((selWay.nodes.size() > 2) && selWay.nodes.get(0).equals(selWay.nodes.get(selWay.nodes.size()-1))) { |
| | 713 | // more than 2 nodes |
| | 714 | |
| | 715 | EastNorth center = calcCenter(selWay.nodes); |
| | 716 | Point p = mv.getPoint(center); |
| | 717 | |
| | 718 | // draw a point |
| | 719 | Graphics2D g2 = (Graphics2D)g; |
| | 720 | g2.setColor(selectedColor); |
| | 721 | g2.setStroke(new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); |
| | 722 | |
| | 723 | g2.drawArc(p.x, p.y, 2, 2, 0, 360); |
| | 724 | |
| | 725 | g2.setStroke(new BasicStroke(1)); |
| | 726 | |
| | 727 | } |
| | 728 | } |
| | 729 | } |
| | 730 | } |
| | 731 | |
| | 732 | @Override public String getModeHelpText() { |
| | 733 | if (mode == Mode.select) |
| | 734 | return tr("Release the mouse button to select the objects in the rectangle."); |
| | 735 | else if (mode == Mode.circle) |
| | 736 | return tr("Make a circle of the desired size, then release the mouse button."); |
| | 737 | else if (mode == Mode.rotate) |
| | 738 | return tr("Release the mouse button to stop rotating."); |
| | 739 | else { |
| | 740 | List<OsmPrimitive> sel = new ArrayList<OsmPrimitive>(Main.ds.getSelectedNodesAndWays()); |
| | 741 | if (sel.size() == 1 && sel.get(0) instanceof Way) { |
| | 742 | Way selWay = (Way) sel.get(0); |
| | 743 | |
| | 744 | if (selWay.nodes.size() == 2) |
| | 745 | return tr("Double-click on the selected way to create a circle."); |
| | 746 | else if ((selWay.nodes.size() > 2) && selWay.nodes.get(0).equals(selWay.nodes.get(selWay.nodes.size()-1))) |
| | 747 | return tr("Double-click on the selected way to align nodes in a circle."); |
| | 748 | |
| | 749 | } else if (sel.size() == 1 && sel.get(0) instanceof Node) |
| | 750 | return tr("Click on the selected node and move the mouse to create a circle."); |
| | 751 | } |
| | 752 | return tr("Select a way or node to make a circle."); |
| | 753 | } |
| | 754 | |
| | 755 | @Override public boolean layerIsSupported(Layer l) { |
| | 756 | return l instanceof OsmDataLayer; |
| | 757 | } |
| | 758 | } |