Changeset 194 in josm
- Timestamp:
- 2007-01-12T18:37:38+01:00 (18 years ago)
- Location:
- src/org/openstreetmap/josm
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
src/org/openstreetmap/josm/actions/SplitWayAction.java
r186 r194 5 5 import java.awt.event.ActionEvent; 6 6 import java.awt.event.KeyEvent; 7 import java.util.Array s;7 import java.util.ArrayList; 8 8 import java.util.Collection; 9 import java.util.Collections; 10 import java.util.Comparator; 11 import java.util.HashMap; 9 12 import java.util.HashSet; 13 import java.util.Iterator; 14 import java.util.LinkedList; 15 import java.util.List; 16 import java.util.Map.Entry; 10 17 11 18 import javax.swing.JOptionPane; … … 22 29 import org.openstreetmap.josm.data.osm.Way; 23 30 import org.openstreetmap.josm.data.osm.visitor.NameVisitor; 31 import org.openstreetmap.josm.data.osm.visitor.Visitor; 24 32 25 33 /** 34 * Splits a way into multiple ways (all identical except the segments 35 * belonging to the way). 36 * 37 * Various split modes are used depending on what is selected. 38 * 39 * 1. One or more NODES (and, optionally, also one way) selected: 40 * 41 * (All nodes must be part of the same way. If a way is also selected, that way 42 * must contain all selected nodes.) 43 * 44 * Way is split AT the node(s) into contiguous ways. If the original contained 45 * one or more parts that were not reachable from any of the nodes, they form an 46 * extra new way. Examples (numbers are unselected nodes, letters are selected 47 * nodes) 48 * 49 * 1---A---2 becomes 1---A and A---2 50 * 51 * 1---A---2---B---3 becomes 1---A and A---2---B and B---3 52 * 53 * 2 54 * | 55 * 1---A---3 becomes 1---A and 2---A and A---3 26 56 * 27 * @author framm 57 * 1---A---2 3---4 becomes 1---A and A---2 and 3---4 58 * 59 * If the selected node(s) do not clearly define the way that is to be split, 60 * then the way must be selected for disambiguation (e.g. you have two ways, 61 * 1---2---3 and 4---2---5, and select node 2, then you must also select the 62 * way you want to split). 63 * 64 * This function will result in at least two ways, unless the selected node is 65 * at the end of the way AND the way is contiguous, which will lead to an error 66 * message. 67 * 68 * After executing the operation, the selection will be cleared. 69 * 70 * 2. One or more SEGMENTS (and, optionally, also one way) selected: 71 * 72 * (All segments must be part of the same way) 73 * 74 * The way is split in a fashion that makes a new way from the selected segments, 75 * i.e. the selected segments are removed from the way to form a new one. 76 * 77 * This function will result in exactly two ways. 78 * 79 * If splitting the segments out of the way makes a non-contiguous part from 80 * something that was contiguous before, the action is aborted and an error 81 * message is shown. 82 * 83 * 3. Exactly one WAY selected 84 * 85 * If the way is contiguous, you will get an error message. If the way is not 86 * contiguous it is split it into 2...n contiguous ways. 28 87 */ 88 29 89 public class SplitWayAction extends JosmAction implements SelectionChangedListener { 30 90 31 private Way way; 32 private Node node; 91 private Way selectedWay; 92 private List<Node> selectedNodes; 93 private List<Segment> selectedSegments; 33 94 34 95 /** … … 41 102 42 103 /** 43 * Called when the action is executed 104 * Called when the action is executed. 105 * 106 * This method performs an expensive check whether the selection clearly defines one 107 * of the split actions outlined above, and if yes, calls the splitWay method. 44 108 */ 45 109 public void actionPerformed(ActionEvent e) { 46 if (!checkSelection(Main.ds.getSelected())) { 47 JOptionPane.showMessageDialog(Main.parent, tr("Select exactly one way to split and one node to split the way at.")); 110 111 Collection<OsmPrimitive> selection = Main.ds.getSelected(); 112 113 if (!checkSelection(selection)) { 114 JOptionPane.showMessageDialog(Main.parent, tr("The current selection cannot be used for splitting.")); 48 115 return; 49 116 } 50 117 51 if (way == null) { 52 for (Way w : Main.ds.ways) { 53 for (Segment s : w.segments) { 54 if (s.from == node || s.to == node) { 55 if (way != null) { 56 JOptionPane.showMessageDialog(Main.parent, tr("The selected node belongs to more than one way. Please select both, the node and the way to split.")); 57 return; 118 selectedWay = null; 119 selectedNodes = null; 120 selectedSegments = null; 121 122 Visitor splitVisitor = new Visitor(){ 123 public void visit(Node n) { 124 if (selectedNodes == null) 125 selectedNodes = new LinkedList<Node>(); 126 selectedNodes.add(n); 127 } 128 public void visit(Segment s) { 129 if (selectedSegments == null) 130 selectedSegments = new LinkedList<Segment>(); 131 selectedSegments.add(s); 132 } 133 public void visit(Way w) { 134 selectedWay = w; 135 } 136 }; 137 138 for (OsmPrimitive p : selection) 139 p.visit(splitVisitor); 140 141 // If only nodes are selected, try to guess which way to split. This works if there 142 // is exactly one way that all nodes are part of. 143 if (selectedWay == null && selectedNodes != null) { 144 HashMap<Way, Integer> wayOccurenceCounter = new HashMap<Way, Integer>(); 145 for (Node n : selectedNodes) { 146 for (Way w : Main.ds.ways) { 147 for (Segment s : w.segments) { 148 if (n.equals(s.from) || n.equals(s.to)) { 149 Integer old = wayOccurenceCounter.get(w); 150 wayOccurenceCounter.put(w, (old == null) ? 1 : old+1); 151 break; 58 152 } 59 way = w; 153 } 154 } 155 } 156 if (wayOccurenceCounter.isEmpty()) { 157 JOptionPane.showMessageDialog(Main.parent, tr("The selected node(s) is (are) not part of any way.")); 158 return; 159 } 160 161 for (Entry<Way, Integer> entry : wayOccurenceCounter.entrySet()) { 162 if (entry.getValue().equals(selectedNodes.size())) { 163 if (selectedWay != null) { 164 JOptionPane.showMessageDialog(Main.parent, tr("There is more than one way using the node(s) you selected. Please select the way also.")); 165 return; 166 } 167 selectedWay = entry.getKey(); 168 } 169 } 170 171 if (selectedWay == null) { 172 JOptionPane.showMessageDialog(Main.parent, tr("The selected nodes do not share the same way.")); 173 return; 174 } 175 176 // If a way and nodes are selected, verify that the nodes are part of the way. 177 } else if (selectedWay != null && selectedNodes != null) { 178 179 HashSet<Node> nds = new HashSet<Node>(selectedNodes); 180 for (Segment s : selectedWay.segments) { 181 nds.remove(s.from); 182 nds.remove(s.to); 183 } 184 if (!nds.isEmpty()) { 185 JOptionPane.showMessageDialog(Main.parent, tr("The selected way does not contain (all) the selected node(s).")); 186 return; 187 } 188 189 // If only segments are selected, guess which way to use. 190 } else if (selectedWay == null && selectedSegments != null) { 191 192 HashMap<Way, Integer> wayOccurenceCounter = new HashMap<Way, Integer>(); 193 for (Segment s : selectedSegments) { 194 for (Way w : Main.ds.ways) { 195 if (w.segments.contains(s)) { 196 Integer old = wayOccurenceCounter.get(w); 197 wayOccurenceCounter.put(w, (old == null) ? 1 : old+1); 60 198 break; 61 199 } 62 200 } 63 201 } 64 } 65 if (way == null) { 66 JOptionPane.showMessageDialog(Main.parent, tr("The seleced node is not part of any way.")); 67 return; 68 } 69 70 boolean node_found = false; 71 for (Segment s : way.segments) { 202 if (wayOccurenceCounter.isEmpty()) { 203 JOptionPane.showMessageDialog(Main.parent, tr("The selected segment(s) is (are) not part of any way.")); 204 return; 205 } 206 207 for (Entry<Way, Integer> entry : wayOccurenceCounter.entrySet()) { 208 if (entry.getValue().equals(selectedSegments.size())) { 209 if (selectedWay != null) { 210 JOptionPane.showMessageDialog(Main.parent, tr("There is more than one way using the segment(s) you selected. Please select the way also.")); 211 return; 212 } 213 selectedWay = entry.getKey(); 214 } 215 } 216 217 if (selectedWay == null) { 218 JOptionPane.showMessageDialog(Main.parent, tr("The selected segments do not share the same way.")); 219 return; 220 } 221 222 // If a way and segments are selected, verify that the segments are part of the way. 223 } else if (selectedWay != null && selectedSegments != null) { 224 225 if (!selectedWay.segments.containsAll(selectedSegments)) { 226 JOptionPane.showMessageDialog(Main.parent, tr("The selected way does not contain (all) the selected segment(s).")); 227 return; 228 } 229 } 230 231 // finally check if the selected way is complete. 232 for (Segment s : selectedWay.segments) { 72 233 if (s.incomplete) { 73 234 JOptionPane.showMessageDialog(Main.parent, tr("Warning: This way is incomplete. Try to download it before splitting.")); 74 235 return; 75 236 } 76 if (!node_found && (s.from.equals(node) || s.to.equals(node))) { 77 node_found = true; 78 } 79 } 80 81 if (!node_found) { 82 JOptionPane.showMessageDialog(Main.parent, tr("The seleced node is not part of the selected way.")); 83 return; 84 } 85 237 } 238 239 // and then do the work. 86 240 splitWay(); 87 241 } 88 242 89 243 /** 90 * Checks if the selection consists of eactly one way and one node. 91 * Does not check whether the node is part of the way. 92 */ 244 * Checks if the selection consists of something we can work with. 245 * Checks only if the number and type of items selected looks good; 246 * does not check whether the selected items are really a valid 247 * input for splitting (this would be too expensive to be carried 248 * out from the selectionChanged listener). 249 */ 93 250 private boolean checkSelection(Collection<? extends OsmPrimitive> selection) { 94 if (selection.isEmpty() || selection.size() > 2) 95 return false; 96 way = null; 97 node = null; 251 boolean way = false; 252 boolean segment = false; 253 boolean node = false; 98 254 for (OsmPrimitive p : selection) { 99 if ((p instanceof Way) && (way == null)) { 100 way = (Way)p; 101 } else if ((p instanceof Node) && (node == null)) { 102 node = (Node)p; 103 } else { 255 if (p instanceof Way && !way) 256 way = true; 257 else if (p instanceof Node && !segment) 258 node = true; 259 else if (p instanceof Segment && !node) 260 segment = true; 261 else 104 262 return false; 105 } 106 } 107 return node != null; 263 } 264 return way || segment || node; 108 265 } 109 266 110 267 /** 111 * Split a way into two parts. 268 * Split a way into two or more parts, starting at a selected node. 269 * 270 * @param way the way to split 271 * @param nodes the node(s) to split the way at; must be part of the way. 112 272 */ 113 273 private void splitWay() { 114 274 115 HashSet<Node> nodesInFirstHalf = new HashSet<Node>(); 116 117 for (Segment s : way.segments) { 118 if (s.from.equals(node)) { 119 nodesInFirstHalf.add(s.to); 120 break; 121 } else if (s.to.equals(node)) { 122 nodesInFirstHalf.add(s.from); 123 break; 124 } 125 } 126 127 boolean loop = true; 128 while (loop) { 129 loop = false; 130 for (Segment s : way.segments) { 131 if (nodesInFirstHalf.contains(s.from) && (!s.to.equals(node)) 132 && !nodesInFirstHalf.contains(s.to)) { 133 nodesInFirstHalf.add(s.to); 134 loop = true; 135 } else if (nodesInFirstHalf.contains(s.to) 136 && (!s.from.equals(node)) 137 && !nodesInFirstHalf.contains(s.from)) { 138 nodesInFirstHalf.add(s.from); 139 loop = true; 140 } 141 } 142 } 143 144 Way wayToAdd = new Way(way); 145 wayToAdd.id = 0; 146 Way changedWay = new Way(way); 147 148 for (Segment s : way.segments) { 149 if (nodesInFirstHalf.contains(s.from) || nodesInFirstHalf.contains(s.to)) { 150 changedWay.segments.remove(s); 275 // The basic idea is to first divide all segments forming this way into 276 // groups, and later form new ways according to the groups. Initally, 277 // all segments are copied into allSegments, and then gradually removed 278 // from there as new groups are built. 279 280 LinkedList<Segment> allSegments = new LinkedList<Segment>(); 281 allSegments.addAll(selectedWay.segments); 282 List<List<Segment>> segmentSets = new ArrayList<List<Segment>>(); 283 284 if (selectedNodes != null) { 285 286 // This is the "split at node" mode. 287 288 boolean split = true; 289 Segment splitSeg = null; 290 while (split) { 291 split = false; 292 293 // Look for a "split segment". A split segment is any segment 294 // that touches one of the split nodes and has not yet been 295 // assigned to one of the segment groups. 296 for (Segment s : allSegments) { 297 for (Node node : selectedNodes) { 298 if (s.from.equals(node) || s.to.equals(node)) { 299 split = true; 300 splitSeg = s; 301 break; 302 } 303 } 304 if (split) 305 break; 306 } 307 308 // If a split segment was found, move this segment and all segments 309 // connected to it into a new segment group, stopping only if we 310 // reach another split node. Segment moving is done recursively by 311 // the moveSegments method. 312 if (split) { 313 ArrayList<Segment> subSegments = new ArrayList<Segment>(); 314 moveSegments(allSegments, subSegments, splitSeg, selectedNodes); 315 segmentSets.add(subSegments); 316 } 317 318 // The loop continues until no more split segments were found. 319 // Nb. not all segments touching a split node are split segments; 320 // e.g. 321 // 322 // 2 4 323 // | | 324 // 1---A---3---C---5 325 // 326 // This way will be split into 5 ways (1---A,2---A,A---3---C,4---C, 327 // C---5). Depending on which is processed first, either A---3 becomes 328 // a split segment and 3---C is moved as a connecting segment, or vice 329 // versa. The result is, of course, the same but this explains why we 330 // cannot simply start a new way for each segment connecting to a split 331 // node. 332 } 333 334 } else if (selectedSegments != null) { 335 336 // This is the "split segments" mode. It is quite easy as the segments to 337 // remove are already explicitly selected, but some restrictions have to 338 // be observed to make sure that no non-contiguous parts are created. 339 340 // first create a "scratch" copy of the full segment list and move all 341 // segments connected to the first selected segment into a temporary list. 342 LinkedList<Segment> copyOfAllSegments = new LinkedList<Segment>(allSegments); 343 LinkedList<Segment> partThatContainsSegments = new LinkedList<Segment>(); 344 moveSegments(copyOfAllSegments, partThatContainsSegments, selectedSegments.get(0), null); 345 346 // this list must now contain ALL selected segments; otherwise, segments 347 // from unconnected parts of the way have been selected and this is not allowed 348 // as it would create a new non-contiguous way. 349 if (!partThatContainsSegments.containsAll(selectedSegments)) { 350 JOptionPane.showMessageDialog(Main.parent, tr("The selected segments are not in the same contiguous part of the way.")); 351 return; 352 } 353 354 // if the contiguous part that contains the segments becomes non-contiguous 355 // after the removal of the segments, that is also an error. 356 partThatContainsSegments.removeAll(selectedSegments); 357 if (!partThatContainsSegments.isEmpty()) { 358 ArrayList<Segment> contiguousSubpart = new ArrayList<Segment>(); 359 moveSegments(partThatContainsSegments, contiguousSubpart, partThatContainsSegments.get(0), null); 360 // if partThatContainsSegments was contiguous before, it will now be empty as all segments 361 // connected to the first segment therein have been moved 362 if (!partThatContainsSegments.isEmpty()) { 363 JOptionPane.showMessageDialog(Main.parent, tr("Removing the selected segments would make a part of the way non-contiguous.")); 364 return; 365 } 366 } 367 368 ArrayList<Segment> subSegments = new ArrayList<Segment>(); 369 subSegments.addAll(selectedSegments); 370 allSegments.removeAll(selectedSegments); 371 segmentSets.add(subSegments); 372 373 } else { 374 375 // This is the "split way into contiguous parts" mode. 376 // We use a similar mechanism to splitting at nodes, but we do not 377 // select split segments. Instead, we randomly grab a segment out 378 // of the way and move all connecting segments to a new group. If 379 // segments remain in the original way, we repeat the procedure. 380 381 while (!allSegments.isEmpty()) { 382 List<Segment> subSegments = new LinkedList<Segment>(); 383 moveSegments(allSegments, subSegments, allSegments.get(0), null); 384 segmentSets.add(subSegments); 385 } 386 } 387 388 // We now have a number of segment groups. 389 390 // If segments remain in allSegments, this means that they were not reachable 391 // from any of the split nodes, and they will be made into an extra way. 392 if (!allSegments.isEmpty()) { 393 segmentSets.add(allSegments); 394 } 395 396 // If we do not have at least two parts, then the way was circular or the node(s) 397 // were at one end of the way. User error ;-) 398 if (segmentSets.size() < 2) { 399 if (selectedNodes != null) { 400 JOptionPane.showMessageDialog(Main.parent, tr("The way cannot be split at the selected node. (Hint: To split circular ways, select two nodes.)")); 151 401 } else { 152 wayToAdd.segments.remove(s); 153 } 154 } 402 JOptionPane.showMessageDialog(Main.parent, tr("The way cannot be split because it is contiguous. (Hint: To split at a node, select that node.)")); 403 } 404 return; 405 } 406 407 // sort the list of segment lists according to their number of elements, so that 408 // the biggest part of the way comes first. That way, we will "change" the largest 409 // part of the way by removing a few segments, and "add" new, smaller ways; looks 410 // nicer. 411 Collections.sort(segmentSets, new Comparator<Collection<Segment>>() { 412 public int compare(Collection<Segment> a, Collection<Segment> b) { 413 if (a.size() < b.size()) 414 return 1; 415 if (b.size() < a.size()) 416 return -1; 417 return 0; 418 } 419 }); 420 421 // build a list of commands, and also a list of ways 422 Collection<Command> commandList = new ArrayList<Command>(segmentSets.size()); 423 Collection<Way> newSelection = new ArrayList<Way>(segmentSets.size()); 424 Iterator<List<Segment>> segsIt = segmentSets.iterator(); 155 425 156 if (wayToAdd.segments.isEmpty() || changedWay.segments.isEmpty()) { 157 JOptionPane.showMessageDialog(Main.parent, tr("The selected node is not in the middle of the way.")); 158 return; 426 // the first is always a change to the existing way; 427 Way changedWay = new Way(selectedWay); 428 changedWay.segments.clear(); 429 changedWay.segments.addAll(segsIt.next()); 430 commandList.add(new ChangeCommand(selectedWay, changedWay)); 431 newSelection.add(selectedWay); 432 433 // and commands 1...n are additions of new ways. 434 while (segsIt.hasNext()) { 435 Way wayToAdd = new Way(); 436 if (selectedWay.keys != null) 437 wayToAdd.keys = new HashMap<String, String>(selectedWay.keys); 438 wayToAdd.segments.clear(); 439 wayToAdd.segments.addAll(segsIt.next()); 440 commandList.add(new AddCommand(wayToAdd)); 441 newSelection.add(wayToAdd); 159 442 } 160 443 161 444 NameVisitor v = new NameVisitor(); 162 v.visit(way); 163 Main.main.editLayer().add(new SequenceCommand(tr("Split way {0}",v.name), 164 Arrays.asList(new Command[]{new ChangeCommand(way, changedWay), new AddCommand(wayToAdd)}))); 165 Main.ds.clearSelection(); 166 way = null; 167 node = null; 168 return; 169 } 170 445 v.visit(selectedWay); 446 Main.main.editLayer().add(new SequenceCommand(tr("Split way {0} into {1} parts",v.name, segmentSets.size()), commandList)); 447 Main.ds.setSelected(newSelection); 448 } 449 450 /** 451 * Enable the "split way" menu option if the selection looks like we could use it. 452 */ 171 453 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) { 172 454 setEnabled(checkSelection(newSelection)); 173 455 } 456 457 /** 458 * Move contiguous segments from one collection to another. The given segment is moved first, and 459 * then the procedure is recursively called for all segments that connect to the first segment at 460 * either end. 461 * 462 * @param source the source collection 463 * @param destination the destination collection 464 * @param start the first segment to be moved 465 * @param stopNodes collection of nodes which should be considered end points for moving (may be null). 466 */ 467 private void moveSegments(Collection<Segment> source, Collection<Segment> destination, Segment start, Collection<Node> stopNodes) { 468 source.remove(start); 469 destination.add(start); 470 Segment moveSeg = start; 471 while(moveSeg != null) { 472 moveSeg = null; 473 474 for (Node node : new Node[] { start.from, start.to }) { 475 if (stopNodes != null && stopNodes.contains(node)) continue; 476 for (Segment sourceSeg : source) { 477 if (sourceSeg.from.equals(node) || sourceSeg.to.equals(node)) { 478 moveSeg = sourceSeg; 479 break; 480 } 481 } 482 if (moveSeg != null) 483 break; 484 } 485 if (moveSeg != null) { 486 moveSegments(source, destination, moveSeg, stopNodes); 487 } 488 } 489 } 174 490 } -
src/org/openstreetmap/josm/data/osm/OsmPrimitive.java
r159 r194 34 34 * An id of 0 means an unknown id. The object has not been uploaded yet to 35 35 * know what id it will get. 36 * 37 * Do not write to this attribute except you know exactly what you are doing. 38 * More specific, it is not good to set this to 0 and think the object is now 39 * new to the server! To create a new object, call the default constructor of 40 * the respective class. 36 41 */ 37 42 public long id = 0;
Note:
See TracChangeset
for help on using the changeset viewer.