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
012public class ScaleCommand extends TransformNodesCommand {
013    /**
014     * Pivot point
015     */
016    private final EastNorth pivot;
017
018    /**
019     * Current scaling factor applied
020     */
021    private double scalingFactor;
022
023    /**
024     * World position of the mouse when the user started the command.
025     */
026    private final EastNorth startEN;
027
028    /**
029     * Creates a ScaleCommand.
030     * Assign the initial object set, compute pivot point.
031     * Computation of pivot point is done by the same rules that are used in
032     * the "align nodes in circle" action.
033     * @param objects objects to fetch nodes from
034     * @param currentEN cuurent eats/north
035     */
036    public ScaleCommand(Collection<OsmPrimitive> objects, EastNorth currentEN) {
037        super(objects);
038
039        pivot = getNodesCenter();
040
041        // We remember the very first position of the mouse for this action.
042        // Note that SelectAction will keep the same ScaleCommand when the user
043        // releases the button and presses it again with the same modifiers.
044        // The very first point of this operation is stored here.
045        startEN   = currentEN;
046
047        handleEvent(currentEN);
048    }
049
050    /**
051     * Compute new scaling factor and transform nodes accordingly.
052     */
053    @Override
054    public final void handleEvent(EastNorth currentEN) {
055        double startAngle = Math.atan2(startEN.east()-pivot.east(), startEN.north()-pivot.north());
056        double endAngle = Math.atan2(currentEN.east()-pivot.east(), currentEN.north()-pivot.north());
057        double startDistance = pivot.distance(startEN);
058        double currentDistance = pivot.distance(currentEN);
059        scalingFactor = Math.cos(startAngle-endAngle) * currentDistance / startDistance;
060        transformNodes();
061    }
062
063    /**
064     * Scale nodes.
065     */
066    @Override
067    protected void transformNodes() {
068        for (Node n : nodes) {
069            EastNorth oldEastNorth = oldStates.get(n).getEastNorth();
070            double dx = oldEastNorth.east() - pivot.east();
071            double dy = oldEastNorth.north() - pivot.north();
072            double nx = pivot.east() + scalingFactor * dx;
073            double ny = pivot.north() + scalingFactor * dy;
074            n.setEastNorth(new EastNorth(nx, ny));
075        }
076    }
077
078    @Override
079    public String getDescriptionText() {
080        return trn("Scale {0} node", "Scale {0} nodes", nodes.size(), nodes.size());
081    }
082
083    @Override
084    public int hashCode() {
085        final int prime = 31;
086        int result = super.hashCode();
087        result = prime * result + ((pivot == null) ? 0 : pivot.hashCode());
088        long temp;
089        temp = Double.doubleToLongBits(scalingFactor);
090        result = prime * result + (int) (temp ^ (temp >>> 32));
091        result = prime * result + ((startEN == null) ? 0 : startEN.hashCode());
092        return result;
093    }
094
095    @Override
096    public boolean equals(Object obj) {
097        if (this == obj)
098            return true;
099        if (!super.equals(obj))
100            return false;
101        if (getClass() != obj.getClass())
102            return false;
103        ScaleCommand other = (ScaleCommand) obj;
104        if (pivot == null) {
105            if (other.pivot != null)
106                return false;
107        } else if (!pivot.equals(other.pivot))
108            return false;
109        if (Double.doubleToLongBits(scalingFactor) != Double.doubleToLongBits(other.scalingFactor))
110            return false;
111        if (startEN == null) {
112            if (other.startEN != null)
113                return false;
114        } else if (!startEN.equals(other.startEN))
115            return false;
116        return true;
117    }
118}