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    }