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 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 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     */
034    public ScaleCommand(Collection<OsmPrimitive> objects, EastNorth currentEN) {
035        super(objects);
036
037        pivot = getNodesCenter();
038
039        // We remember the very first position of the mouse for this action.
040        // Note that SelectAction will keep the same ScaleCommand when the user
041        // releases the button and presses it again with the same modifiers.
042        // The very first point of this operation is stored here.
043        startEN   = currentEN;
044
045        handleEvent(currentEN);
046    }
047
048    /**
049     * Compute new scaling factor and transform nodes accordingly.
050     */
051    @Override
052    public final void handleEvent(EastNorth currentEN) {
053        double startAngle = Math.atan2(startEN.east()-pivot.east(), startEN.north()-pivot.north());
054        double endAngle = Math.atan2(currentEN.east()-pivot.east(), currentEN.north()-pivot.north());
055        double startDistance = pivot.distance(startEN);
056        double currentDistance = pivot.distance(currentEN);
057        scalingFactor = Math.cos(startAngle-endAngle) * currentDistance / startDistance;
058        transformNodes();
059    }
060
061    /**
062     * Scale nodes.
063     */
064    @Override
065    protected void transformNodes() {
066        for (Node n : nodes) {
067            EastNorth oldEastNorth = oldStates.get(n).getEastNorth();
068            double dx = oldEastNorth.east() - pivot.east();
069            double dy = oldEastNorth.north() - pivot.north();
070            double nx = pivot.east() + scalingFactor * dx;
071            double ny = pivot.north() + scalingFactor * dy;
072            n.setEastNorth(new EastNorth(nx, ny));
073        }
074    }
075
076    @Override
077    public String getDescriptionText() {
078        return trn("Scale {0} node", "Scale {0} nodes", nodes.size(), nodes.size());
079    }
080
081    @Override
082    public int hashCode() {
083        final int prime = 31;
084        int result = super.hashCode();
085        result = prime * result + ((pivot == null) ? 0 : pivot.hashCode());
086        long temp;
087        temp = Double.doubleToLongBits(scalingFactor);
088        result = prime * result + (int) (temp ^ (temp >>> 32));
089        result = prime * result + ((startEN == null) ? 0 : startEN.hashCode());
090        return result;
091    }
092
093    @Override
094    public boolean equals(Object obj) {
095        if (this == obj)
096            return true;
097        if (!super.equals(obj))
098            return false;
099        if (getClass() != obj.getClass())
100            return false;
101        ScaleCommand other = (ScaleCommand) obj;
102        if (pivot == null) {
103            if (other.pivot != null)
104                return false;
105        } else if (!pivot.equals(other.pivot))
106            return false;
107        if (Double.doubleToLongBits(scalingFactor) != Double.doubleToLongBits(other.scalingFactor))
108            return false;
109        if (startEN == null) {
110            if (other.startEN != null)
111                return false;
112        } else if (!startEN.equals(other.startEN))
113            return false;
114        return true;
115    }
116}