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

Last change on this file since 1180 was 1169, checked in by stoecker, 15 years ago

removed usage of tab stops

  • Property svn:eol-style set to native
File size: 12.0 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.RelationMember;
32import org.openstreetmap.josm.data.osm.Way;
33import org.openstreetmap.josm.data.osm.visitor.NameVisitor;
34import org.openstreetmap.josm.data.osm.visitor.Visitor;
35import org.openstreetmap.josm.tools.Shortcut;
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."),
54 Shortcut.registerShortcut("tools:splitway", tr("Tool: {0}", tr("Split Way")), KeyEvent.VK_P, Shortcut.GROUP_EDIT), true);
55 DataSet.selListeners.add(this);
56 }
57
58 /**
59 * Called when the action is executed.
60 *
61 * This method performs an expensive check whether the selection clearly defines one
62 * of the split actions outlined above, and if yes, calls the splitWay method.
63 */
64 public void actionPerformed(ActionEvent e) {
65
66 Collection<OsmPrimitive> selection = Main.ds.getSelected();
67
68 if (!checkSelection(selection)) {
69 JOptionPane.showMessageDialog(Main.parent, tr("The current selection cannot be used for splitting."));
70 return;
71 }
72
73 selectedWay = null;
74 selectedNodes = null;
75
76 Visitor splitVisitor = new Visitor(){
77 public void visit(Node n) {
78 if (selectedNodes == null)
79 selectedNodes = new LinkedList<Node>();
80 selectedNodes.add(n);
81 }
82 public void visit(Way w) {
83 selectedWay = w;
84 }
85 public void visit(Relation e) {
86 // enties are not considered
87 }
88 };
89
90 for (OsmPrimitive p : selection)
91 p.visit(splitVisitor);
92
93 // If only nodes are selected, try to guess which way to split. This works if there
94 // is exactly one way that all nodes are part of.
95 if (selectedWay == null && selectedNodes != null) {
96 HashMap<Way, Integer> wayOccurenceCounter = new HashMap<Way, Integer>();
97 for (Node n : selectedNodes) {
98 for (Way w : Main.ds.ways) {
99 if (w.deleted || w.incomplete) continue;
100 int last = w.nodes.size()-1;
101 if(last <= 0) continue; // zero or one node ways
102 Boolean circular = w.nodes.get(0).equals(w.nodes.get(last));
103 int i = 0;
104 for (Node wn : w.nodes) {
105 if ((circular || (i > 0 && i < last)) && n.equals(wn)) {
106 Integer old = wayOccurenceCounter.get(w);
107 wayOccurenceCounter.put(w, (old == null) ? 1 : old+1);
108 break;
109 }
110 i++;
111 }
112 }
113 }
114 if (wayOccurenceCounter.isEmpty()) {
115 JOptionPane.showMessageDialog(Main.parent,
116 trn("The selected node is no inner part of any way.",
117 "The selected nodes are no inner part of any way.", selectedNodes.size()));
118 return;
119 }
120
121 for (Entry<Way, Integer> entry : wayOccurenceCounter.entrySet()) {
122 if (entry.getValue().equals(selectedNodes.size())) {
123 if (selectedWay != null) {
124 JOptionPane.showMessageDialog(Main.parent, tr("There is more than one way using the node(s) you selected. Please select the way also."));
125 return;
126 }
127 selectedWay = entry.getKey();
128 }
129 }
130
131 if (selectedWay == null) {
132 JOptionPane.showMessageDialog(Main.parent, tr("The selected nodes do not share the same way."));
133 return;
134 }
135
136 // If a way and nodes are selected, verify that the nodes are part of the way.
137 } else if (selectedWay != null && selectedNodes != null) {
138
139 HashSet<Node> nds = new HashSet<Node>(selectedNodes);
140 for (Node n : selectedWay.nodes) {
141 nds.remove(n);
142 }
143 if (!nds.isEmpty()) {
144 JOptionPane.showMessageDialog(Main.parent,
145 trn("The selected way does not contain the selected node.",
146 "The selected way does not contain all the selected nodes.", selectedNodes.size()));
147 return;
148 }
149 }
150
151 // and then do the work.
152 splitWay();
153 }
154
155 /**
156 * Checks if the selection consists of something we can work with.
157 * Checks only if the number and type of items selected looks good;
158 * does not check whether the selected items are really a valid
159 * input for splitting (this would be too expensive to be carried
160 * out from the selectionChanged listener).
161 */
162 private boolean checkSelection(Collection<? extends OsmPrimitive> selection) {
163 boolean way = false;
164 boolean node = false;
165 for (OsmPrimitive p : selection) {
166 if (p instanceof Way && !way) {
167 way = true;
168 } else if (p instanceof Node) {
169 node = true;
170 } else {
171 return false;
172 }
173 }
174 return node;
175 }
176
177 /**
178 * Split a way into two or more parts, starting at a selected node.
179 */
180 private void splitWay() {
181 // We take our way's list of nodes and copy them to a way chunk (a
182 // list of nodes). Whenever we stumble upon a selected node, we start
183 // a new way chunk.
184
185 Set<Node> nodeSet = new HashSet<Node>(selectedNodes);
186 List<List<Node>> wayChunks = new LinkedList<List<Node>>();
187 List<Node> currentWayChunk = new ArrayList<Node>();
188 wayChunks.add(currentWayChunk);
189
190 Iterator<Node> it = selectedWay.nodes.iterator();
191 while (it.hasNext()) {
192 Node currentNode = it.next();
193 boolean atEndOfWay = currentWayChunk.isEmpty() || !it.hasNext();
194 currentWayChunk.add(currentNode);
195 if (nodeSet.contains(currentNode) && !atEndOfWay) {
196 currentWayChunk = new ArrayList<Node>();
197 currentWayChunk.add(currentNode);
198 wayChunks.add(currentWayChunk);
199 }
200 }
201
202 // Handle circular ways specially.
203 // If you split at a circular way at two nodes, you just want to split
204 // it at these points, not also at the former endpoint.
205 // So if the last node is the same first node, join the last and the
206 // first way chunk.
207 List<Node> lastWayChunk = wayChunks.get(wayChunks.size() - 1);
208 if (wayChunks.size() >= 2
209 && wayChunks.get(0).get(0) == lastWayChunk.get(lastWayChunk.size() - 1)
210 && !nodeSet.contains(wayChunks.get(0).get(0))) {
211 if (wayChunks.size() == 2) {
212 JOptionPane.showMessageDialog(Main.parent, tr("You must select two or more nodes to split a circular way."));
213 return;
214 }
215 lastWayChunk.remove(lastWayChunk.size() - 1);
216 lastWayChunk.addAll(wayChunks.get(0));
217 wayChunks.remove(wayChunks.size() - 1);
218 wayChunks.set(0, lastWayChunk);
219 }
220
221 if (wayChunks.size() < 2) {
222 if(wayChunks.get(0).get(0) == wayChunks.get(0).get(wayChunks.get(0).size()-1))
223 JOptionPane.showMessageDialog(Main.parent, tr("You must select two or more nodes to split a circular way."));
224 else
225 JOptionPane.showMessageDialog(Main.parent, tr("The way cannot be split at the selected nodes. (Hint: Select nodes in the middle of the way.)"));
226 return;
227 }
228 //Main.debug("wayChunks.size(): " + wayChunks.size());
229 //Main.debug("way id: " + selectedWay.id);
230
231 // build a list of commands, and also a new selection list
232 Collection<Command> commandList = new ArrayList<Command>(wayChunks.size());
233 Collection<Way> newSelection = new ArrayList<Way>(wayChunks.size());
234
235 Iterator<List<Node>> chunkIt = wayChunks.iterator();
236
237 // First, change the original way
238 Way changedWay = new Way(selectedWay);
239 changedWay.nodes.clear();
240 changedWay.nodes.addAll(chunkIt.next());
241 commandList.add(new ChangeCommand(selectedWay, changedWay));
242 newSelection.add(selectedWay);
243
244 Collection<Way> newWays = new ArrayList<Way>();
245 // Second, create new ways
246 while (chunkIt.hasNext()) {
247 Way wayToAdd = new Way();
248 if (selectedWay.keys != null) {
249 wayToAdd.keys = new HashMap<String, String>(selectedWay.keys);
250 wayToAdd.checkTagged();
251 wayToAdd.checkDirectionTagged();
252 }
253 newWays.add(wayToAdd);
254 wayToAdd.nodes.addAll(chunkIt.next());
255 commandList.add(new AddCommand(wayToAdd));
256 //Main.debug("wayToAdd: " + wayToAdd);
257 newSelection.add(wayToAdd);
258
259 }
260 Boolean warnme=false;
261 // now copy all relations to new way also
262 for (Relation r : Main.ds.relations) {
263 if (r.deleted || r.incomplete) continue;
264 for (RelationMember rm : r.members) {
265 if (rm.member instanceof Way) {
266 if (rm.member == selectedWay)
267 {
268 Relation c = new Relation(r);
269 for(Way wayToAdd : newWays)
270 {
271 RelationMember em = new RelationMember();
272 em.member = wayToAdd;
273 em.role = rm.role;
274 if(em.role.length() > 0)
275 warnme = true;
276 c.members.add(em);
277 }
278 commandList.add(new ChangeCommand(r, c));
279 break;
280 }
281 }
282 }
283 }
284 if(warnme)
285 JOptionPane.showMessageDialog(Main.parent, tr("A role based relation membership was copied to all new ways.\nYou should verify this and correct it when necessary."));
286
287 NameVisitor v = new NameVisitor();
288 v.visit(selectedWay);
289 Main.main.undoRedo.add(
290 new SequenceCommand(tr("Split way {0} into {1} parts",
291 v.name, wayChunks.size()),
292 commandList));
293 Main.ds.setSelected(newSelection);
294 }
295
296 /**
297 * Enable the "split way" menu option if the selection looks like we could use it.
298 */
299 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
300 setEnabled(checkSelection(newSelection));
301 }
302}
Note: See TracBrowser for help on using the repository browser.