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}