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 Objects.requireNonNull(objects, "objects"); 096 this.nodes = AllNodesVisitor.getAllNodes(objects); 097 for (Node n : this.nodes) { 098 oldState.add(new OldNodeState(n)); 099 } 100 } 101 102 /** 103 * Constructs a new {@code MoveCommand} to move a collection of primitives. 104 * @param objects The primitives to move 105 * @param start The starting position (northern/eastern) 106 * @param end The ending position (northern/eastern) 107 */ 108 public MoveCommand(Collection<OsmPrimitive> objects, EastNorth start, EastNorth end) { 109 this( 110 Objects.requireNonNull(objects, "objects"), 111 Objects.requireNonNull(end, "end").getX() - Objects.requireNonNull(start, "start").getX(), 112 Objects.requireNonNull(end, "end").getY() - Objects.requireNonNull(start, "start").getY()); 113 startEN = start; 114 } 115 116 /** 117 * Constructs a new {@code MoveCommand} to move a primitive. 118 * @param p The primitive to move 119 * @param start The starting position (northern/eastern) 120 * @param end The ending position (northern/eastern) 121 */ 122 public MoveCommand(OsmPrimitive p, EastNorth start, EastNorth end) { 123 this( 124 Collections.singleton(Objects.requireNonNull(p, "p")), 125 Objects.requireNonNull(end, "end").getX() - Objects.requireNonNull(start, "start").getX(), 126 Objects.requireNonNull(end, "end").getY() - Objects.requireNonNull(start, "start").getY()); 127 startEN = start; 128 } 129 130 /** 131 * Move the same set of objects again by the specified vector. The vectors 132 * are added together and so the resulting will be moved to the previous 133 * vector plus this one. 134 * 135 * The move is immediately executed and any undo will undo both vectors to 136 * the original position the objects had before first moving. 137 * 138 * @param x X difference movement. Coordinates are in northern/eastern 139 * @param y Y difference movement. Coordinates are in northern/eastern 140 */ 141 public void moveAgain(double x, double y) { 142 for (Node n : nodes) { 143 n.setEastNorth(n.getEastNorth().add(x, y)); 144 } 145 this.x += x; 146 this.y += y; 147 } 148 149 /** 150 * Move again to the specified coordinates. 151 * @param x X coordinate 152 * @param y Y coordinate 153 * @see #moveAgain 154 */ 155 public void moveAgainTo(double x, double y) { 156 moveAgain(x - this.x, y - this.y); 157 } 158 159 /** 160 * Change the displacement vector to have endpoint {@code currentEN}. 161 * starting point is startEN 162 * @param currentEN the new endpoint 163 */ 164 public void applyVectorTo(EastNorth currentEN) { 165 if (startEN == null) 166 return; 167 x = currentEN.getX() - startEN.getX(); 168 y = currentEN.getY() - startEN.getY(); 169 updateCoordinates(); 170 } 171 172 /** 173 * Changes base point of movement 174 * @param newDraggedStartPoint - new starting point after movement (where user clicks to start new drag) 175 */ 176 public void changeStartPoint(EastNorth newDraggedStartPoint) { 177 startEN = new EastNorth(newDraggedStartPoint.getX()-x, newDraggedStartPoint.getY()-y); 178 } 179 180 /** 181 * Save curent displacement to restore in case of some problems 182 */ 183 public final void saveCheckpoint() { 184 backupX = x; 185 backupY = y; 186 } 187 188 /** 189 * Restore old displacement in case of some problems 190 */ 191 public void resetToCheckpoint() { 192 x = backupX; 193 y = backupY; 194 updateCoordinates(); 195 } 196 197 private void updateCoordinates() { 198 Iterator<OldNodeState> it = oldState.iterator(); 199 for (Node n : nodes) { 200 OldNodeState os = it.next(); 201 if (os.getEastNorth() != null) { 202 n.setEastNorth(os.getEastNorth().add(x, y)); 203 } 204 } 205 } 206 207 @Override 208 public boolean executeCommand() { 209 for (Node n : nodes) { 210 // in case #3892 happens again 211 if (n == null) 212 throw new AssertionError("null detected in node list"); 213 EastNorth en = n.getEastNorth(); 214 if (en != null) { 215 n.setEastNorth(en.add(x, y)); 216 n.setModified(true); 217 } 218 } 219 return true; 220 } 221 222 @Override 223 public void undoCommand() { 224 Iterator<OldNodeState> it = oldState.iterator(); 225 for (Node n : nodes) { 226 OldNodeState os = it.next(); 227 n.setCoor(os.getLatLon()); 228 n.setModified(os.isModified()); 229 } 230 } 231 232 @Override 233 public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) { 234 for (OsmPrimitive osm : nodes) { 235 modified.add(osm); 236 } 237 } 238 239 @Override 240 public String getDescriptionText() { 241 return trn("Move {0} node", "Move {0} nodes", nodes.size(), nodes.size()); 242 } 243 244 @Override 245 public Icon getDescriptionIcon() { 246 return ImageProvider.get("data", "node"); 247 } 248 249 @Override 250 public Collection<Node> getParticipatingPrimitives() { 251 return nodes; 252 } 253 254 /** 255 * Gets the offset. 256 * @return The current offset. 257 */ 258 protected EastNorth getOffset() { 259 return new EastNorth(x, y); 260 } 261 262 @Override 263 public int hashCode() { 264 return Objects.hash(super.hashCode(), nodes, startEN, x, y, backupX, backupY, oldState); 265 } 266 267 @Override 268 public boolean equals(Object obj) { 269 if (this == obj) return true; 270 if (obj == null || getClass() != obj.getClass()) return false; 271 if (!super.equals(obj)) return false; 272 MoveCommand that = (MoveCommand) obj; 273 return Double.compare(that.x, x) == 0 && 274 Double.compare(that.y, y) == 0 && 275 Double.compare(that.backupX, backupX) == 0 && 276 Double.compare(that.backupY, backupY) == 0 && 277 Objects.equals(nodes, that.nodes) && 278 Objects.equals(startEN, that.startEN) && 279 Objects.equals(oldState, that.oldState); 280 } 281}