[6380] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
[626] | 2 | package org.openstreetmap.josm.command;
|
---|
| 3 |
|
---|
| 4 | import static org.openstreetmap.josm.tools.I18n.trn;
|
---|
| 5 |
|
---|
| 6 | import java.util.Collection;
|
---|
[307] | 7 | import java.util.Collections;
|
---|
[626] | 8 | import java.util.Iterator;
|
---|
| 9 | import java.util.LinkedList;
|
---|
| 10 | import java.util.List;
|
---|
[12726] | 11 | import java.util.NoSuchElementException;
|
---|
[9371] | 12 | import java.util.Objects;
|
---|
[6173] | 13 |
|
---|
[4918] | 14 | import javax.swing.Icon;
|
---|
[626] | 15 |
|
---|
[3266] | 16 | import org.openstreetmap.josm.data.coor.EastNorth;
|
---|
[626] | 17 | import org.openstreetmap.josm.data.coor.LatLon;
|
---|
[12759] | 18 | import org.openstreetmap.josm.data.osm.DataSet;
|
---|
[626] | 19 | import org.openstreetmap.josm.data.osm.Node;
|
---|
| 20 | import org.openstreetmap.josm.data.osm.OsmPrimitive;
|
---|
| 21 | import org.openstreetmap.josm.data.osm.visitor.AllNodesVisitor;
|
---|
[4126] | 22 | import org.openstreetmap.josm.data.projection.Projections;
|
---|
[626] | 23 | import org.openstreetmap.josm.tools.ImageProvider;
|
---|
| 24 |
|
---|
| 25 | /**
|
---|
| 26 | * MoveCommand moves a set of OsmPrimitives along the map. It can be moved again
|
---|
| 27 | * to collect several MoveCommands into one command.
|
---|
[1169] | 28 | *
|
---|
[626] | 29 | * @author imi
|
---|
| 30 | */
|
---|
| 31 | public class MoveCommand extends Command {
|
---|
[1169] | 32 | /**
|
---|
| 33 | * The objects that should be moved.
|
---|
| 34 | */
|
---|
[7005] | 35 | private Collection<Node> nodes = new LinkedList<>();
|
---|
[1169] | 36 | /**
|
---|
[6069] | 37 | * Starting position, base command point, current (mouse-drag) position = startEN + (x,y) =
|
---|
[5418] | 38 | */
|
---|
| 39 | private EastNorth startEN;
|
---|
| 40 |
|
---|
| 41 | /**
|
---|
[1169] | 42 | * x difference movement. Coordinates are in northern/eastern
|
---|
| 43 | */
|
---|
| 44 | private double x;
|
---|
| 45 | /**
|
---|
| 46 | * y difference movement. Coordinates are in northern/eastern
|
---|
| 47 | */
|
---|
| 48 | private double y;
|
---|
[626] | 49 |
|
---|
[5418] | 50 | private double backupX;
|
---|
| 51 | private double backupY;
|
---|
| 52 |
|
---|
[1169] | 53 | /**
|
---|
| 54 | * List of all old states of the objects.
|
---|
| 55 | */
|
---|
[9067] | 56 | private final List<OldNodeState> oldState = new LinkedList<>();
|
---|
[626] | 57 |
|
---|
[6881] | 58 | /**
|
---|
| 59 | * Constructs a new {@code MoveCommand} to move a primitive.
|
---|
| 60 | * @param osm The primitive to move
|
---|
| 61 | * @param x X difference movement. Coordinates are in northern/eastern
|
---|
| 62 | * @param y Y difference movement. Coordinates are in northern/eastern
|
---|
| 63 | */
|
---|
[1169] | 64 | public MoveCommand(OsmPrimitive osm, double x, double y) {
|
---|
| 65 | this(Collections.singleton(osm), x, y);
|
---|
| 66 | }
|
---|
[3266] | 67 |
|
---|
[6881] | 68 | /**
|
---|
| 69 | * Constructs a new {@code MoveCommand} to move a node.
|
---|
| 70 | * @param node The node to move
|
---|
[8447] | 71 | * @param position The new location (lat/lon)
|
---|
[6881] | 72 | */
|
---|
[3266] | 73 | public MoveCommand(Node node, LatLon position) {
|
---|
[8549] | 74 | this(Collections.singleton((OsmPrimitive) node), Projections.project(position).subtract(node.getEastNorth()));
|
---|
[3266] | 75 | }
|
---|
| 76 |
|
---|
[6881] | 77 | /**
|
---|
| 78 | * Constructs a new {@code MoveCommand} to move a collection of primitives.
|
---|
| 79 | * @param objects The primitives to move
|
---|
| 80 | * @param offset The movement vector
|
---|
| 81 | */
|
---|
[3266] | 82 | public MoveCommand(Collection<OsmPrimitive> objects, EastNorth offset) {
|
---|
| 83 | this(objects, offset.getX(), offset.getY());
|
---|
| 84 | }
|
---|
[6069] | 85 |
|
---|
[1169] | 86 | /**
|
---|
[6881] | 87 | * Constructs a new {@code MoveCommand} and assign the initial object set and movement vector.
|
---|
[12726] | 88 | * @param objects The primitives to move. Must neither be null nor empty. Objects must belong to a data set
|
---|
[6881] | 89 | * @param x X difference movement. Coordinates are in northern/eastern
|
---|
| 90 | * @param y Y difference movement. Coordinates are in northern/eastern
|
---|
[12726] | 91 | * @throws NullPointerException if objects is null or contain null item
|
---|
| 92 | * @throws NoSuchElementException if objects is empty
|
---|
[1169] | 93 | */
|
---|
| 94 | public MoveCommand(Collection<OsmPrimitive> objects, double x, double y) {
|
---|
[12759] | 95 | this(objects.iterator().next().getDataSet(), objects, x, y);
|
---|
| 96 | }
|
---|
| 97 |
|
---|
| 98 | /**
|
---|
| 99 | * Constructs a new {@code MoveCommand} and assign the initial object set and movement vector.
|
---|
| 100 | * @param ds the dataset context for moving these primitives. Must not be null.
|
---|
| 101 | * @param objects The primitives to move. Must neither be null.
|
---|
| 102 | * @param x X difference movement. Coordinates are in northern/eastern
|
---|
| 103 | * @param y Y difference movement. Coordinates are in northern/eastern
|
---|
| 104 | * @throws NullPointerException if objects is null or contain null item
|
---|
| 105 | * @throws NoSuchElementException if objects is empty
|
---|
| 106 | * @since 12759
|
---|
| 107 | */
|
---|
| 108 | public MoveCommand(DataSet ds, Collection<OsmPrimitive> objects, double x, double y) {
|
---|
| 109 | super(ds);
|
---|
[5418] | 110 | startEN = null;
|
---|
| 111 | saveCheckpoint(); // (0,0) displacement will be saved
|
---|
[1169] | 112 | this.x = x;
|
---|
| 113 | this.y = y;
|
---|
[11070] | 114 | Objects.requireNonNull(objects, "objects");
|
---|
[1750] | 115 | this.nodes = AllNodesVisitor.getAllNodes(objects);
|
---|
| 116 | for (Node n : this.nodes) {
|
---|
[6173] | 117 | oldState.add(new OldNodeState(n));
|
---|
[1169] | 118 | }
|
---|
| 119 | }
|
---|
[626] | 120 |
|
---|
[8447] | 121 | /**
|
---|
| 122 | * Constructs a new {@code MoveCommand} to move a collection of primitives.
|
---|
[12759] | 123 | * @param ds the dataset context for moving these primitives. Must not be null.
|
---|
[8447] | 124 | * @param objects The primitives to move
|
---|
| 125 | * @param start The starting position (northern/eastern)
|
---|
| 126 | * @param end The ending position (northern/eastern)
|
---|
[12759] | 127 | * @since 12759
|
---|
[8447] | 128 | */
|
---|
[12759] | 129 | public MoveCommand(DataSet ds, Collection<OsmPrimitive> objects, EastNorth start, EastNorth end) {
|
---|
| 130 | this(Objects.requireNonNull(ds, "ds"),
|
---|
| 131 | Objects.requireNonNull(objects, "objects"),
|
---|
| 132 | Objects.requireNonNull(end, "end").getX() - Objects.requireNonNull(start, "start").getX(),
|
---|
| 133 | Objects.requireNonNull(end, "end").getY() - Objects.requireNonNull(start, "start").getY());
|
---|
[10378] | 134 | startEN = start;
|
---|
[6173] | 135 | }
|
---|
[5418] | 136 |
|
---|
[8447] | 137 | /**
|
---|
[12759] | 138 | * Constructs a new {@code MoveCommand} to move a collection of primitives.
|
---|
| 139 | * @param objects The primitives to move
|
---|
| 140 | * @param start The starting position (northern/eastern)
|
---|
| 141 | * @param end The ending position (northern/eastern)
|
---|
| 142 | */
|
---|
| 143 | public MoveCommand(Collection<OsmPrimitive> objects, EastNorth start, EastNorth end) {
|
---|
| 144 | this(Objects.requireNonNull(objects, "objects").iterator().next().getDataSet(), objects, start, end);
|
---|
| 145 | }
|
---|
| 146 |
|
---|
| 147 | /**
|
---|
[8447] | 148 | * Constructs a new {@code MoveCommand} to move a primitive.
|
---|
[12759] | 149 | * @param ds the dataset context for moving these primitives. Must not be null.
|
---|
[8447] | 150 | * @param p The primitive to move
|
---|
| 151 | * @param start The starting position (northern/eastern)
|
---|
| 152 | * @param end The ending position (northern/eastern)
|
---|
[12759] | 153 | * @since 12759
|
---|
[8447] | 154 | */
|
---|
[12759] | 155 | public MoveCommand(DataSet ds, OsmPrimitive p, EastNorth start, EastNorth end) {
|
---|
| 156 | this(ds, Collections.singleton(Objects.requireNonNull(p, "p")), start, end);
|
---|
| 157 | }
|
---|
| 158 |
|
---|
| 159 | /**
|
---|
| 160 | * Constructs a new {@code MoveCommand} to move a primitive.
|
---|
| 161 | * @param p The primitive to move
|
---|
| 162 | * @param start The starting position (northern/eastern)
|
---|
| 163 | * @param end The ending position (northern/eastern)
|
---|
| 164 | */
|
---|
[6173] | 165 | public MoveCommand(OsmPrimitive p, EastNorth start, EastNorth end) {
|
---|
[12759] | 166 | this(Collections.singleton(Objects.requireNonNull(p, "p")), start, end);
|
---|
[6173] | 167 | }
|
---|
[6069] | 168 |
|
---|
[1169] | 169 | /**
|
---|
| 170 | * Move the same set of objects again by the specified vector. The vectors
|
---|
| 171 | * are added together and so the resulting will be moved to the previous
|
---|
| 172 | * vector plus this one.
|
---|
| 173 | *
|
---|
| 174 | * The move is immediately executed and any undo will undo both vectors to
|
---|
| 175 | * the original position the objects had before first moving.
|
---|
[6881] | 176 | *
|
---|
| 177 | * @param x X difference movement. Coordinates are in northern/eastern
|
---|
| 178 | * @param y Y difference movement. Coordinates are in northern/eastern
|
---|
[1169] | 179 | */
|
---|
| 180 | public void moveAgain(double x, double y) {
|
---|
[1750] | 181 | for (Node n : nodes) {
|
---|
[1640] | 182 | n.setEastNorth(n.getEastNorth().add(x, y));
|
---|
[1169] | 183 | }
|
---|
| 184 | this.x += x;
|
---|
| 185 | this.y += y;
|
---|
| 186 | }
|
---|
[626] | 187 |
|
---|
[9231] | 188 | /**
|
---|
| 189 | * Move again to the specified coordinates.
|
---|
| 190 | * @param x X coordinate
|
---|
| 191 | * @param y Y coordinate
|
---|
| 192 | * @see #moveAgain
|
---|
| 193 | */
|
---|
[3682] | 194 | public void moveAgainTo(double x, double y) {
|
---|
| 195 | moveAgain(x - this.x, y - this.y);
|
---|
| 196 | }
|
---|
[6069] | 197 |
|
---|
[5418] | 198 | /**
|
---|
[9231] | 199 | * Change the displacement vector to have endpoint {@code currentEN}.
|
---|
| 200 | * starting point is startEN
|
---|
| 201 | * @param currentEN the new endpoint
|
---|
[5418] | 202 | */
|
---|
| 203 | public void applyVectorTo(EastNorth currentEN) {
|
---|
[6069] | 204 | if (startEN == null)
|
---|
[5418] | 205 | return;
|
---|
| 206 | x = currentEN.getX() - startEN.getX();
|
---|
| 207 | y = currentEN.getY() - startEN.getY();
|
---|
| 208 | updateCoordinates();
|
---|
| 209 | }
|
---|
[3682] | 210 |
|
---|
[6173] | 211 | /**
|
---|
[5418] | 212 | * Changes base point of movement
|
---|
| 213 | * @param newDraggedStartPoint - new starting point after movement (where user clicks to start new drag)
|
---|
| 214 | */
|
---|
| 215 | public void changeStartPoint(EastNorth newDraggedStartPoint) {
|
---|
| 216 | startEN = new EastNorth(newDraggedStartPoint.getX()-x, newDraggedStartPoint.getY()-y);
|
---|
[6173] | 217 | }
|
---|
[5418] | 218 |
|
---|
| 219 | /**
|
---|
| 220 | * Save curent displacement to restore in case of some problems
|
---|
| 221 | */
|
---|
[6890] | 222 | public final void saveCheckpoint() {
|
---|
[5418] | 223 | backupX = x;
|
---|
| 224 | backupY = y;
|
---|
| 225 | }
|
---|
| 226 |
|
---|
| 227 | /**
|
---|
| 228 | * Restore old displacement in case of some problems
|
---|
| 229 | */
|
---|
| 230 | public void resetToCheckpoint() {
|
---|
| 231 | x = backupX;
|
---|
| 232 | y = backupY;
|
---|
| 233 | updateCoordinates();
|
---|
| 234 | }
|
---|
| 235 |
|
---|
| 236 | private void updateCoordinates() {
|
---|
[6173] | 237 | Iterator<OldNodeState> it = oldState.iterator();
|
---|
[5418] | 238 | for (Node n : nodes) {
|
---|
[6173] | 239 | OldNodeState os = it.next();
|
---|
[8285] | 240 | if (os.getEastNorth() != null) {
|
---|
| 241 | n.setEastNorth(os.getEastNorth().add(x, y));
|
---|
[6215] | 242 | }
|
---|
[5418] | 243 | }
|
---|
| 244 | }
|
---|
| 245 |
|
---|
[6881] | 246 | @Override
|
---|
| 247 | public boolean executeCommand() {
|
---|
[12348] | 248 | ensurePrimitivesAreInDataset();
|
---|
| 249 |
|
---|
[1750] | 250 | for (Node n : nodes) {
|
---|
[2435] | 251 | // in case #3892 happens again
|
---|
[3173] | 252 | if (n == null)
|
---|
[3172] | 253 | throw new AssertionError("null detected in node list");
|
---|
[6215] | 254 | EastNorth en = n.getEastNorth();
|
---|
| 255 | if (en != null) {
|
---|
| 256 | n.setEastNorth(en.add(x, y));
|
---|
| 257 | n.setModified(true);
|
---|
| 258 | }
|
---|
[1169] | 259 | }
|
---|
| 260 | return true;
|
---|
[626] | 261 | }
|
---|
[1169] | 262 |
|
---|
[6881] | 263 | @Override
|
---|
| 264 | public void undoCommand() {
|
---|
[12348] | 265 | ensurePrimitivesAreInDataset();
|
---|
| 266 |
|
---|
[6173] | 267 | Iterator<OldNodeState> it = oldState.iterator();
|
---|
[1750] | 268 | for (Node n : nodes) {
|
---|
[6173] | 269 | OldNodeState os = it.next();
|
---|
[10248] | 270 | n.setCoor(os.getLatLon());
|
---|
[8285] | 271 | n.setModified(os.isModified());
|
---|
[1169] | 272 | }
|
---|
| 273 | }
|
---|
| 274 |
|
---|
[6881] | 275 | @Override
|
---|
| 276 | public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
|
---|
[1750] | 277 | for (OsmPrimitive osm : nodes) {
|
---|
[1169] | 278 | modified.add(osm);
|
---|
[1750] | 279 | }
|
---|
[1169] | 280 | }
|
---|
| 281 |
|
---|
[4918] | 282 | @Override
|
---|
| 283 | public String getDescriptionText() {
|
---|
| 284 | return trn("Move {0} node", "Move {0} nodes", nodes.size(), nodes.size());
|
---|
[1169] | 285 | }
|
---|
[1750] | 286 |
|
---|
[3262] | 287 | @Override
|
---|
[4918] | 288 | public Icon getDescriptionIcon() {
|
---|
| 289 | return ImageProvider.get("data", "node");
|
---|
| 290 | }
|
---|
| 291 |
|
---|
| 292 | @Override
|
---|
[3262] | 293 | public Collection<Node> getParticipatingPrimitives() {
|
---|
| 294 | return nodes;
|
---|
| 295 | }
|
---|
[8447] | 296 |
|
---|
[10663] | 297 | /**
|
---|
| 298 | * Gets the offset.
|
---|
| 299 | * @return The current offset.
|
---|
| 300 | */
|
---|
| 301 | protected EastNorth getOffset() {
|
---|
| 302 | return new EastNorth(x, y);
|
---|
| 303 | }
|
---|
| 304 |
|
---|
[8447] | 305 | @Override
|
---|
| 306 | public int hashCode() {
|
---|
[9371] | 307 | return Objects.hash(super.hashCode(), nodes, startEN, x, y, backupX, backupY, oldState);
|
---|
[8447] | 308 | }
|
---|
| 309 |
|
---|
| 310 | @Override
|
---|
| 311 | public boolean equals(Object obj) {
|
---|
[9371] | 312 | if (this == obj) return true;
|
---|
| 313 | if (obj == null || getClass() != obj.getClass()) return false;
|
---|
| 314 | if (!super.equals(obj)) return false;
|
---|
| 315 | MoveCommand that = (MoveCommand) obj;
|
---|
| 316 | return Double.compare(that.x, x) == 0 &&
|
---|
| 317 | Double.compare(that.y, y) == 0 &&
|
---|
| 318 | Double.compare(that.backupX, backupX) == 0 &&
|
---|
| 319 | Double.compare(that.backupY, backupY) == 0 &&
|
---|
| 320 | Objects.equals(nodes, that.nodes) &&
|
---|
| 321 | Objects.equals(startEN, that.startEN) &&
|
---|
| 322 | Objects.equals(oldState, that.oldState);
|
---|
[8447] | 323 | }
|
---|
[626] | 324 | }
|
---|