source: josm/trunk/src/org/openstreetmap/josm/command/MoveCommand.java@ 17335

Last change on this file since 17335 was 17093, checked in by simon04, 4 years ago

Use Collection.addAll

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