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 * RotateCommand rotates a number of objects around their centre. 015 * 016 * @author Frederik Ramm 017 */ 018public class RotateCommand extends TransformNodesCommand { 019 020 /** 021 * Pivot point 022 */ 023 private final EastNorth pivot; 024 025 /** 026 * angle of rotation starting click to pivot 027 */ 028 private final double startAngle; 029 030 /** 031 * computed rotation angle between starting click and current mouse pos 032 */ 033 private double rotationAngle; 034 035 /** 036 * Creates a RotateCommand. 037 * Assign the initial object set, compute pivot point and initial rotation angle. 038 * @param objects objects to fetch nodes from 039 * @param currentEN current east/north 040 */ 041 public RotateCommand(Collection<? extends OsmPrimitive> objects, EastNorth currentEN) { 042 super(objects); 043 044 pivot = getNodesCenter(); 045 startAngle = getAngle(currentEN); 046 rotationAngle = 0.0; 047 048 handleEvent(currentEN); 049 } 050 051 /** 052 * Get angle between the horizontal axis and the line formed by the pivot and given point. 053 * @param currentEN current east/north 054 * @return angle between the horizontal axis and the line formed by the pivot and given point 055 **/ 056 protected final double getAngle(EastNorth currentEN) { 057 if (pivot == null) 058 return 0.0; // should never happen by contract 059 return Math.atan2(currentEN.east()-pivot.east(), currentEN.north()-pivot.north()); 060 } 061 062 /** 063 * Compute new rotation angle and transform nodes accordingly. 064 */ 065 @Override 066 public final void handleEvent(EastNorth currentEN) { 067 double currentAngle = getAngle(currentEN); 068 rotationAngle = currentAngle - startAngle; 069 transformNodes(); 070 } 071 072 /** 073 * Set the rotation angle. 074 * @param rotationAngle The rotate angle 075 */ 076 protected void setRotationAngle(double rotationAngle) { 077 this.rotationAngle = rotationAngle; 078 } 079 080 /** 081 * Returns the rotation angle. 082 * @return The rotation angle 083 */ 084 public double getRotationAngle() { 085 return rotationAngle; 086 } 087 088 /** 089 * Rotate nodes. 090 */ 091 @Override 092 protected void transformNodes() { 093 double cosPhi = Math.cos(rotationAngle); 094 double sinPhi = Math.sin(rotationAngle); 095 for (Node n : nodes) { 096 EastNorth oldEastNorth = oldStates.get(n).getEastNorth(); 097 double x = oldEastNorth.east() - pivot.east(); 098 double y = oldEastNorth.north() - pivot.north(); 099 // CHECKSTYLE.OFF: SingleSpaceSeparator 100 double nx = cosPhi * x + sinPhi * y + pivot.east(); 101 double ny = -sinPhi * x + cosPhi * y + pivot.north(); 102 // CHECKSTYLE.ON: SingleSpaceSeparator 103 n.setEastNorth(new EastNorth(nx, ny)); 104 } 105 } 106 107 @Override 108 public String getDescriptionText() { 109 return trn("Rotate {0} node", "Rotate {0} nodes", nodes.size(), nodes.size()); 110 } 111 112 @Override 113 public int hashCode() { 114 return Objects.hash(super.hashCode(), pivot, startAngle, rotationAngle); 115 } 116 117 @Override 118 public boolean equals(Object obj) { 119 if (this == obj) return true; 120 if (obj == null || getClass() != obj.getClass()) return false; 121 if (!super.equals(obj)) return false; 122 RotateCommand that = (RotateCommand) obj; 123 return Double.compare(that.startAngle, startAngle) == 0 && 124 Double.compare(that.rotationAngle, rotationAngle) == 0 && 125 Objects.equals(pivot, that.pivot); 126 } 127}