Changeset 343 in josm for trunk/src/org/openstreetmap/josm/actions/SplitWayAction.java
- Timestamp:
- 2007-10-07T13:20:27+02:00 (17 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/actions/SplitWayAction.java
r301 r343 16 16 import java.util.LinkedList; 17 17 import java.util.List; 18 import java.util.Set; 18 19 import java.util.Map.Entry; 19 20 … … 27 28 import org.openstreetmap.josm.data.SelectionChangedListener; 28 29 import org.openstreetmap.josm.data.osm.DataSet; 30 import org.openstreetmap.josm.data.osm.Relation; 29 31 import org.openstreetmap.josm.data.osm.Node; 30 32 import org.openstreetmap.josm.data.osm.OsmPrimitive; 31 import org.openstreetmap.josm.data.osm.Segment;32 33 import org.openstreetmap.josm.data.osm.Way; 33 34 import org.openstreetmap.josm.data.osm.visitor.NameVisitor; … … 35 36 36 37 /** 37 * Splits a way into multiple ways (all identical except the segments 38 * belonging to the way). 38 * Splits a way into multiple ways (all identical except for their node list). 39 39 * 40 * Various split modes are used depending on what is selected. 41 * 42 * 1. One or more NODES (and, optionally, also one way) selected: 43 * 44 * (All nodes must be part of the same way. If a way is also selected, that way 45 * must contain all selected nodes.) 46 * 47 * Way is split AT the node(s) into contiguous ways. If the original contained 48 * one or more parts that were not reachable from any of the nodes, they form an 49 * extra new way. Examples (numbers are unselected nodes, letters are selected 50 * nodes) 51 * 52 * 1---A---2 becomes 1---A and A---2 53 * 54 * 1---A---2---B---3 becomes 1---A and A---2---B and B---3 55 * 56 * 2 57 * | 58 * 1---A---3 becomes 1---A and 2---A and A---3 59 * 60 * 1---A---2 3---4 becomes 1---A and A---2 and 3---4 61 * 62 * If the selected node(s) do not clearly define the way that is to be split, 63 * then the way must be selected for disambiguation (e.g. you have two ways, 64 * 1---2---3 and 4---2---5, and select node 2, then you must also select the 65 * way you want to split). 66 * 67 * This function will result in at least two ways, unless the selected node is 68 * at the end of the way AND the way is contiguous, which will lead to an error 69 * message. 70 * 71 * After executing the operation, the selection will be cleared. 72 * 73 * 2. One or more SEGMENTS (and, optionally, also one way) selected: 74 * 75 * (All segments must be part of the same way) 76 * 77 * The way is split in a fashion that makes a new way from the selected segments, 78 * i.e. the selected segments are removed from the way to form a new one. 79 * 80 * This function will result in exactly two ways. 81 * 82 * If splitting the segments out of the way makes a non-contiguous part from 83 * something that was contiguous before, the action is aborted and an error 84 * message is shown. 85 * 86 * 3. Exactly one WAY selected 87 * 88 * If the way is contiguous, you will get an error message. If the way is not 89 * contiguous it is split it into 2...n contiguous ways. 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. 90 42 */ 91 43 … … 94 46 private Way selectedWay; 95 47 private List<Node> selectedNodes; 96 private List<Segment> selectedSegments;97 48 98 49 /** … … 101 52 public SplitWayAction() { 102 53 super(tr("Split Way"), "splitway", tr("Split a way at the selected node."), KeyEvent.VK_P, KeyEvent.CTRL_MASK | KeyEvent.SHIFT_MASK, true); 103 DataSet. listeners.add(this);54 DataSet.selListeners.add(this); 104 55 } 105 56 … … 121 72 selectedWay = null; 122 73 selectedNodes = null; 123 selectedSegments = null;124 74 125 75 Visitor splitVisitor = new Visitor(){ … … 129 79 selectedNodes.add(n); 130 80 } 131 public void visit(Segment s) {132 if (selectedSegments == null)133 selectedSegments = new LinkedList<Segment>();134 selectedSegments.add(s);135 }136 81 public void visit(Way w) { 137 82 selectedWay = w; 138 83 } 84 public void visit(Relation e) { 85 // enties are not considered 86 } 139 87 }; 140 88 … … 148 96 for (Node n : selectedNodes) { 149 97 for (Way w : Main.ds.ways) { 150 for ( Segment s : w.segments) {151 if (n.equals( s.from) || n.equals(s.to)) {98 for (Node wn : w.nodes) { 99 if (n.equals(wn)) { 152 100 Integer old = wayOccurenceCounter.get(w); 153 101 wayOccurenceCounter.put(w, (old == null) ? 1 : old+1); … … 183 131 184 132 HashSet<Node> nds = new HashSet<Node>(selectedNodes); 185 for (Segment s : selectedWay.segments) { 186 nds.remove(s.from); 187 nds.remove(s.to); 133 for (Node n : selectedWay.nodes) { 134 nds.remove(n); 188 135 } 189 136 if (!nds.isEmpty()) { … … 193 140 return; 194 141 } 195 196 // If only segments are selected, guess which way to use.197 } else if (selectedWay == null && selectedSegments != null) {198 199 HashMap<Way, Integer> wayOccurenceCounter = new HashMap<Way, Integer>();200 for (Segment s : selectedSegments) {201 for (Way w : Main.ds.ways) {202 if (w.segments.contains(s)) {203 Integer old = wayOccurenceCounter.get(w);204 wayOccurenceCounter.put(w, (old == null) ? 1 : old+1);205 break;206 }207 }208 }209 if (wayOccurenceCounter.isEmpty()) {210 JOptionPane.showMessageDialog(Main.parent,211 trn("The selected segment is not part of any way.",212 "The selected segments are not part of any way.", selectedSegments.size()));213 return;214 }215 216 for (Entry<Way, Integer> entry : wayOccurenceCounter.entrySet()) {217 if (entry.getValue().equals(selectedSegments.size())) {218 if (selectedWay != null) {219 JOptionPane.showMessageDialog(Main.parent,220 trn("There is more than one way using the segment you selected. Please select the way also.",221 "There is more than one way using the segments you selected. Please select the way also.", selectedSegments.size()));222 return;223 }224 selectedWay = entry.getKey();225 }226 }227 228 if (selectedWay == null) {229 JOptionPane.showMessageDialog(Main.parent, tr("The selected segments do not share the same way."));230 return;231 }232 233 // If a way and segments are selected, verify that the segments are part of the way.234 } else if (selectedWay != null && selectedSegments != null) {235 236 if (!selectedWay.segments.containsAll(selectedSegments)) {237 JOptionPane.showMessageDialog(Main.parent,238 trn("The selected way does not contain the selected segment.",239 "The selected way does not contain all the selected segments.", selectedSegments.size()));240 return;241 }242 }243 244 // finally check if the selected way is complete.245 if (selectedWay.isIncomplete()) {246 JOptionPane.showMessageDialog(Main.parent, tr("Warning: This way is incomplete. Try to download it before splitting."));247 return;248 142 } 249 143 … … 261 155 private boolean checkSelection(Collection<? extends OsmPrimitive> selection) { 262 156 boolean way = false; 263 boolean segment = false;264 157 boolean node = false; 265 158 for (OsmPrimitive p : selection) { 266 if (p instanceof Way && !way) 159 if (p instanceof Way && !way) { 267 160 way = true; 268 else if (p instanceof Node && !segment)161 } else if (p instanceof Node) { 269 162 node = true; 270 else if (p instanceof Segment && !node) 271 segment = true; 272 else 163 } else { 273 164 return false; 274 165 } 275 return way || segment || node; 166 } 167 return node; 276 168 } 277 169 … … 279 171 * Split a way into two or more parts, starting at a selected node. 280 172 * 173 * FIXME: what do the following "arguments" refer to? 281 174 * @param way the way to split 282 175 * @param nodes the node(s) to split the way at; must be part of the way. 283 176 */ 284 177 private void splitWay() { 285 286 // The basic idea is to first divide all segments forming this way into 287 // groups, and later form new ways according to the groups. Initally, 288 // all segments are copied into allSegments, and then gradually removed 289 // from there as new groups are built. 290 291 LinkedList<Segment> allSegments = new LinkedList<Segment>(); 292 allSegments.addAll(selectedWay.segments); 293 List<List<Segment>> segmentSets = new ArrayList<List<Segment>>(); 294 295 if (selectedNodes != null) { 296 297 // This is the "split at node" mode. 298 299 boolean split = true; 300 Segment splitSeg = null; 301 while (split) { 302 split = false; 303 304 // Look for a "split segment". A split segment is any segment 305 // that touches one of the split nodes and has not yet been 306 // assigned to one of the segment groups. 307 for (Segment s : allSegments) { 308 for (Node node : selectedNodes) { 309 if (s.from.equals(node) || s.to.equals(node)) { 310 split = true; 311 splitSeg = s; 312 break; 313 } 314 } 315 if (split) 316 break; 178 // We take our way's list of nodes and copy them to a way chunk (a 179 // list of nodes). Whenever we stumble upon a selected node, we start 180 // a new way chunk. 181 182 Set<Node> nodeSet = new HashSet<Node>(selectedNodes); 183 List<List<Node>> wayChunks = new LinkedList<List<Node>>(); 184 List<Node> currentWayChunk = new ArrayList<Node>(); 185 wayChunks.add(currentWayChunk); 186 187 Iterator<Node> it = selectedWay.nodes.iterator(); 188 while (it.hasNext()) { 189 Node currentNode = it.next(); 190 boolean atEndOfWay = currentWayChunk.isEmpty() || !it.hasNext(); 191 currentWayChunk.add(currentNode); 192 if (nodeSet.contains(currentNode) && !atEndOfWay) { 193 currentWayChunk = new ArrayList<Node>(); 194 currentWayChunk.add(currentNode); 195 wayChunks.add(currentWayChunk); 317 196 } 318 319 // If a split segment was found, move this segment and all segments 320 // connected to it into a new segment group, stopping only if we 321 // reach another split node. Segment moving is done recursively by 322 // the moveSegments method. 323 if (split) { 324 LinkedList<Segment> subSegments = new LinkedList<Segment>(); 325 moveSegments(allSegments, subSegments, splitSeg, selectedNodes); 326 segmentSets.add(subSegments); 327 } 328 329 // The loop continues until no more split segments were found. 330 // Nb. not all segments touching a split node are split segments; 331 // e.g. 332 // 333 // 2 4 334 // | | 335 // 1---A---3---C---5 336 // 337 // This way will be split into 5 ways (1---A,2---A,A---3---C,4---C, 338 // C---5). Depending on which is processed first, either A---3 becomes 339 // a split segment and 3---C is moved as a connecting segment, or vice 340 // versa. The result is, of course, the same but this explains why we 341 // cannot simply start a new way for each segment connecting to a split 342 // node. 343 } 344 345 } else if (selectedSegments != null) { 346 347 // This is the "split segments" mode. It is quite easy as the segments to 348 // remove are already explicitly selected, but some restrictions have to 349 // be observed to make sure that no non-contiguous parts are created. 350 351 // first create a "scratch" copy of the full segment list and move all 352 // segments connected to the first selected segment into a temporary list. 353 LinkedList<Segment> copyOfAllSegments = new LinkedList<Segment>(allSegments); 354 LinkedList<Segment> partThatContainsSegments = new LinkedList<Segment>(); 355 moveSegments(copyOfAllSegments, partThatContainsSegments, selectedSegments.get(0), null); 356 357 // this list must now contain ALL selected segments; otherwise, segments 358 // from unconnected parts of the way have been selected and this is not allowed 359 // as it would create a new non-contiguous way. 360 if (!partThatContainsSegments.containsAll(selectedSegments)) { 361 JOptionPane.showMessageDialog(Main.parent, tr("The selected segments are not in the same contiguous part of the way.")); 362 return; 363 } 364 365 // if the contiguous part that contains the segments becomes non-contiguous 366 // after the removal of the segments, that is also an error. 367 partThatContainsSegments.removeAll(selectedSegments); 368 if (!partThatContainsSegments.isEmpty()) { 369 LinkedList<Segment> contiguousSubpart = new LinkedList<Segment>(); 370 moveSegments(partThatContainsSegments, contiguousSubpart, partThatContainsSegments.get(0), null); 371 // if partThatContainsSegments was contiguous before, it will now be empty as all segments 372 // connected to the first segment therein have been moved 373 if (!partThatContainsSegments.isEmpty()) { 374 JOptionPane.showMessageDialog(Main.parent, tr("Removing the selected segments would make a part of the way non-contiguous.")); 375 return; 376 } 377 } 378 379 ArrayList<Segment> subSegments = new ArrayList<Segment>(); 380 subSegments.addAll(selectedSegments); 381 allSegments.removeAll(selectedSegments); 382 segmentSets.add(subSegments); 383 384 } else { 385 386 // This is the "split way into contiguous parts" mode. 387 // We use a similar mechanism to splitting at nodes, but we do not 388 // select split segments. Instead, we randomly grab a segment out 389 // of the way and move all connecting segments to a new group. If 390 // segments remain in the original way, we repeat the procedure. 391 392 while (!allSegments.isEmpty()) { 393 LinkedList<Segment> subSegments = new LinkedList<Segment>(); 394 moveSegments(allSegments, subSegments, allSegments.get(0), null); 395 segmentSets.add(subSegments); 396 } 397 } 398 399 // We now have a number of segment groups. 400 401 // If segments remain in allSegments, this means that they were not reachable 402 // from any of the split nodes, and they will be made into an extra way. 403 if (!allSegments.isEmpty()) { 404 segmentSets.add(allSegments); 405 } 406 407 // If we do not have at least two parts, then the way was circular or the node(s) 408 // were at one end of the way. User error ;-) 409 if (segmentSets.size() < 2) { 410 if (selectedNodes != null) { 411 JOptionPane.showMessageDialog(Main.parent, tr("The way cannot be split at the selected node. (Hint: To split circular ways, select two nodes.)")); 412 } else { 413 JOptionPane.showMessageDialog(Main.parent, tr("The way cannot be split because it is contiguous. (Hint: To split at a node, select that node.)")); 414 } 197 } 198 199 if (wayChunks.size() < 2) { 200 JOptionPane.showMessageDialog(Main.parent, tr("The way cannot be split at the selected nodes. (Hint: Select nodes in the middle of the way.)")); 415 201 return; 416 202 } 417 203 418 // sort the list of segment lists according to their number of elements, so that 419 // the biggest part of the way comes first. That way, we will "change" the largest 420 // part of the way by removing a few segments, and "add" new, smaller ways; looks 421 // nicer. 422 Collections.sort(segmentSets, new Comparator<Collection<Segment>>() { 423 public int compare(Collection<Segment> a, Collection<Segment> b) { 424 if (a.size() < b.size()) 425 return 1; 426 if (b.size() < a.size()) 427 return -1; 428 return 0; 429 } 430 }); 431 432 // build a list of commands, and also a list of ways 433 Collection<Command> commandList = new ArrayList<Command>(segmentSets.size()); 434 Collection<Way> newSelection = new ArrayList<Way>(segmentSets.size()); 435 Iterator<List<Segment>> segsIt = segmentSets.iterator(); 204 // build a list of commands, and also a new selection list 205 Collection<Command> commandList = new ArrayList<Command>(wayChunks.size()); 206 Collection<Way> newSelection = new ArrayList<Way>(wayChunks.size()); 436 207 437 // the first is always a change to the existing way; 208 Iterator<List<Node>> chunkIt = wayChunks.iterator(); 209 210 // First, change the original way 438 211 Way changedWay = new Way(selectedWay); 439 changedWay. segments.clear();440 changedWay. segments.addAll(segsIt.next());212 changedWay.nodes.clear(); 213 changedWay.nodes.addAll(chunkIt.next()); 441 214 commandList.add(new ChangeCommand(selectedWay, changedWay)); 442 215 newSelection.add(selectedWay); 443 216 444 // and commands 1...n are additions of new ways.445 while ( segsIt.hasNext()) {217 // Second, create new ways 218 while (chunkIt.hasNext()) { 446 219 Way wayToAdd = new Way(); 447 220 if (selectedWay.keys != null) 448 221 wayToAdd.keys = new HashMap<String, String>(selectedWay.keys); 449 wayToAdd.segments.clear(); 450 wayToAdd.segments.addAll(segsIt.next()); 222 wayToAdd.nodes.addAll(chunkIt.next()); 451 223 commandList.add(new AddCommand(wayToAdd)); 452 224 newSelection.add(wayToAdd); … … 455 227 NameVisitor v = new NameVisitor(); 456 228 v.visit(selectedWay); 457 Main.main.undoRedo.add(new SequenceCommand(tr("Split way {0} into {1} parts",v.name, segmentSets.size()), commandList)); 229 Main.main.undoRedo.add( 230 new SequenceCommand(tr("Split way {0} into {1} parts", 231 v.name, wayChunks.size()), 232 commandList)); 458 233 Main.ds.setSelected(newSelection); 459 234 } … … 465 240 setEnabled(checkSelection(newSelection)); 466 241 } 467 468 /**469 * Move contiguous segments from one collection to another. The given segment is moved first, and470 * then the procedure is recursively called for all segments that connect to the first segment at471 * either end.472 *473 * @param source the source collection474 * @param destination the destination collection475 * @param start the first segment to be moved476 * @param stopNodes collection of nodes which should be considered end points for moving (may be null).477 */478 private void moveSegments(Collection<Segment> source, LinkedList<Segment> destination, Segment start, Collection<Node> stopNodes) {479 source.remove(start);480 if (destination.isEmpty() || destination.iterator().next().from.equals(start.to))481 destination.addFirst(start);482 else483 destination.addLast(start);484 Segment moveSeg = start;485 while(moveSeg != null) {486 moveSeg = null;487 488 for (Node node : new Node[] { start.from, start.to }) {489 if (stopNodes != null && stopNodes.contains(node))490 continue;491 for (Segment sourceSeg : source) {492 if (sourceSeg.from.equals(node) || sourceSeg.to.equals(node)) {493 moveSeg = sourceSeg;494 break;495 }496 }497 if (moveSeg != null)498 break;499 }500 if (moveSeg != null) {501 moveSegments(source, destination, moveSeg, stopNodes);502 }503 }504 }505 242 }
Note:
See TracChangeset
for help on using the changeset viewer.