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

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