source: josm/trunk/src/org/openstreetmap/josm/actions/SplitWayAction.java@ 560

Last change on this file since 560 was 529, checked in by gebner, 16 years ago

Part one of patch by Dave Hansen <dave@…>

  • Remove unused imports
  • Main.debug
  • Make attribute merging aware of TIGER-import attributes
  • Better upload progress information
  • Retry uploads
File size: 8.8 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.actions;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.awt.event.ActionEvent;
8import java.awt.event.KeyEvent;
9import java.util.ArrayList;
10import java.util.Collection;
11import java.util.Collections;
12import java.util.Comparator;
13import java.util.HashMap;
14import java.util.HashSet;
15import java.util.Iterator;
16import java.util.LinkedList;
17import java.util.List;
18import java.util.Set;
19import java.util.Map.Entry;
20
21import javax.swing.JOptionPane;
22
23import org.openstreetmap.josm.Main;
24import org.openstreetmap.josm.command.AddCommand;
25import org.openstreetmap.josm.command.ChangeCommand;
26import org.openstreetmap.josm.command.Command;
27import org.openstreetmap.josm.command.SequenceCommand;
28import org.openstreetmap.josm.data.SelectionChangedListener;
29import org.openstreetmap.josm.data.osm.DataSet;
30import org.openstreetmap.josm.data.osm.Relation;
31import org.openstreetmap.josm.data.osm.Node;
32import org.openstreetmap.josm.data.osm.OsmPrimitive;
33import org.openstreetmap.josm.data.osm.Way;
34import org.openstreetmap.josm.data.osm.visitor.NameVisitor;
35import org.openstreetmap.josm.data.osm.visitor.Visitor;
36
37/**
38 * Splits a way into multiple ways (all identical except for their node list).
39 *
40 * Ways are just split at the selected nodes. The nodes remain in their
41 * original order. Selected nodes at the end of a way are ignored.
42 */
43
44public class SplitWayAction extends JosmAction implements SelectionChangedListener {
45
46 private Way selectedWay;
47 private List<Node> selectedNodes;
48
49 /**
50 * Create a new SplitWayAction.
51 */
52 public SplitWayAction() {
53 super(tr("Split Way"), "splitway", tr("Split a way at the selected node."), KeyEvent.VK_P, 0, true);
54 DataSet.selListeners.add(this);
55 }
56
57 /**
58 * Called when the action is executed.
59 *
60 * This method performs an expensive check whether the selection clearly defines one
61 * of the split actions outlined above, and if yes, calls the splitWay method.
62 */
63 public void actionPerformed(ActionEvent e) {
64
65 Collection<OsmPrimitive> selection = Main.ds.getSelected();
66
67 if (!checkSelection(selection)) {
68 JOptionPane.showMessageDialog(Main.parent, tr("The current selection cannot be used for splitting."));
69 return;
70 }
71
72 selectedWay = null;
73 selectedNodes = null;
74
75 Visitor splitVisitor = new Visitor(){
76 public void visit(Node n) {
77 if (selectedNodes == null)
78 selectedNodes = new LinkedList<Node>();
79 selectedNodes.add(n);
80 }
81 public void visit(Way w) {
82 selectedWay = w;
83 }
84 public void visit(Relation e) {
85 // enties are not considered
86 }
87 };
88
89 for (OsmPrimitive p : selection)
90 p.visit(splitVisitor);
91
92 // If only nodes are selected, try to guess which way to split. This works if there
93 // is exactly one way that all nodes are part of.
94 if (selectedWay == null && selectedNodes != null) {
95 HashMap<Way, Integer> wayOccurenceCounter = new HashMap<Way, Integer>();
96 for (Node n : selectedNodes) {
97 for (Way w : Main.ds.ways) {
98 if (w.deleted || w.incomplete) continue;
99 for (Node wn : w.nodes) {
100 if (n.equals(wn)) {
101 Integer old = wayOccurenceCounter.get(w);
102 wayOccurenceCounter.put(w, (old == null) ? 1 : old+1);
103 break;
104 }
105 }
106 }
107 }
108 if (wayOccurenceCounter.isEmpty()) {
109 JOptionPane.showMessageDialog(Main.parent,
110 trn("The selected node is not part of any way.",
111 "The selected nodes are not part of any way.", selectedNodes.size()));
112 return;
113 }
114
115 for (Entry<Way, Integer> entry : wayOccurenceCounter.entrySet()) {
116 if (entry.getValue().equals(selectedNodes.size())) {
117 if (selectedWay != null) {
118 JOptionPane.showMessageDialog(Main.parent, tr("There is more than one way using the node(s) you selected. Please select the way also."));
119 return;
120 }
121 selectedWay = entry.getKey();
122 }
123 }
124
125 if (selectedWay == null) {
126 JOptionPane.showMessageDialog(Main.parent, tr("The selected nodes do not share the same way."));
127 return;
128 }
129
130 // If a way and nodes are selected, verify that the nodes are part of the way.
131 } else if (selectedWay != null && selectedNodes != null) {
132
133 HashSet<Node> nds = new HashSet<Node>(selectedNodes);
134 for (Node n : selectedWay.nodes) {
135 nds.remove(n);
136 }
137 if (!nds.isEmpty()) {
138 JOptionPane.showMessageDialog(Main.parent,
139 trn("The selected way does not contain the selected node.",
140 "The selected way does not contain all the selected nodes.", selectedNodes.size()));
141 return;
142 }
143 }
144
145 // and then do the work.
146 splitWay();
147 }
148
149 /**
150 * Checks if the selection consists of something we can work with.
151 * Checks only if the number and type of items selected looks good;
152 * does not check whether the selected items are really a valid
153 * input for splitting (this would be too expensive to be carried
154 * out from the selectionChanged listener).
155 */
156 private boolean checkSelection(Collection<? extends OsmPrimitive> selection) {
157 boolean way = false;
158 boolean node = false;
159 for (OsmPrimitive p : selection) {
160 if (p instanceof Way && !way) {
161 way = true;
162 } else if (p instanceof Node) {
163 node = true;
164 } else {
165 return false;
166 }
167 }
168 return node;
169 }
170
171 /**
172 * Split a way into two or more parts, starting at a selected node.
173 *
174 * FIXME: what do the following "arguments" refer to?
175 * @param way the way to split
176 * @param nodes the node(s) to split the way at; must be part of the way.
177 */
178 private void splitWay() {
179 // We take our way's list of nodes and copy them to a way chunk (a
180 // list of nodes). Whenever we stumble upon a selected node, we start
181 // a new way chunk.
182
183 Set<Node> nodeSet = new HashSet<Node>(selectedNodes);
184 List<List<Node>> wayChunks = new LinkedList<List<Node>>();
185 List<Node> currentWayChunk = new ArrayList<Node>();
186 wayChunks.add(currentWayChunk);
187
188 Iterator<Node> it = selectedWay.nodes.iterator();
189 while (it.hasNext()) {
190 Node currentNode = it.next();
191 boolean atEndOfWay = currentWayChunk.isEmpty() || !it.hasNext();
192 currentWayChunk.add(currentNode);
193 if (nodeSet.contains(currentNode) && !atEndOfWay) {
194 currentWayChunk = new ArrayList<Node>();
195 currentWayChunk.add(currentNode);
196 wayChunks.add(currentWayChunk);
197 }
198 }
199
200 // Handle circular ways specially.
201 // If you split at a circular way at two nodes, you just want to split
202 // it at these points, not also at the former endpoint.
203 // So if the last node is the same first node, join the last and the
204 // first way chunk.
205 List<Node> lastWayChunk = wayChunks.get(wayChunks.size() - 1);
206 if (wayChunks.size() >= 2
207 && wayChunks.get(0).get(0) == lastWayChunk.get(lastWayChunk.size() - 1)
208 && !nodeSet.contains(wayChunks.get(0).get(0))) {
209 lastWayChunk.remove(lastWayChunk.size() - 1);
210 lastWayChunk.addAll(wayChunks.get(0));
211 wayChunks.remove(wayChunks.size() - 1);
212 wayChunks.set(0, lastWayChunk);
213 }
214
215 if (wayChunks.size() < 2) {
216 JOptionPane.showMessageDialog(Main.parent, tr("The way cannot be split at the selected nodes. (Hint: Select nodes in the middle of the way.)"));
217 return;
218 }
219 Main.debug("wayChunks.size(): " + wayChunks.size());
220 Main.debug("way id: " + selectedWay.id);
221
222 // build a list of commands, and also a new selection list
223 Collection<Command> commandList = new ArrayList<Command>(wayChunks.size());
224 Collection<Way> newSelection = new ArrayList<Way>(wayChunks.size());
225
226 Iterator<List<Node>> chunkIt = wayChunks.iterator();
227
228 // First, change the original way
229 Way changedWay = new Way(selectedWay);
230 changedWay.nodes.clear();
231 changedWay.nodes.addAll(chunkIt.next());
232 commandList.add(new ChangeCommand(selectedWay, changedWay));
233 newSelection.add(selectedWay);
234
235 // Second, create new ways
236 while (chunkIt.hasNext()) {
237 Way wayToAdd = new Way();
238 if (selectedWay.keys != null) {
239 wayToAdd.keys = new HashMap<String, String>(selectedWay.keys);
240 wayToAdd.checkTagged();
241 }
242 wayToAdd.nodes.addAll(chunkIt.next());
243 commandList.add(new AddCommand(wayToAdd));
244 Main.debug("wayToAdd: " + wayToAdd);
245 newSelection.add(wayToAdd);
246 }
247
248 NameVisitor v = new NameVisitor();
249 v.visit(selectedWay);
250 Main.main.undoRedo.add(
251 new SequenceCommand(tr("Split way {0} into {1} parts",
252 v.name, wayChunks.size()),
253 commandList));
254 Main.ds.setSelected(newSelection);
255 }
256
257 /**
258 * Enable the "split way" menu option if the selection looks like we could use it.
259 */
260 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
261 setEnabled(checkSelection(newSelection));
262 }
263}
Note: See TracBrowser for help on using the repository browser.