001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.command;
003
004import static org.openstreetmap.josm.tools.I18n.trn;
005
006import java.util.Collection;
007
008import org.openstreetmap.josm.data.coor.EastNorth;
009import org.openstreetmap.josm.data.osm.Node;
010import org.openstreetmap.josm.data.osm.OsmPrimitive;
011
012/**
013 * RotateCommand rotates a number of objects around their centre.
014 *
015 * @author Frederik Ramm
016 */
017public class RotateCommand extends TransformNodesCommand {
018
019    /**
020     * Pivot point
021     */
022    private EastNorth pivot;
023
024    /**
025     * angle of rotation starting click to pivot
026     */
027    private double startAngle;
028
029    /**
030     * computed rotation angle between starting click and current mouse pos
031     */
032    private double rotationAngle;
033
034    /**
035     * Creates a RotateCommand.
036     * Assign the initial object set, compute pivot point and inital rotation angle.
037     */
038    public RotateCommand(Collection<OsmPrimitive> objects, EastNorth currentEN) {
039        super(objects);
040
041        pivot = getNodesCenter();
042        startAngle = getAngle(currentEN);
043        rotationAngle = 0.0;
044
045        handleEvent(currentEN);
046    }
047
048    /**
049     * Get angle between the horizontal axis and the line formed by the pivot and given point.
050     * @return angle between the horizontal axis and the line formed by the pivot and given point
051     **/
052    protected final double getAngle(EastNorth currentEN) {
053        if (pivot == null)
054            return 0.0; // should never happen by contract
055        return Math.atan2(currentEN.east()-pivot.east(), currentEN.north()-pivot.north());
056    }
057
058    /**
059     * Compute new rotation angle and transform nodes accordingly.
060     */
061    @Override
062    public final void handleEvent(EastNorth currentEN) {
063        double currentAngle = getAngle(currentEN);
064        rotationAngle = currentAngle - startAngle;
065        transformNodes();
066    }
067
068    /**
069     * Rotate nodes.
070     */
071    @Override
072    protected void transformNodes() {
073        for (Node n : nodes) {
074            double cosPhi = Math.cos(rotationAngle);
075            double sinPhi = Math.sin(rotationAngle);
076            EastNorth oldEastNorth = oldStates.get(n).getEastNorth();
077            double x = oldEastNorth.east() - pivot.east();
078            double y = oldEastNorth.north() - pivot.north();
079            double nx =  cosPhi * x + sinPhi * y + pivot.east();
080            double ny = -sinPhi * x + cosPhi * y + pivot.north();
081            n.setEastNorth(new EastNorth(nx, ny));
082        }
083    }
084
085    @Override
086    public String getDescriptionText() {
087        return trn("Rotate {0} node", "Rotate {0} nodes", nodes.size(), nodes.size());
088    }
089
090    @Override
091    public int hashCode() {
092        final int prime = 31;
093        int result = super.hashCode();
094        result = prime * result + ((pivot == null) ? 0 : pivot.hashCode());
095        long temp;
096        temp = Double.doubleToLongBits(rotationAngle);
097        result = prime * result + (int) (temp ^ (temp >>> 32));
098        temp = Double.doubleToLongBits(startAngle);
099        result = prime * result + (int) (temp ^ (temp >>> 32));
100        return result;
101    }
102
103    @Override
104    public boolean equals(Object obj) {
105        if (this == obj)
106            return true;
107        if (!super.equals(obj))
108            return false;
109        if (getClass() != obj.getClass())
110            return false;
111        RotateCommand other = (RotateCommand) obj;
112        if (pivot == null) {
113            if (other.pivot != null)
114                return false;
115        } else if (!pivot.equals(other.pivot))
116            return false;
117        if (Double.doubleToLongBits(rotationAngle) != Double.doubleToLongBits(other.rotationAngle))
118            return false;
119        if (Double.doubleToLongBits(startAngle) != Double.doubleToLongBits(other.startAngle))
120            return false;
121        return true;
122    }
123}