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 final EastNorth pivot;
023
024    /**
025     * angle of rotation starting click to pivot
026     */
027    private final 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     * @param objects objects to fetch nodes from
038     * @param currentEN cuurent eats/north
039     */
040    public RotateCommand(Collection<OsmPrimitive> objects, EastNorth currentEN) {
041        super(objects);
042
043        pivot = getNodesCenter();
044        startAngle = getAngle(currentEN);
045        rotationAngle = 0.0;
046
047        handleEvent(currentEN);
048    }
049
050    /**
051     * Get angle between the horizontal axis and the line formed by the pivot and given point.
052     * @param currentEN cuurent eats/north
053     * @return angle between the horizontal axis and the line formed by the pivot and given point
054     **/
055    protected final double getAngle(EastNorth currentEN) {
056        if (pivot == null)
057            return 0.0; // should never happen by contract
058        return Math.atan2(currentEN.east()-pivot.east(), currentEN.north()-pivot.north());
059    }
060
061    /**
062     * Compute new rotation angle and transform nodes accordingly.
063     */
064    @Override
065    public final void handleEvent(EastNorth currentEN) {
066        double currentAngle = getAngle(currentEN);
067        rotationAngle = currentAngle - startAngle;
068        transformNodes();
069    }
070
071    /**
072     * Rotate nodes.
073     */
074    @Override
075    protected void transformNodes() {
076        for (Node n : nodes) {
077            double cosPhi = Math.cos(rotationAngle);
078            double sinPhi = Math.sin(rotationAngle);
079            EastNorth oldEastNorth = oldStates.get(n).getEastNorth();
080            double x = oldEastNorth.east() - pivot.east();
081            double y = oldEastNorth.north() - pivot.north();
082            double nx =  cosPhi * x + sinPhi * y + pivot.east();
083            double ny = -sinPhi * x + cosPhi * y + pivot.north();
084            n.setEastNorth(new EastNorth(nx, ny));
085        }
086    }
087
088    @Override
089    public String getDescriptionText() {
090        return trn("Rotate {0} node", "Rotate {0} nodes", nodes.size(), nodes.size());
091    }
092
093    @Override
094    public int hashCode() {
095        final int prime = 31;
096        int result = super.hashCode();
097        result = prime * result + ((pivot == null) ? 0 : pivot.hashCode());
098        long temp;
099        temp = Double.doubleToLongBits(rotationAngle);
100        result = prime * result + (int) (temp ^ (temp >>> 32));
101        temp = Double.doubleToLongBits(startAngle);
102        result = prime * result + (int) (temp ^ (temp >>> 32));
103        return result;
104    }
105
106    @Override
107    public boolean equals(Object obj) {
108        if (this == obj)
109            return true;
110        if (!super.equals(obj))
111            return false;
112        if (getClass() != obj.getClass())
113            return false;
114        RotateCommand other = (RotateCommand) obj;
115        if (pivot == null) {
116            if (other.pivot != null)
117                return false;
118        } else if (!pivot.equals(other.pivot))
119            return false;
120        if (Double.doubleToLongBits(rotationAngle) != Double.doubleToLongBits(other.rotationAngle))
121            return false;
122        if (Double.doubleToLongBits(startAngle) != Double.doubleToLongBits(other.startAngle))
123            return false;
124        return true;
125    }
126}