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