001 // License: GPL. For details, see LICENSE file.
002 package org.openstreetmap.josm.command;
003
004 import static org.openstreetmap.josm.tools.I18n.trn;
005
006 import java.util.Collection;
007
008 import javax.swing.Icon;
009
010 import org.openstreetmap.josm.data.coor.EastNorth;
011 import org.openstreetmap.josm.data.osm.Node;
012 import org.openstreetmap.josm.data.osm.OsmPrimitive;
013 import org.openstreetmap.josm.tools.ImageProvider;
014
015 /**
016 * RotateCommand rotates a number of objects around their centre.
017 *
018 * @author Frederik Ramm <frederik@remote.org>
019 */
020 public class RotateCommand extends TransformNodesCommand {
021
022 /**
023 * Pivot point
024 */
025 private EastNorth pivot;
026
027 /**
028 * World position of the mouse when the user started the command.
029 *
030 */
031 EastNorth startEN = null;
032
033 /**
034 * angle of rotation starting click to pivot
035 */
036 private double startAngle = 0.0;
037
038 /**
039 * computed rotation angle between starting click and current mouse pos
040 */
041 private double rotationAngle = 0.0;
042
043 /**
044 * Creates a RotateCommand.
045 * Assign the initial object set, compute pivot point and inital rotation angle.
046 */
047 public RotateCommand(Collection<OsmPrimitive> objects, EastNorth currentEN) {
048 super(objects);
049
050 pivot = getNodesCenter();
051
052 // We remember the very first position of the mouse for this action.
053 // Note that SelectAction will keep the same ScaleCommand when the user
054 // releases the button and presses it again with the same modifiers.
055 // The very first point of this operation is stored here.
056 startEN = currentEN;
057
058 startAngle = getAngle(currentEN);
059 rotationAngle = 0.0;
060
061 handleEvent(currentEN);
062 }
063
064 /**
065 * Get angle between the horizontal axis and the line formed by the pivot and give points.
066 **/
067 protected double getAngle(EastNorth currentEN) {
068 if ( pivot == null )
069 return 0.0; // should never happen by contract
070 return Math.atan2(currentEN.east()-pivot.east(), currentEN.north()-pivot.north());
071 }
072
073 /**
074 * Compute new rotation angle and transform nodes accordingly.
075 */
076 @Override
077 public void handleEvent(EastNorth currentEN) {
078 double currentAngle = getAngle(currentEN);
079 rotationAngle = currentAngle - startAngle;
080 transformNodes();
081 }
082
083 /**
084 * Rotate nodes.
085 */
086 @Override
087 protected void transformNodes() {
088 for (Node n : nodes) {
089 double cosPhi = Math.cos(rotationAngle);
090 double sinPhi = Math.sin(rotationAngle);
091 EastNorth oldEastNorth = oldStates.get(n).eastNorth;
092 double x = oldEastNorth.east() - pivot.east();
093 double y = oldEastNorth.north() - pivot.north();
094 double nx = cosPhi * x + sinPhi * y + pivot.east();
095 double ny = -sinPhi * x + cosPhi * y + pivot.north();
096 n.setEastNorth(new EastNorth(nx, ny));
097 }
098 }
099
100 @Override
101 public String getDescriptionText() {
102 return trn("Rotate {0} node", "Rotate {0} nodes", nodes.size(), nodes.size());
103 }
104
105 @Override
106 public Icon getDescriptionIcon() {
107 return ImageProvider.get("data", "node");
108 }
109 }