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.HashMap; 008import java.util.Map; 009import java.util.NoSuchElementException; 010import java.util.Objects; 011 012import javax.swing.Icon; 013 014import org.openstreetmap.josm.data.coor.EastNorth; 015import org.openstreetmap.josm.data.osm.Node; 016import org.openstreetmap.josm.data.osm.OsmPrimitive; 017import org.openstreetmap.josm.data.osm.visitor.AllNodesVisitor; 018import org.openstreetmap.josm.tools.ImageProvider; 019 020/** 021 * Abstract class with common services for nodes rotation and scaling commands. 022 * 023 * @author Olivier Croquette <ocroquette@free.fr> 024 */ 025public abstract class TransformNodesCommand extends Command { 026 027 /** 028 * The nodes to transform. 029 */ 030 protected final Collection<Node> nodes; 031 032 /** 033 * List of all old states of the nodes. 034 */ 035 protected final Map<Node, OldNodeState> oldStates = new HashMap<>(); 036 037 /** 038 * Stores the state of the nodes before the command. 039 */ 040 protected final void storeOldState() { 041 for (Node n : this.nodes) { 042 oldStates.put(n, new OldNodeState(n)); 043 } 044 } 045 046 /** 047 * Creates a TransformNodesObject. 048 * Find out the impacted nodes and store their initial state. 049 * @param objects objects to fetch nodes from. Must neither be null nor empty. Items must belong to a data set 050 * @throws NullPointerException if objects is null or contain null item 051 * @throws NoSuchElementException if objects is empty 052 */ 053 public TransformNodesCommand(Collection<? extends OsmPrimitive> objects) { 054 super(objects.iterator().next().getDataSet()); 055 this.nodes = AllNodesVisitor.getAllNodes(objects); 056 storeOldState(); 057 } 058 059 /** 060 * Handling of a mouse event (e.g. dragging event). 061 * @param currentEN the current world position of the mouse 062 */ 063 public abstract void handleEvent(EastNorth currentEN); 064 065 /** 066 * Implementation for the nodes transformation. 067 * No parameters are given here, you should handle the user input in handleEvent() 068 * and store it internally. 069 */ 070 protected abstract void transformNodes(); 071 072 /** 073 * Finally apply the transformation of the nodes. 074 * This is called when the user is happy with the current state of the command 075 * and its effects. 076 */ 077 @Override 078 public boolean executeCommand() { 079 transformNodes(); 080 flagNodesAsModified(); 081 return true; 082 } 083 084 /** 085 * Flag all nodes as modified. 086 */ 087 public void flagNodesAsModified() { 088 for (Node n : nodes) { 089 n.setModified(true); 090 } 091 } 092 093 /** 094 * Restore the state of the nodes from the backup. 095 */ 096 @Override 097 public void undoCommand() { 098 for (Node n : nodes) { 099 OldNodeState os = oldStates.get(n); 100 n.setCoor(os.getLatLon()); 101 n.setModified(os.isModified()); 102 } 103 } 104 105 @Override 106 public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) { 107 } 108 109 @Override 110 public Collection<? extends OsmPrimitive> getParticipatingPrimitives() { 111 return nodes; 112 } 113 114 @Override 115 public String getDescriptionText() { 116 return trn("Transform {0} node", "Transform {0} nodes", nodes.size(), nodes.size()); 117 } 118 119 @Override 120 public Icon getDescriptionIcon() { 121 return ImageProvider.get("data", "node"); 122 } 123 124 /** 125 * Get the nodes with the current transformation applied. 126 * @return nodes with the current transformation applied 127 */ 128 public Collection<Node> getTransformedNodes() { 129 return nodes; 130 } 131 132 /** 133 * Get the center of the nodes under modification. 134 * It's just the barycenter. 135 * @return center east/north of the nodes under modification 136 * @see org.openstreetmap.josm.tools.Geometry#getCentroid(java.util.List) 137 */ 138 public EastNorth getNodesCenter() { 139 EastNorth sum = new EastNorth(0, 0); 140 141 for (Node n : nodes) { 142 EastNorth en = n.getEastNorth(); 143 sum = sum.add(en.east(), en.north()); 144 } 145 return new EastNorth(sum.east()/this.nodes.size(), sum.north()/this.nodes.size()); 146 147 } 148 149 @Override 150 public int hashCode() { 151 return Objects.hash(super.hashCode(), nodes, oldStates); 152 } 153 154 @Override 155 public boolean equals(Object obj) { 156 if (this == obj) return true; 157 if (obj == null || getClass() != obj.getClass()) return false; 158 if (!super.equals(obj)) return false; 159 TransformNodesCommand that = (TransformNodesCommand) obj; 160 return Objects.equals(nodes, that.nodes) && 161 Objects.equals(oldStates, that.oldStates); 162 } 163}