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