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

Last change on this file since 18823 was 18494, checked in by taylor.smock, 2 years ago

Fix #22115: Extract methods from LatLon into ILatLon where they are generally applicable

This also removes calls to Node#getCoor where possible, which reduces
the number of memory allocations in SearchCompiler#match, and overall
allocations due to Node#getCoor

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