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}