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.Collections; 008import java.util.Iterator; 009import java.util.LinkedList; 010import java.util.List; 011import java.util.Objects; 012 013import javax.swing.Icon; 014 015import org.openstreetmap.josm.data.coor.EastNorth; 016import org.openstreetmap.josm.data.coor.LatLon; 017import org.openstreetmap.josm.data.osm.Node; 018import org.openstreetmap.josm.data.osm.OsmPrimitive; 019import org.openstreetmap.josm.data.osm.visitor.AllNodesVisitor; 020import org.openstreetmap.josm.data.projection.Projections; 021import org.openstreetmap.josm.tools.ImageProvider; 022 023/** 024 * MoveCommand moves a set of OsmPrimitives along the map. It can be moved again 025 * to collect several MoveCommands into one command. 026 * 027 * @author imi 028 */ 029public class MoveCommand extends Command { 030 /** 031 * The objects that should be moved. 032 */ 033 private Collection<Node> nodes = new LinkedList<>(); 034 /** 035 * Starting position, base command point, current (mouse-drag) position = startEN + (x,y) = 036 */ 037 private EastNorth startEN; 038 039 /** 040 * x difference movement. Coordinates are in northern/eastern 041 */ 042 private double x; 043 /** 044 * y difference movement. Coordinates are in northern/eastern 045 */ 046 private double y; 047 048 private double backupX; 049 private double backupY; 050 051 /** 052 * List of all old states of the objects. 053 */ 054 private final List<OldNodeState> oldState = new LinkedList<>(); 055 056 /** 057 * Constructs a new {@code MoveCommand} to move a primitive. 058 * @param osm The primitive to move 059 * @param x X difference movement. Coordinates are in northern/eastern 060 * @param y Y difference movement. Coordinates are in northern/eastern 061 */ 062 public MoveCommand(OsmPrimitive osm, double x, double y) { 063 this(Collections.singleton(osm), x, y); 064 } 065 066 /** 067 * Constructs a new {@code MoveCommand} to move a node. 068 * @param node The node to move 069 * @param position The new location (lat/lon) 070 */ 071 public MoveCommand(Node node, LatLon position) { 072 this(Collections.singleton((OsmPrimitive) node), Projections.project(position).subtract(node.getEastNorth())); 073 } 074 075 /** 076 * Constructs a new {@code MoveCommand} to move a collection of primitives. 077 * @param objects The primitives to move 078 * @param offset The movement vector 079 */ 080 public MoveCommand(Collection<OsmPrimitive> objects, EastNorth offset) { 081 this(objects, offset.getX(), offset.getY()); 082 } 083 084 /** 085 * Constructs a new {@code MoveCommand} and assign the initial object set and movement vector. 086 * @param objects The primitives to move 087 * @param x X difference movement. Coordinates are in northern/eastern 088 * @param y Y difference movement. Coordinates are in northern/eastern 089 */ 090 public MoveCommand(Collection<OsmPrimitive> objects, double x, double y) { 091 startEN = null; 092 saveCheckpoint(); // (0,0) displacement will be saved 093 this.x = x; 094 this.y = y; 095 this.nodes = AllNodesVisitor.getAllNodes(objects); 096 for (Node n : this.nodes) { 097 oldState.add(new OldNodeState(n)); 098 } 099 } 100 101 /** 102 * Constructs a new {@code MoveCommand} to move a collection of primitives. 103 * @param objects The primitives to move 104 * @param start The starting position (northern/eastern) 105 * @param end The ending position (northern/eastern) 106 */ 107 public MoveCommand(Collection<OsmPrimitive> objects, EastNorth start, EastNorth end) { 108 this(objects, end.getX()-start.getX(), end.getY()-start.getY()); 109 startEN = start; 110 } 111 112 /** 113 * Constructs a new {@code MoveCommand} to move a primitive. 114 * @param p The primitive to move 115 * @param start The starting position (northern/eastern) 116 * @param end The ending position (northern/eastern) 117 */ 118 public MoveCommand(OsmPrimitive p, EastNorth start, EastNorth end) { 119 this(Collections.singleton(p), end.getX()-start.getX(), end.getY()-start.getY()); 120 startEN = start; 121 } 122 123 /** 124 * Move the same set of objects again by the specified vector. The vectors 125 * are added together and so the resulting will be moved to the previous 126 * vector plus this one. 127 * 128 * The move is immediately executed and any undo will undo both vectors to 129 * the original position the objects had before first moving. 130 * 131 * @param x X difference movement. Coordinates are in northern/eastern 132 * @param y Y difference movement. Coordinates are in northern/eastern 133 */ 134 public void moveAgain(double x, double y) { 135 for (Node n : nodes) { 136 n.setEastNorth(n.getEastNorth().add(x, y)); 137 } 138 this.x += x; 139 this.y += y; 140 } 141 142 /** 143 * Move again to the specified coordinates. 144 * @param x X coordinate 145 * @param y Y coordinate 146 * @see #moveAgain 147 */ 148 public void moveAgainTo(double x, double y) { 149 moveAgain(x - this.x, y - this.y); 150 } 151 152 /** 153 * Change the displacement vector to have endpoint {@code currentEN}. 154 * starting point is startEN 155 * @param currentEN the new endpoint 156 */ 157 public void applyVectorTo(EastNorth currentEN) { 158 if (startEN == null) 159 return; 160 x = currentEN.getX() - startEN.getX(); 161 y = currentEN.getY() - startEN.getY(); 162 updateCoordinates(); 163 } 164 165 /** 166 * Changes base point of movement 167 * @param newDraggedStartPoint - new starting point after movement (where user clicks to start new drag) 168 */ 169 public void changeStartPoint(EastNorth newDraggedStartPoint) { 170 startEN = new EastNorth(newDraggedStartPoint.getX()-x, newDraggedStartPoint.getY()-y); 171 } 172 173 /** 174 * Save curent displacement to restore in case of some problems 175 */ 176 public final void saveCheckpoint() { 177 backupX = x; 178 backupY = y; 179 } 180 181 /** 182 * Restore old displacement in case of some problems 183 */ 184 public void resetToCheckpoint() { 185 x = backupX; 186 y = backupY; 187 updateCoordinates(); 188 } 189 190 private void updateCoordinates() { 191 Iterator<OldNodeState> it = oldState.iterator(); 192 for (Node n : nodes) { 193 OldNodeState os = it.next(); 194 if (os.getEastNorth() != null) { 195 n.setEastNorth(os.getEastNorth().add(x, y)); 196 } 197 } 198 } 199 200 @Override 201 public boolean executeCommand() { 202 for (Node n : nodes) { 203 // in case #3892 happens again 204 if (n == null) 205 throw new AssertionError("null detected in node list"); 206 EastNorth en = n.getEastNorth(); 207 if (en != null) { 208 n.setEastNorth(en.add(x, y)); 209 n.setModified(true); 210 } 211 } 212 return true; 213 } 214 215 @Override 216 public void undoCommand() { 217 Iterator<OldNodeState> it = oldState.iterator(); 218 for (Node n : nodes) { 219 OldNodeState os = it.next(); 220 n.setCoor(os.getLatlon()); 221 n.setModified(os.isModified()); 222 } 223 } 224 225 @Override 226 public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) { 227 for (OsmPrimitive osm : nodes) { 228 modified.add(osm); 229 } 230 } 231 232 @Override 233 public String getDescriptionText() { 234 return trn("Move {0} node", "Move {0} nodes", nodes.size(), nodes.size()); 235 } 236 237 @Override 238 public Icon getDescriptionIcon() { 239 return ImageProvider.get("data", "node"); 240 } 241 242 @Override 243 public Collection<Node> getParticipatingPrimitives() { 244 return nodes; 245 } 246 247 @Override 248 public int hashCode() { 249 return Objects.hash(super.hashCode(), nodes, startEN, x, y, backupX, backupY, oldState); 250 } 251 252 @Override 253 public boolean equals(Object obj) { 254 if (this == obj) return true; 255 if (obj == null || getClass() != obj.getClass()) return false; 256 if (!super.equals(obj)) return false; 257 MoveCommand that = (MoveCommand) obj; 258 return Double.compare(that.x, x) == 0 && 259 Double.compare(that.y, y) == 0 && 260 Double.compare(that.backupX, backupX) == 0 && 261 Double.compare(that.backupY, backupY) == 0 && 262 Objects.equals(nodes, that.nodes) && 263 Objects.equals(startEN, that.startEN) && 264 Objects.equals(oldState, that.oldState); 265 } 266}