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

Last change on this file since 655 was 655, checked in by ramack, 16 years ago

patch by bruce89, closes #812; thanks bruce

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