Changeset 3554 in josm for trunk/src/org/openstreetmap
- Timestamp:
- 2010-09-22T22:28:06+02:00 (14 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/actions/JoinAreasAction.java
r3449 r3554 2 2 package org.openstreetmap.josm.actions; 3 3 4 import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.combineTigerTags; 5 import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.completeTagCollectionForEditing; 6 import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.normalizeTagCollectionBeforeEditing; 4 7 import static org.openstreetmap.josm.tools.I18n.marktr; 5 8 import static org.openstreetmap.josm.tools.I18n.tr; 6 9 import static org.openstreetmap.josm.tools.I18n.trn; 7 10 8 import java.awt.GridBagLayout;9 11 import java.awt.event.ActionEvent; 10 12 import java.awt.event.KeyEvent; … … 14 16 import java.util.Collection; 15 17 import java.util.Collections; 18 import java.util.Comparator; 16 19 import java.util.HashMap; 17 20 import java.util.HashSet; 21 import java.util.LinkedHashSet; 18 22 import java.util.LinkedList; 19 23 import java.util.List; 20 24 import java.util.Map; 21 import java.util.Map.Entry;22 25 import java.util.Set; 23 26 import java.util.TreeMap; 24 import java.util.TreeSet; 25 26 import javax.swing.Box; 27 import javax.swing.JComboBox; 28 import javax.swing.JLabel; 27 29 28 import javax.swing.JOptionPane; 30 import javax.swing.JPanel;31 29 32 30 import org.openstreetmap.josm.Main; 31 import org.openstreetmap.josm.actions.ReverseWayAction.ReverseWayResult; 33 32 import org.openstreetmap.josm.actions.SplitWayAction.SplitWayResult; 34 33 import org.openstreetmap.josm.command.AddCommand; … … 37 36 import org.openstreetmap.josm.command.DeleteCommand; 38 37 import org.openstreetmap.josm.command.SequenceCommand; 38 import org.openstreetmap.josm.corrector.UserCancelException; 39 39 import org.openstreetmap.josm.data.UndoRedoHandler; 40 40 import org.openstreetmap.josm.data.coor.EastNorth; … … 45 45 import org.openstreetmap.josm.data.osm.Relation; 46 46 import org.openstreetmap.josm.data.osm.RelationMember; 47 import org.openstreetmap.josm.data.osm.T igerUtils;47 import org.openstreetmap.josm.data.osm.TagCollection; 48 48 import org.openstreetmap.josm.data.osm.Way; 49 import org.openstreetmap.josm.gui. ExtendedDialog;50 import org.openstreetmap.josm.tools. GBC;49 import org.openstreetmap.josm.gui.conflict.tags.CombinePrimitiveResolverDialog; 50 import org.openstreetmap.josm.tools.Pair; 51 51 import org.openstreetmap.josm.tools.Shortcut; 52 52 … … 56 56 private int cmdsCount = 0; 57 57 58 58 59 /** 59 60 * This helper class describes join ares action result. … … 63 64 public static class JoinAreasResult { 64 65 65 public Way outerWay;66 public List<Way> innerWays;67 68 66 public boolean mergeSuccessful; 69 67 public boolean hasChanges; 70 68 public boolean hasRelationProblems; 71 } 72 73 // HelperClass 74 // Saves a node and two positions where to insert the node into the ways 75 private static class NodeToSegs implements Comparable<NodeToSegs> { 76 public int pos; 77 public Node n; 78 public double dis; 79 public NodeToSegs(int pos, Node n, LatLon dis) { 80 this.pos = pos; 81 this.n = n; 82 this.dis = n.getCoor().greatCircleDistance(dis); 83 } 84 85 public int compareTo(NodeToSegs o) { 86 if(this.pos == o.pos) 87 return (this.dis - o.dis) > 0 ? 1 : -1; 88 return this.pos - o.pos; 89 } 90 91 @Override 92 public int hashCode() { 93 return pos; 94 } 95 96 @Override 97 public boolean equals(Object o) { 98 if (o instanceof NodeToSegs) 99 return compareTo((NodeToSegs) o) == 0; 100 else 101 return false; 69 70 public List<Multipolygon> polygons; 71 } 72 73 public static class Multipolygon { 74 public Way outerWay; 75 public List<Way> innerWays; 76 77 public Relation relation; 78 79 public Multipolygon(Way way) { 80 outerWay = way; 81 innerWays = new ArrayList<Way>(); 102 82 } 103 83 } … … 126 106 } 127 107 128 /** 129 * HelperClass 130 * saves a way and the "inside" side 131 * insideToTheLeft: if true left side is "in", false -right side is "in". 132 * Left and right are determined along the orientation of way. 133 */ 134 private static class WayInPath { 108 109 //HelperClass 110 //saves a way and the "inside" side 111 // insideToTheLeft: if true left side is "in", false -right side is "in". Left and right are determined along the orientation of way. 112 private static class WayInPolygon { 135 113 public final Way way; 136 public boolean insideToThe Left;137 138 public WayInP ath(Way way, boolean insideLeft) {139 this.way = way;140 this.insideToThe Left = insideLeft;114 public boolean insideToTheRight; 115 116 public WayInPolygon(Way _way, boolean _insideRight) { 117 this.way = _way; 118 this.insideToTheRight = _insideRight; 141 119 } 142 120 … … 148 126 @Override 149 127 public boolean equals(Object other) { 150 if (!(other instanceof WayInPath)) 151 return false; 152 WayInPath otherMember = (WayInPath) other; 153 return otherMember.way.equals(this.way) && otherMember.insideToTheLeft == this.insideToTheLeft; 154 } 155 } 128 if (!(other instanceof WayInPolygon)) return false; 129 WayInPolygon otherMember = (WayInPolygon) other; 130 return otherMember.way.equals(this.way) && otherMember.insideToTheRight == this.insideToTheRight; 131 } 132 } 133 134 /** 135 * This helper class describes a polygon, assembled from several ways. 136 * @author viesturs 137 * 138 */ 139 private static class AssembledPolygon { 140 public List<WayInPolygon> ways; 141 142 public AssembledPolygon(List<WayInPolygon> boundary) { 143 this.ways = boundary; 144 } 145 146 public List<Node> getNodes() { 147 List<Node> nodes = new ArrayList<Node>(); 148 for (WayInPolygon way : this.ways) { 149 //do not add the last node as it will be repeated in the next way 150 if (way.insideToTheRight) { 151 for (int pos = 0; pos < way.way.getNodesCount() - 1; pos++) { 152 nodes.add(way.way.getNode(pos)); 153 } 154 } 155 else { 156 for (int pos = way.way.getNodesCount() - 1; pos > 0; pos--) { 157 nodes.add(way.way.getNode(pos)); 158 } 159 } 160 } 161 162 return nodes; 163 } 164 } 165 166 167 public static class AssembledMultipolygon { 168 public AssembledPolygon outerWay; 169 public List<AssembledPolygon> innerWays; 170 171 public AssembledMultipolygon(AssembledPolygon way) { 172 outerWay = way; 173 innerWays = new ArrayList<AssembledPolygon>(); 174 } 175 } 176 177 /** 178 * This hepler class implements algorithm traversing trough connected ways. 179 * Assumes you are going in clockwise orientation. 180 * @author viesturs 181 * 182 */ 183 private static class WayTraverser { 184 185 private Set<WayInPolygon> availableWays; 186 private WayInPolygon lastWay; 187 private boolean lastWayReverse; 188 189 public WayTraverser(Collection<WayInPolygon> ways) { 190 191 availableWays = new HashSet<WayInPolygon>(ways); 192 lastWay = null; 193 } 194 195 public void removeWays(Collection<WayInPolygon> ways) { 196 availableWays.removeAll(ways); 197 } 198 199 public boolean hasWays() { 200 return availableWays.size() > 0; 201 } 202 203 public WayInPolygon startNewWay(WayInPolygon way) { 204 lastWay = way; 205 lastWayReverse = !lastWay.insideToTheRight; 206 207 return lastWay; 208 } 209 210 public WayInPolygon startNewWay() { 211 if (availableWays.size() == 0) { 212 lastWay = null; 213 } else { 214 lastWay = availableWays.iterator().next(); 215 lastWayReverse = !lastWay.insideToTheRight; 216 } 217 218 return lastWay; 219 } 220 221 222 public WayInPolygon advanceNextLeftmostWay() { 223 return advanceNextWay(false); 224 } 225 226 public WayInPolygon advanceNextRightmostWay() { 227 return advanceNextWay(true); 228 } 229 230 private WayInPolygon advanceNextWay(boolean rightmost) { 231 232 Node headNode = !lastWayReverse ? lastWay.way.lastNode() : lastWay.way.firstNode(); 233 Node prevNode = !lastWayReverse ? lastWay.way.getNode(lastWay.way.getNodesCount() - 2) : lastWay.way.getNode(1); 234 235 //find best next way 236 WayInPolygon bestWay = null; 237 Node bestWayNextNode = null; 238 boolean bestWayReverse = false; 239 240 for (WayInPolygon way : availableWays) { 241 if (way.way.firstNode().equals(headNode)) { 242 //start adjacent to headNode 243 Node nextNode = way.way.getNode(1); 244 245 if (nextNode.equals(prevNode)) 246 { 247 //this is the path we came from - ignore it. 248 } 249 else if (bestWay == null || (isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode) == rightmost)) { 250 //the new way is better 251 bestWay = way; 252 bestWayReverse = false; 253 bestWayNextNode = nextNode; 254 } 255 } 256 257 if (way.way.lastNode().equals(headNode)) { 258 //end adjacent to headNode 259 Node nextNode = way.way.getNode(way.way.getNodesCount() - 2); 260 261 if (nextNode.equals(prevNode)) { 262 //this is the path we came from - ignore it. 263 } 264 else if (bestWay == null || (isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode) == rightmost)) { 265 //the new way is better 266 bestWay = way; 267 bestWayReverse = true; 268 bestWayNextNode = nextNode; 269 } 270 } 271 } 272 273 lastWay = bestWay; 274 lastWayReverse = bestWayReverse; 275 276 return lastWay; 277 } 278 279 public boolean isLastWayInsideToTheRight() { 280 return lastWayReverse != lastWay.insideToTheRight; 281 } 282 283 public Node getLastWayStartNode() { 284 return lastWayReverse ? lastWay.way.lastNode() : lastWay.way.firstNode(); 285 } 286 287 public Node getLastWayEndNode() { 288 return lastWayReverse ? lastWay.way.firstNode() : lastWay.way.lastNode(); 289 } 290 } 291 292 /** 293 * Provides some node order , based on coordinates, nodes with equal coordinates are equal. 294 * @author viesturs 295 * 296 */ 297 private class NodePositionComparator implements Comparator<Node> { 298 299 @Override 300 public int compare(Node n1, Node n2) { 301 302 double dLat = n1.getCoor().lat() - n2.getCoor().lat(); 303 double dLon = n1.getCoor().lon() - n2.getCoor().lon(); 304 305 if (dLat > 0) 306 return 1; 307 else if (dLat < 0) 308 return -1; 309 else if (dLon == 0) //dlat is 0 here 310 return 0; 311 else 312 return dLon > 0 ? 1 : -1; 313 } 314 } 315 316 317 /** 318 * Helper storage class for finding findOuterWays 319 * @author viesturs 320 */ 321 static class PolygonLevel { 322 public final int level; 323 public final AssembledMultipolygon pol; 324 325 public PolygonLevel(AssembledMultipolygon _pol, int _level) { 326 pol = _pol; 327 level = _level; 328 } 329 } 330 156 331 157 332 // Adds the menu entry, Shortcuts, etc. … … 173 348 } 174 349 175 // Too many ways176 if(ways.size() > 2) {177 JOptionPane.showMessageDialog(Main.parent, tr("Only up to two areas can be joined at the moment."));178 return;179 }180 181 350 List<Node> allNodes = new ArrayList<Node>(); 182 for (Way way : ways) {183 if (!way.isClosed()) {351 for (Way way : ways) { 352 if (!way.isClosed()) { 184 353 JOptionPane.showMessageDialog(Main.parent, tr("\"{0}\" is not closed and therefore cannot be joined.", way.getName())); 185 354 return; … … 192 361 Area dataSourceArea = Main.main.getCurrentDataSet().getDataSourceArea(); 193 362 if (dataSourceArea != null) { 194 for (Node node : allNodes) {363 for (Node node : allNodes) { 195 364 if (!dataSourceArea.contains(node.getCoor())) { 196 365 int option = JOptionPane.showConfirmDialog(Main.parent, … … 209 378 } 210 379 211 if (checkForTagConflicts(ways.getFirst(), ways.getLast())) 212 //there was conflicts and user canceled abort the action. 380 //analyze multipolygon relations and collect all areas 381 List<Multipolygon> areas = collectMultipolygons(ways); 382 383 if (areas == null) 384 //too complex multipolygon relations found 213 385 return; 214 386 215 216 JoinAreasResult result = joinAreas(ways.getFirst(), ways.getLast()); 217 218 if (result.hasChanges) { 219 Main.map.mapView.repaint(); 220 DataSet ds = Main.main.getCurrentDataSet(); 221 ds.fireSelectionChanged(); 222 } else { 387 if (!testJoin(areas)) { 223 388 JOptionPane.showMessageDialog(Main.parent, tr("No intersection found. Nothing was changed.")); 224 } 225 } 226 227 /** 228 * Will join two overlapping areas 229 * @param Way First way/area 230 * @param Way Second way/area 231 */ 232 private JoinAreasResult joinAreas(Way a, Way b) { 389 return; 390 } 391 392 if (!resolveTagConflicts(areas)) 393 return; 394 //user cancelled, do nothing. 395 396 try { 397 JoinAreasResult result = joinAreas(areas); 398 399 if (result.hasChanges) { 400 401 Main.map.mapView.repaint(); 402 DataSet ds = Main.main.getCurrentDataSet(); 403 ds.fireSelectionChanged(); 404 } else { 405 JOptionPane.showMessageDialog(Main.parent, tr("No intersection found. Nothing was changed.")); 406 } 407 } 408 catch (UserCancelException exception) { 409 //revert changes 410 //FIXME: this is dirty hack 411 makeCommitsOneAction(tr("Reverting changes")); 412 Main.main.undoRedo.undo(); 413 Main.main.undoRedo.redoCommands.clear(); 414 } 415 } 416 417 418 /** 419 * Tests if the areas have some intersections to join. 420 * @param areas 421 * @return 422 */ 423 private boolean testJoin(List<Multipolygon> areas) { 424 List<Way> allStartingWays = new ArrayList<Way>(); 425 426 for (Multipolygon area : areas) { 427 allStartingWays.add(area.outerWay); 428 allStartingWays.addAll(area.innerWays); 429 } 430 431 //find intersection points 432 ArrayList<Node> nodes = addIntersections(allStartingWays, true); 433 return nodes.size() > 0; 434 } 435 436 /** 437 * Will join two or more overlapping areas 438 * @param areas - list of areas to join 439 * @return new area formed. 440 */ 441 private JoinAreasResult joinAreas(List<Multipolygon> areas) throws UserCancelException { 233 442 234 443 JoinAreasResult result = new JoinAreasResult(); 235 444 result.hasChanges = false; 236 445 237 // Fix self-overlapping first or other errors238 boolean same = a.equals(b);239 if(!same) {240 int i = 0; 241 242 //join each area with itself, fixing self-crossings.243 JoinAreasResult resultA = joinAreas(a, a);244 JoinAreasResult resultB = joinAreas(b, b);245 246 if (resultA.mergeSuccessful) {247 a = resultA.outerWay;248 ++i; 249 }250 if(resultB.mergeSuccessful) {251 b = resultB.outerWay;252 ++i; 253 }254 255 result.hasChanges = i > 0;256 cmdsCount = i;257 } 258 259 ArrayList<Node> nodes = addIntersections(a , b);446 List<Way> allStartingWays = new ArrayList<Way>(); 447 List<Way> innerStartingWays = new ArrayList<Way>(); 448 List<Way> outerStartingWays = new ArrayList<Way>(); 449 450 for (Multipolygon area : areas) { 451 outerStartingWays.add(area.outerWay); 452 innerStartingWays.addAll(area.innerWays); 453 } 454 455 allStartingWays.addAll(innerStartingWays); 456 allStartingWays.addAll(outerStartingWays); 457 458 //first remove nodes in the same coordinate 459 boolean removedDuplicates = false; 460 removedDuplicates |= removeDuplicateNodes(allStartingWays); 461 462 if (removedDuplicates) { 463 result.hasChanges = true; 464 commitCommands(marktr("Removed duplicate nodes")); 465 } 466 467 //find intersection points 468 ArrayList<Node> nodes = addIntersections(allStartingWays, false); 260 469 261 470 //no intersections, return. 262 if (nodes.size() == 0) return result;471 if (nodes.size() == 0) return result; 263 472 commitCommands(marktr("Added node on all intersections")); 264 473 474 ArrayList<RelationRole> relations = new ArrayList<RelationRole>(); 475 265 476 // Remove ways from all relations so ways can be combined/split quietly 266 ArrayList<RelationRole> relations = removeFromRelations(a); 267 if(!same) { 268 relations.addAll(removeFromRelations(b)); 477 for (Way way : allStartingWays) { 478 relations.addAll(removeFromAllRelations(way)); 269 479 } 270 480 271 481 // Don't warn now, because it will really look corrupted 272 boolean warnAboutRelations = relations.size() > 0; 273 274 ArrayList<Way> allWays = splitWaysOnNodes(a, b, nodes); 275 276 // Find inner ways save them to a list 277 ArrayList<WayInPath> outerWays = findOuterWays(allWays); 278 ArrayList<Way> innerWays = findInnerWays(allWays, outerWays); 279 280 // Join outer ways 281 Way outerWay = joinOuterWays(outerWays); 282 283 // Fix Multipolygons if there are any 284 List<Way> newInnerWays = fixMultipolygons(innerWays, outerWay, same); 285 286 // Delete the remaining inner ways 287 if(innerWays != null && innerWays.size() > 0) { 288 cmds.add(DeleteCommand.delete(Main.map.mapView.getEditLayer(), innerWays, true)); 482 boolean warnAboutRelations = relations.size() > 0 && allStartingWays.size() > 1; 483 484 ArrayList<WayInPolygon> preparedWays = new ArrayList<WayInPolygon>(); 485 486 for (Way way : outerStartingWays) { 487 ArrayList<Way> splitWays = splitWayOnNodes(way, nodes); 488 preparedWays.addAll(markWayInsideSide(splitWays, false)); 489 } 490 491 for (Way way : innerStartingWays) { 492 ArrayList<Way> splitWays = splitWayOnNodes(way, nodes); 493 preparedWays.addAll(markWayInsideSide(splitWays, true)); 494 } 495 496 // Find boundary ways 497 ArrayList<Way> discardedWays = new ArrayList<Way>(); 498 List<AssembledPolygon> bounadries = findBoundaryPolygons(preparedWays, discardedWays); 499 500 //find polygons 501 List<AssembledMultipolygon> preparedPolygons = findPolygons(bounadries); 502 503 //assemble final ways 504 List<Multipolygon> polygons = new ArrayList<Multipolygon>(); 505 for (AssembledMultipolygon pol : preparedPolygons) { 506 polygons.add(joinPolygon(pol)); 507 } 508 509 // Delete the discarded inner ways 510 if (discardedWays.size() > 0) { 511 cmds.add(DeleteCommand.delete(Main.map.mapView.getEditLayer(), discardedWays, true)); 289 512 } 290 513 commitCommands(marktr("Delete Ways that are not part of an inner multipolygon")); 291 514 292 515 // We can attach our new multipolygon relation and pretend it has always been there 293 addOwnMultigonRelation(newInnerWays, outerWay, relations); 294 fixRelations(relations, outerWay); 516 for (Multipolygon pol : polygons) { 517 addOwnMultigonRelation(pol.innerWays, pol.outerWay, relations); 518 fixRelations(relations, pol.outerWay); 519 } 520 295 521 commitCommands(marktr("Fix relations")); 296 522 297 stripTags(newInnerWays); 298 299 makeCommitsOneAction( 300 same 301 ? marktr("Joined self-overlapping area") 302 : marktr("Joined overlapping areas") 303 ); 304 305 if(warnAboutRelations) { 523 for (Multipolygon pol : polygons) { 524 stripTags(pol.innerWays); 525 } 526 527 makeCommitsOneAction(marktr("Joined overlapping areas")); 528 529 if (warnAboutRelations) { 306 530 JOptionPane.showMessageDialog(Main.parent, tr("Some of the ways were part of relations that have been modified. Please verify no errors have been introduced.")); 307 531 } … … 309 533 result.hasChanges = true; 310 534 result.mergeSuccessful = true; 311 result.outerWay = outerWay; 312 result.innerWays = newInnerWays; 313 535 result.polygons = polygons; 314 536 return result; 315 537 } … … 319 541 * @param Way First way to check 320 542 * @param Way Second Way to check 321 * @return boolean True if not all conflicts could be resolved, False if everything's fine 322 */ 323 private boolean checkForTagConflicts(Way a, Way b) { 324 ArrayList<Way> ways = new ArrayList<Way>(); 325 ways.add(a); 326 ways.add(b); 327 328 // FIXME: This is mostly copied and pasted from CombineWayAction.java and one day should be moved into tools 329 // We have TagCollection handling for that now - use it here as well 330 Map<String, Set<String>> props = new TreeMap<String, Set<String>>(); 331 for (Way w : ways) { 332 for (String key: w.keySet()) { 333 if (!props.containsKey(key)) { 334 props.put(key, new TreeSet<String>()); 335 } 336 props.get(key).add(w.get(key)); 337 } 338 } 339 340 Way ax = new Way(a); 341 Way bx = new Way(b); 342 343 Map<String, JComboBox> components = new HashMap<String, JComboBox>(); 344 JPanel p = new JPanel(new GridBagLayout()); 345 for (Entry<String, Set<String>> e : props.entrySet()) { 346 if (TigerUtils.isTigerTag(e.getKey())) { 347 String combined = TigerUtils.combineTags(e.getKey(), e.getValue()); 348 ax.put(e.getKey(), combined); 349 bx.put(e.getKey(), combined); 350 } else if (e.getValue().size() > 1) { 351 if("created_by".equals(e.getKey())) 352 { 353 ax.remove("created_by"); 354 bx.remove("created_by"); 543 * @return boolean True if all conflicts are resolved, False if conflicts remain. 544 */ 545 private boolean resolveTagConflicts(List<Multipolygon> polygons) { 546 547 List<Way> ways = new ArrayList<Way>(); 548 549 for (Multipolygon pol : polygons) { 550 ways.add(pol.outerWay); 551 ways.addAll(pol.innerWays); 552 } 553 554 if (ways.size() < 2) 555 return true; 556 557 //mostly copied from CombineWayAction.java. 558 TagCollection wayTags = TagCollection.unionOfAllPrimitives(ways); 559 TagCollection completeWayTags = new TagCollection(wayTags); 560 combineTigerTags(completeWayTags); 561 normalizeTagCollectionBeforeEditing(completeWayTags, ways); 562 TagCollection tagsToEdit = new TagCollection(completeWayTags); 563 completeTagCollectionForEditing(tagsToEdit); 564 565 CombinePrimitiveResolverDialog dialog = CombinePrimitiveResolverDialog.getInstance(); 566 dialog.getTagConflictResolverModel().populate(tagsToEdit, completeWayTags.getKeysWithMultipleValues()); 567 dialog.setTargetPrimitive(ways.get(0)); 568 Collection<Relation> parentRelations = CombineWayAction.getParentRelations(ways); 569 parentRelations = filterOwnMultipolygonRelations(parentRelations, polygons); 570 dialog.getRelationMemberConflictResolverModel().populate( 571 parentRelations, 572 ways 573 ); 574 dialog.prepareDefaultDecisions(); 575 576 // resolve tag conflicts if necessary 577 // 578 if (!completeWayTags.isApplicableToPrimitive() || !parentRelations.isEmpty()) { 579 dialog.setVisible(true); 580 if (dialog.isCancelled()) 581 return false; 582 } 583 584 for (Way way : ways) { 585 dialog.setTargetPrimitive(way); 586 cmds.addAll(dialog.buildResolutionCommands()); 587 } 588 589 commitCommands(marktr("Fix tag conflicts")); 590 return true; 591 } 592 593 594 /** 595 * This method removes duplicate points (if any) from the input way. 596 * @param way the way to process 597 * @return true if any changes where made 598 */ 599 private boolean removeDuplicateNodes(List<Way> ways) { 600 //TODO: maybe join nodes with JoinNodesAction, rather than reconnect the ways. 601 602 Map<Node, Node> nodeMap = new TreeMap<Node, Node>(new NodePositionComparator()); 603 int totalNodesRemoved = 0; 604 605 for (Way way : ways) { 606 if (way.getNodes().size() < 2) { 607 continue; 608 } 609 610 int nodesRemoved = 0; 611 List<Node> newNodes = new ArrayList<Node>(); 612 Node prevNode = null; 613 614 for (Node node : way.getNodes()) { 615 if (!nodeMap.containsKey(node)) { 616 //new node 617 nodeMap.put(node, node); 618 619 //avoid duplicate nodes 620 if (prevNode != node) { 621 newNodes.add(node); 622 } else { 623 nodesRemoved ++; 624 } 355 625 } else { 356 JComboBox c = new JComboBox(e.getValue().toArray()); 357 c.setEditable(true); 358 p.add(new JLabel(e.getKey()), GBC.std()); 359 p.add(Box.createHorizontalStrut(10), GBC.std()); 360 p.add(c, GBC.eol()); 361 components.put(e.getKey(), c); 362 } 363 } else { 364 String val = e.getValue().iterator().next(); 365 ax.put(e.getKey(), val); 366 bx.put(e.getKey(), val); 367 } 368 } 369 370 if (components.isEmpty()) 371 return false; // No conflicts found 372 373 ExtendedDialog ed = new ExtendedDialog(Main.parent, 374 tr("Enter values for all conflicts."), 375 new String[] {tr("Solve Conflicts"), tr("Cancel")}); 376 ed.setButtonIcons(new String[] {"dialogs/conflict.png", "cancel.png"}); 377 ed.setContent(p); 378 ed.showDialog(); 379 380 if (ed.getValue() != 1) return true; // user cancel, unresolvable conflicts 381 382 for (Entry<String, JComboBox> e : components.entrySet()) { 383 String val = e.getValue().getEditor().getItem().toString(); 384 ax.put(e.getKey(), val); 385 bx.put(e.getKey(), val); 386 } 387 388 cmds.add(new ChangeCommand(a, ax)); 389 cmds.add(new ChangeCommand(b, bx)); 390 commitCommands(marktr("Fix tag conflicts")); 391 return false; 392 } 393 394 /** 395 * Will find all intersection and add nodes there for two given ways 396 * @param Way First way 397 * @param Way Second way 398 * @return ArrayList<OsmPrimitive> List of new nodes 399 */ 400 private ArrayList<Node> addIntersections(Way a, Way b) { 401 boolean same = a.equals(b); 402 int nodesSizeA = a.getNodesCount(); 403 int nodesSizeB = b.getNodesCount(); 404 405 ArrayList<Node> nodes = new ArrayList<Node>(); 406 ArrayList<NodeToSegs> nodesA = new ArrayList<NodeToSegs>(); 407 ArrayList<NodeToSegs> nodesB = new ArrayList<NodeToSegs>(); 408 409 for (int i = (same ? 1 : 0); i < nodesSizeA - 1; i++) { 410 for (int j = (same ? i + 2 : 0); j < nodesSizeB - 1; j++) { 411 // Avoid re-adding nodes that already exist on (some) intersections 412 if(a.getNode(i).equals(b.getNode(j)) || a.getNode(i+1).equals(b.getNode(j))) { 413 nodes.add(b.getNode(j)); 414 continue; 415 } else 416 if(a.getNode(i).equals(b.getNode(j+1)) || a.getNode(i+1).equals(b.getNode(j+1))) { 417 nodes.add(b.getNode(j+1)); 418 continue; 626 //node with same coordinates already exists, substitute with existing node 627 Node representator = nodeMap.get(node); 628 629 if (representator != node) { 630 nodesRemoved ++; 419 631 } 420 LatLon intersection = getLineLineIntersection( 421 a.getNode(i) .getEastNorth().east(), a.getNode(i) .getEastNorth().north(), 422 a.getNode(i+1).getEastNorth().east(), a.getNode(i+1).getEastNorth().north(), 423 b.getNode(j) .getEastNorth().east(), b.getNode(j) .getEastNorth().north(), 424 b.getNode(j+1).getEastNorth().east(), b.getNode(j+1).getEastNorth().north()); 425 if(intersection == null) { 426 continue; 427 } 428 429 // Create the node. Adding them to the ways must be delayed because we still loop over them 430 Node n = new Node(intersection); 431 cmds.add(new AddCommand(n)); 432 nodes.add(n); 433 // The distance is needed to sort and add the nodes in direction of the way 434 nodesA.add(new NodeToSegs(i, n, a.getNode(i).getCoor())); 435 if(same) { 436 nodesA.add(new NodeToSegs(j, n, a.getNode(j).getCoor())); 437 } else { 438 nodesB.add(new NodeToSegs(j, n, b.getNode(j).getCoor())); 439 } 440 } 441 } 442 443 addNodesToWay(a, nodesA); 444 if(!same) { 445 addNodesToWay(b, nodesB); 446 } 447 448 return nodes; 632 633 //avoid duplicate node 634 if (prevNode != representator) { 635 newNodes.add(representator); 636 } 637 } 638 prevNode = node; 639 } 640 641 if (nodesRemoved > 0) { 642 643 if (newNodes.size() == 1) { //all nodes in the same coordinate - add one more node, to have closed way. 644 newNodes.add(newNodes.get(0)); 645 } 646 647 Way newWay=new Way(way); 648 newWay.setNodes(newNodes); 649 cmds.add(new ChangeCommand(way, newWay)); 650 totalNodesRemoved += nodesRemoved; 651 } 652 } 653 654 return totalNodesRemoved > 0; 655 } 656 657 658 659 /** 660 * Will find all intersection and add nodes there for list of given ways. Handles self-intersections too. 661 * And make commands to add the intersection points to ways. 662 * @param List<Way> - a list of ways to test 663 * @return ArrayList<Node> List of new nodes 664 * Prerequisite: no two nodes have the same coordinates. 665 */ 666 private ArrayList<Node> addIntersections(List<Way> ways, boolean test) { 667 //TODO: this is a bit slow - O( (number of nodes)^2 + numberOfIntersections * numberOfNodes ) 668 669 //stupid java, cannot instantiate array of generic classes.. 670 @SuppressWarnings("unchecked") 671 ArrayList<Node>[] newNodes = new ArrayList[ways.size()]; 672 boolean[] changedWays = new boolean[ways.size()]; 673 674 Set<Node> intersectionNodes = new LinkedHashSet<Node>(); 675 676 for (int pos = 0; pos < ways.size(); pos ++) { 677 newNodes[pos] = new ArrayList<Node>(ways.get(pos).getNodes()); 678 changedWays[pos] = false; 679 } 680 681 //iterate over all segment pairs and introduce the intersections 682 683 Comparator<Node> coordsComparator = new NodePositionComparator(); 684 685 int seg1Way = 0; 686 int seg1Pos = -1; 687 688 while (true) { 689 //advance to next segment 690 seg1Pos++; 691 if (seg1Pos > newNodes[seg1Way].size() - 2) { 692 seg1Way++; 693 seg1Pos = 0; 694 695 if (seg1Way == ways.size()) { //finished 696 break; 697 } 698 } 699 700 701 //iterate over secondary segment 702 703 int seg2Way = seg1Way; 704 int seg2Pos = seg1Pos + 1;//skip the adjacent segment 705 706 while (true) { 707 708 //advance to next segment 709 seg2Pos++; 710 if (seg2Pos > newNodes[seg2Way].size() - 2) { 711 seg2Way++; 712 seg2Pos = 0; 713 714 if (seg2Way == ways.size()) { //finished 715 break; 716 } 717 } 718 719 //need to get them again every time, because other segments may be changed 720 Node seg1Node1 = newNodes[seg1Way].get(seg1Pos); 721 Node seg1Node2 = newNodes[seg1Way].get(seg1Pos + 1); 722 Node seg2Node1 = newNodes[seg2Way].get(seg2Pos); 723 Node seg2Node2 = newNodes[seg2Way].get(seg2Pos + 1); 724 725 int commonCount = 0; 726 //test if we have common nodes to add. 727 if (seg1Node1 == seg2Node1 || seg1Node1 == seg2Node2) { 728 commonCount ++; 729 730 if (seg1Way == seg2Way && 731 seg1Pos == 0 && 732 seg2Pos == newNodes[seg2Way].size() -2) { 733 //do not add - this is first and last segment of the same way. 734 } else { 735 intersectionNodes.add(seg1Node1); 736 } 737 } 738 739 if (seg1Node2 == seg2Node1 || seg1Node2 == seg2Node2) { 740 commonCount ++; 741 742 intersectionNodes.add(seg1Node2); 743 } 744 745 //no common nodes - find intersection 746 if (commonCount == 0) { 747 LatLon intersection = getLineLineIntersection( 748 seg1Node1.getEastNorth().east(), seg1Node1.getEastNorth().north(), 749 seg1Node2.getEastNorth().east(), seg1Node2.getEastNorth().north(), 750 seg2Node1.getEastNorth().east(), seg2Node1.getEastNorth().north(), 751 seg2Node2.getEastNorth().east(), seg2Node2.getEastNorth().north()); 752 753 if (intersection != null) { 754 if (test) { 755 intersectionNodes.add(seg2Node1); 756 return new ArrayList<Node>(intersectionNodes); 757 } 758 759 Node newNode = new Node(intersection); 760 Node intNode = newNode; 761 boolean insertInSeg1 = false; 762 boolean insertInSeg2 = false; 763 764 //find if the intersection point is at end point of one of the segments, if so use that point 765 766 //segment 1 767 if (coordsComparator.compare(newNode, seg1Node1) == 0) { 768 intNode = seg1Node1; 769 } else if (coordsComparator.compare(newNode, seg1Node2) == 0) { 770 intNode = seg1Node2; 771 } else { 772 insertInSeg1 = true; 773 } 774 775 //segment 2 776 if (coordsComparator.compare(newNode, seg2Node1) == 0) { 777 intNode = seg2Node1; 778 } else if (coordsComparator.compare(newNode, seg2Node2) == 0) { 779 intNode = seg2Node2; 780 } else { 781 insertInSeg2 = true; 782 } 783 784 if (insertInSeg1) { 785 newNodes[seg1Way].add(seg1Pos +1, intNode); 786 changedWays[seg1Way] = true; 787 788 //fix seg2 position, as indexes have changed, seg2Pos is always bigger than seg1Pos on the same segment. 789 if (seg2Way == seg1Way) { 790 seg2Pos ++; 791 } 792 } 793 794 if (insertInSeg2) { 795 newNodes[seg2Way].add(seg2Pos +1, intNode); 796 changedWays[seg2Way] = true; 797 798 //Do not need to compare again to already split segment 799 seg2Pos ++; 800 } 801 802 intersectionNodes.add(intNode); 803 804 if (intNode == newNode) { 805 cmds.add(new AddCommand(intNode)); 806 } 807 } 808 } 809 else if (test && intersectionNodes.size() > 0) 810 return new ArrayList<Node>(intersectionNodes); 811 } 812 } 813 814 for (int pos = 0; pos < ways.size(); pos ++) { 815 if (changedWays[pos] == false) { 816 continue; 817 } 818 819 Way way = ways.get(pos); 820 Way newWay = new Way(way); 821 newWay.setNodes(newNodes[pos]); 822 823 cmds.add(new ChangeCommand(way, newWay)); 824 } 825 826 return new ArrayList<Node>(intersectionNodes); 449 827 } 450 828 … … 470 848 // Solve the equations 471 849 double det = a1*b2 - a2*b1; 472 if (det == 0) return null; // Lines are parallel850 if (det == 0) return null; // Lines are parallel 473 851 474 852 return Main.proj.eastNorth2latlon(new EastNorth( … … 478 856 } 479 857 480 /** 481 * Inserts given nodes with positions into the given ways 482 * @param Way The way to insert the nodes into 483 * @param Collection<NodeToSegs> The list of nodes with positions to insert 484 */ 485 private void addNodesToWay(Way a, ArrayList<NodeToSegs> nodes) { 486 if(nodes.size() == 0) 487 return; 488 Way ax=new Way(a); 489 Collections.sort(nodes); 490 491 int numOfAdds = 1; 492 for(NodeToSegs n : nodes) { 493 ax.addNode(n.pos + numOfAdds, n.n); 494 numOfAdds++; 495 } 496 497 cmds.add(new ChangeCommand(a, ax)); 498 } 858 499 859 500 860 /** … … 519 879 } 520 880 521 /** 522 * Removes a given OsmPrimitive from all relations 523 * @param OsmPrimitive Element to remove from all relations 524 * @return ArrayList<RelationRole> List of relations with roles the primitives was part of 525 */ 526 private ArrayList<RelationRole> removeFromRelations(OsmPrimitive osm) { 527 ArrayList<RelationRole> result = new ArrayList<RelationRole>(); 528 for (Relation r : Main.main.getCurrentDataSet().getRelations()) { 529 if (r.isDeleted()) { 530 continue; 531 } 532 for (RelationMember rm : r.getMembers()) { 533 if (rm.getMember() != osm) { 881 882 /** 883 * This method analyzes the way and assigns each part what direction polygon "inside" is. 884 * @param parts the split parts of the way 885 * @param isInner - if true, reverts the direction (for multipolygon islands) 886 * @return list of parts, marked with the inside orientation. 887 */ 888 private ArrayList<WayInPolygon> markWayInsideSide(List<Way> parts, boolean isInner) { 889 890 ArrayList<WayInPolygon> result = new ArrayList<WayInPolygon>(); 891 892 //prepare prev and next maps 893 Map<Way, Way> nextWayMap = new HashMap<Way, Way>(); 894 Map<Way, Way> prevWayMap = new HashMap<Way, Way>(); 895 896 for (int pos = 0; pos < parts.size(); pos ++) { 897 898 if (!parts.get(pos).lastNode().equals(parts.get((pos + 1) % parts.size()).firstNode())) 899 throw new RuntimeException("Way not circular"); 900 901 nextWayMap.put(parts.get(pos), parts.get((pos + 1) % parts.size())); 902 prevWayMap.put(parts.get(pos), parts.get((pos + parts.size() - 1) % parts.size())); 903 } 904 905 //find the node with minimum y - it's guaranteed to be outer. (What about the south pole?) 906 Way topWay = null; 907 Node topNode = null; 908 int topIndex = 0; 909 double minY = Double.POSITIVE_INFINITY; 910 911 for (Way way : parts) { 912 for (int pos = 0; pos < way.getNodesCount(); pos ++) { 913 Node node = way.getNode(pos); 914 915 if (node.getEastNorth().getY() < minY) { 916 minY = node.getEastNorth().getY(); 917 topWay = way; 918 topNode = node; 919 topIndex = pos; 920 } 921 } 922 } 923 924 //get the upper way and it's orientation. 925 926 boolean wayClockwise; // orientation of the top way. 927 928 if (topNode.equals(topWay.firstNode()) || topNode.equals(topWay.lastNode())) { 929 Node headNode = null; // the node at junction 930 Node prevNode = null; // last node from previous path 931 wayClockwise = false; 932 933 //node is in split point - find the outermost way from this point 934 935 headNode = topNode; 936 //make a fake node that is downwards from head node (smaller Y). It will be a division point between paths. 937 prevNode = new Node(new EastNorth(headNode.getEastNorth().getX(), headNode.getEastNorth().getY() - 1e5)); 938 939 topWay = null; 940 wayClockwise = false; 941 Node bestWayNextNode = null; 942 943 for (Way way : parts) { 944 if (way.firstNode().equals(headNode)) { 945 Node nextNode = way.getNode(1); 946 947 if (topWay == null || !isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode)) { 948 //the new way is better 949 topWay = way; 950 wayClockwise = true; 951 bestWayNextNode = nextNode; 952 } 953 } 954 955 if (way.lastNode().equals(headNode)) { 956 //end adjacent to headNode 957 Node nextNode = way.getNode(way.getNodesCount() - 2); 958 959 if (topWay == null || !isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode)) { 960 //the new way is better 961 topWay = way; 962 wayClockwise = false; 963 bestWayNextNode = nextNode; 964 } 965 } 966 } 967 } else { 968 //node is inside way - pick the clockwise going end. 969 Node prev = topWay.getNode(topIndex - 1); 970 Node next = topWay.getNode(topIndex + 1); 971 972 //there will be no parallel segments in the middle of way, so all fine. 973 wayClockwise = angleIsClockwise(prev, topNode, next); 974 } 975 976 Way curWay = topWay; 977 boolean curWayInsideToTheRight = wayClockwise ^ isInner; 978 979 //iterate till full circle is reached 980 while (true) { 981 982 //add cur way 983 WayInPolygon resultWay = new WayInPolygon(curWay, curWayInsideToTheRight); 984 result.add(resultWay); 985 986 //process next way 987 Way nextWay = nextWayMap.get(curWay); 988 Node prevNode = curWay.getNode(curWay.getNodesCount() - 2); 989 Node headNode = curWay.lastNode(); 990 Node nextNode = nextWay.getNode(1); 991 992 if (nextWay == topWay) { 993 //full loop traversed - all done. 994 break; 995 } 996 997 998 //find intersecting segments 999 // the intersections will look like this: 1000 // 1001 // ^ 1002 // | 1003 // X wayBNode 1004 // | 1005 // wayB | 1006 // | 1007 // curWay | nextWay 1008 //----X----------------->X----------------------X----> 1009 // prevNode ^headNode nextNode 1010 // | 1011 // | 1012 // wayA | 1013 // | 1014 // X wayANode 1015 // | 1016 1017 int intersectionCount = 0; 1018 1019 for (Way wayA : parts) { 1020 1021 if (wayA == curWay) { 534 1022 continue; 535 1023 } 536 1024 537 Relation newRel = new Relation(r); 538 List<RelationMember> members = newRel.getMembers(); 539 members.remove(rm); 540 newRel.setMembers(members); 541 542 cmds.add(new ChangeCommand(r, newRel)); 543 RelationRole saverel = new RelationRole(r, rm.getRole()); 544 if(!result.contains(saverel)) { 545 result.add(saverel); 546 } 547 break; 548 } 549 } 550 551 commitCommands(marktr("Removed Element from Relations")); 1025 if (wayA.lastNode().equals(headNode)) { 1026 1027 Way wayB = nextWayMap.get(wayA); 1028 1029 //test if wayA is opposite wayB relative to curWay and nextWay 1030 1031 Node wayANode = wayA.getNode(wayA.getNodesCount() - 2); 1032 Node wayBNode = wayB.getNode(1); 1033 1034 boolean wayAToTheRight = isToTheRightSideOfLine(prevNode, headNode, nextNode, wayANode); 1035 boolean wayBToTheRight = isToTheRightSideOfLine(prevNode, headNode, nextNode, wayBNode); 1036 1037 if (wayAToTheRight != wayBToTheRight) { 1038 intersectionCount ++; 1039 } 1040 } 1041 } 1042 1043 //if odd number of crossings, invert orientation 1044 if (intersectionCount % 2 == 1) { 1045 curWayInsideToTheRight = !curWayInsideToTheRight; 1046 } 1047 1048 curWay = nextWay; 1049 } 1050 552 1051 return result; 553 1052 } 554 1053 555 1054 /** 556 * This method splits waysinto smaller parts, using the prepared nodes list as split points.557 * Uses SplitWayAction.splitWay for the heavy lifting.1055 * This is a method splits way into smaller parts, using the prepared nodes list as split points. 1056 * Uses SplitWayAction.splitWay for the heavy lifting. 558 1057 * @return list of split ways (or original ways if no splitting is done). 559 1058 */ 560 private ArrayList<Way> splitWay sOnNodes(Way a, Way b, Collection<Node> nodes) {1059 private ArrayList<Way> splitWayOnNodes(Way way, Collection<Node> nodes) { 561 1060 562 1061 ArrayList<Way> result = new ArrayList<Way>(); 563 List<Way> ways = new ArrayList<Way>(); 564 ways.add(a); 565 ways.add(b); 566 567 for (Way way: ways) { 568 List<List<Node>> chunks = buildNodeChunks(way, nodes); 1062 List<List<Node>> chunks = buildNodeChunks(way, nodes); 1063 1064 if (chunks.size() > 1) { 569 1065 SplitWayResult split = SplitWayAction.splitWay(Main.map.mapView.getEditLayer(), way, chunks, Collections.<OsmPrimitive>emptyList()); 570 1066 571 1067 //execute the command, we need the results 572 Main.main.undoRedo.add(split.getCommand());573 c mdsCount ++;1068 cmds.add(split.getCommand()); 1069 commitCommands(marktr("Split ways into fragments")); 574 1070 575 1071 result.add(split.getOriginalWay()); 576 1072 result.addAll(split.getNewWays()); 1073 } else { 1074 //nothing to split 1075 result.add(way); 577 1076 } 578 1077 579 1078 return result; 580 1079 } 1080 581 1081 582 1082 /** … … 584 1084 * @param way the way to chunk 585 1085 * @param splitNodes the places where to cut. 586 * @return list of node segments to produce. 587 */ 588 private List<List<Node>> buildNodeChunks(Way way, Collection<Node> splitNodes) 589 { 1086 * @return list of node paths to produce. 1087 */ 1088 private List<List<Node>> buildNodeChunks(Way way, Collection<Node> splitNodes) { 590 1089 List<List<Node>> result = new ArrayList<List<Node>>(); 591 1090 List<Node> curList = new ArrayList<Node>(); 592 1091 593 for (Node node: way.getNodes()){1092 for (Node node : way.getNodes()) { 594 1093 curList.add(node); 595 if (curList.size() > 1 && splitNodes.contains(node)) {1094 if (curList.size() > 1 && splitNodes.contains(node)) { 596 1095 result.add(curList); 597 1096 curList = new ArrayList<Node>(); … … 600 1099 } 601 1100 602 if (curList.size() > 1) 603 { 1101 if (curList.size() > 1) { 604 1102 result.add(curList); 605 1103 } … … 608 1106 } 609 1107 610 /** 611 * Returns all nodes for given ways 612 * @param Collection<Way> The list of ways which nodes are to be returned 613 * @return Collection<Node> The list of nodes the ways contain 614 */ 615 private Collection<Node> getNodesFromWays(Collection<Way> ways) { 616 Collection<Node> allNodes = new ArrayList<Node>(); 617 for(Way w: ways) { 618 allNodes.addAll(w.getNodes()); 619 } 620 return allNodes; 621 } 622 623 /** 624 * Gets all inner ways given all ways and outer ways. 625 * @param multigonWays 626 * @param outerWays 627 * @return list of inner ways. 628 */ 629 private ArrayList<Way> findInnerWays(Collection<Way> multigonWays, Collection<WayInPath> outerWays) { 630 ArrayList<Way> innerWays = new ArrayList<Way>(); 631 Set<Way> outerSet = new HashSet<Way>(); 632 633 for(WayInPath w: outerWays) { 634 outerSet.add(w.way); 635 } 636 637 for(Way way: multigonWays) { 638 if (!outerSet.contains(way)) { 639 innerWays.add(way); 640 } 641 } 642 643 return innerWays; 644 } 645 646 647 /** 648 * Finds all ways for a given list of Ways that form the outer hull. 649 * This works by starting with one node and traversing the multigon clockwise, always picking the leftmost path. 650 * Prerequisites - the ways must not intersect and have common end nodes where they meet. 651 * @param Collection<Way> A list of (splitted) ways that form a multigon 652 * @return Collection<Way> A list of ways that form the outer boundary of the multigon. 653 */ 654 private static ArrayList<WayInPath> findOuterWays(Collection<Way> multigonWays) { 655 656 //find the node with minimum lat - it's guaranteed to be outer. (What about the south pole?) 657 Way bestWay = null; 658 Node topNode = null; 659 int topIndex = 0; 660 double minLat = Double.POSITIVE_INFINITY; 661 662 for(Way way: multigonWays) { 663 for (int pos = 0; pos < way.getNodesCount(); pos ++) { 664 Node node = way.getNode(pos); 665 666 if (node.getCoor().lat() < minLat) { 667 minLat = node.getCoor().lat(); 668 bestWay = way; 669 topNode = node; 670 topIndex = pos; 671 } 672 } 673 } 674 675 //get two final nodes from best way to mark as starting point and orientation. 676 Node headNode = null; 677 Node prevNode = null; 678 679 if (topNode.equals(bestWay.firstNode()) || topNode.equals(bestWay.lastNode())) { 680 //node is in split point 681 headNode = topNode; 682 //make a fake node that is downwards from head node (smaller latitude). It will be a division point between paths. 683 prevNode = new Node(new LatLon(headNode.getCoor().lat() - 1000, headNode.getCoor().lon())); 684 } else { 685 //node is inside way - pick the clockwise going end. 686 Node prev = bestWay.getNode(topIndex - 1); 687 Node next = bestWay.getNode(topIndex + 1); 688 689 if (angleIsClockwise(prev, topNode, next)) { 690 headNode = bestWay.lastNode(); 691 prevNode = bestWay.getNode(bestWay.getNodesCount() - 2); 692 } 693 else { 694 headNode = bestWay.firstNode(); 695 prevNode = bestWay.getNode(1); 696 } 697 } 698 699 Set<Way> outerWays = new HashSet<Way>(); 700 ArrayList<WayInPath> result = new ArrayList<WayInPath>(); 701 702 //iterate till full circle is reached 703 while (true) { 704 705 bestWay = null; 706 Node bestWayNextNode = null; 707 boolean bestWayReverse = false; 708 709 for (Way way: multigonWays) { 710 boolean wayReverse; 711 Node nextNode; 712 713 if (way.firstNode().equals(headNode)) { 714 //start adjacent to headNode 715 nextNode = way.getNode(1); 716 wayReverse = false; 717 718 if (nextNode.equals(prevNode)) { 719 //this is the path we came from - ignore it. 720 } else if (bestWay == null || !isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode)) { 721 //the new way is better 722 bestWay = way; 723 bestWayReverse = wayReverse; 724 bestWayNextNode = nextNode; 1108 1109 /** 1110 * This method finds witch ways are outer and witch are inner. 1111 * @param boundaryWays 1112 * @return 1113 */ 1114 private List<AssembledMultipolygon> findPolygons(Collection<AssembledPolygon> boundaries) { 1115 1116 List<PolygonLevel> list = findOuterWaysImpl(0, boundaries); 1117 List<AssembledMultipolygon> result = new ArrayList<AssembledMultipolygon>(); 1118 1119 //take every other level 1120 for (PolygonLevel pol : list) { 1121 if (pol.level % 2 == 0) { 1122 result.add(pol.pol); 1123 } 1124 } 1125 1126 return result; 1127 } 1128 1129 /** 1130 * Collects outer way and corresponding inner ways from all boundaries. 1131 * @param boundaryWays 1132 * @return the outermostWay. 1133 */ 1134 private List<PolygonLevel> findOuterWaysImpl(int level, Collection<AssembledPolygon> boundaryWays) { 1135 1136 //TODO: bad performance for deep nestings... 1137 List<PolygonLevel> result = new ArrayList<PolygonLevel>(); 1138 1139 for (AssembledPolygon outerWay : boundaryWays) { 1140 1141 boolean outerGood = true; 1142 List<AssembledPolygon> innerCandidates = new ArrayList<AssembledPolygon>(); 1143 1144 for (AssembledPolygon innerWay : boundaryWays) { 1145 if (innerWay == outerWay) { 1146 continue; 1147 } 1148 1149 if (wayInsideWay(outerWay, innerWay)) { 1150 outerGood = false; 1151 break; 1152 } else if (wayInsideWay(innerWay, outerWay)) { 1153 innerCandidates.add(innerWay); 1154 } 1155 } 1156 1157 if (!outerGood) { 1158 continue; 1159 } 1160 1161 //add new outer polygon 1162 AssembledMultipolygon pol = new AssembledMultipolygon(outerWay); 1163 PolygonLevel polLev = new PolygonLevel(pol, level); 1164 1165 //process inner ways 1166 if (innerCandidates.size() > 0) { 1167 List<PolygonLevel> innerList = findOuterWaysImpl(level + 1, innerCandidates); 1168 result.addAll(innerList); 1169 1170 for (PolygonLevel pl : innerList) { 1171 if (pl.level == level + 1) { 1172 pol.innerWays.add(pl.pol.outerWay); 725 1173 } 726 1174 } 727 728 if (way.lastNode().equals(headNode)) { 729 //end adjacent to headNode 730 nextNode = way.getNode(way.getNodesCount() - 2); 731 wayReverse = true; 732 733 if (nextNode.equals(prevNode)) { 734 //this is the path we came from - ignore it. 735 } else if (bestWay == null || !isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode)) { 736 //the new way is better 737 bestWay = way; 738 bestWayReverse = wayReverse; 739 bestWayNextNode = nextNode; 740 } 741 } 742 } 743 744 if (bestWay == null) 745 throw new RuntimeException(); 746 else if (outerWays.contains(bestWay)) { 747 break; //full circle reached, terminate. 1175 } 1176 1177 result.add(polLev); 1178 } 1179 1180 return result; 1181 } 1182 1183 1184 1185 /** 1186 * Finds all ways that form inner or outer boundaries. 1187 * @param Collection<Way> A list of (splitted) ways that form a multigon and share common end nodes on intersections. 1188 * @param Collection<Way> this list is filled with ways that are to be discarded 1189 * @return Collection<Collection<Way>> A list of ways that form the outer and inner boundaries of the multigon. 1190 */ 1191 public static List<AssembledPolygon> findBoundaryPolygons(Collection<WayInPolygon> multigonWays, List<Way> discardedResult) { 1192 //first find all discardable ways, by getting outer shells. 1193 //this will produce incorrect boundaries in some cases, but second pass will fix it. 1194 1195 List<WayInPolygon> discardedWays = new ArrayList<WayInPolygon>(); 1196 Set<WayInPolygon> processedWays = new HashSet<WayInPolygon>(); 1197 WayTraverser traverser = new WayTraverser(multigonWays); 1198 1199 for (WayInPolygon startWay : multigonWays) { 1200 if (processedWays.contains(startWay)) { 1201 continue; 1202 } 1203 1204 traverser.startNewWay(startWay); 1205 1206 List<WayInPolygon> boundary = new ArrayList<WayInPolygon>(); 1207 WayInPolygon lastWay = startWay; 1208 1209 while (true) { 1210 boundary.add(lastWay); 1211 1212 WayInPolygon bestWay = traverser.advanceNextLeftmostWay(); 1213 boolean wayInsideToTheRight = bestWay == null ? false : traverser.isLastWayInsideToTheRight(); 1214 1215 if (bestWay == null || processedWays.contains(bestWay) || !wayInsideToTheRight) { 1216 //bad segment chain - proceed to discard it 1217 lastWay = null; 1218 break; 1219 } else if (boundary.contains(bestWay)) { 1220 //traversed way found - close the way 1221 lastWay = bestWay; 1222 break; 1223 } else { 1224 //proceed to next segment 1225 lastWay = bestWay; 1226 } 1227 } 1228 1229 if (lastWay != null) { 1230 //way good 1231 processedWays.addAll(boundary); 1232 1233 //remove junk segments at the start 1234 while (boundary.get(0) != lastWay) { 1235 discardedWays.add(boundary.get(0)); 1236 boundary.remove(0); 1237 } 748 1238 } else { 749 //add to outer ways, repeat. 750 outerWays.add(bestWay); 751 result.add(new WayInPath(bestWay, bestWayReverse)); 752 headNode = bestWayReverse ? bestWay.firstNode() : bestWay.lastNode(); 753 prevNode = bestWayReverse ? bestWay.getNode(1) : bestWay.getNode(bestWay.getNodesCount() - 2); 754 } 755 } 1239 //way bad 1240 discardedWays.addAll(boundary); 1241 processedWays.addAll(boundary); 1242 } 1243 } 1244 1245 //now we have removed junk segments, collect the real result ways 1246 1247 traverser.removeWays(discardedWays); 1248 1249 List<AssembledPolygon> result = new ArrayList<AssembledPolygon>(); 1250 1251 while (traverser.hasWays()) { 1252 1253 WayInPolygon startWay = traverser.startNewWay(); 1254 List<WayInPolygon> boundary = new ArrayList<WayInPolygon>(); 1255 WayInPolygon curWay = startWay; 1256 1257 do { 1258 boundary.add(curWay); 1259 curWay = traverser.advanceNextRightmostWay(); 1260 1261 //should not happen 1262 if (curWay == null || !traverser.isLastWayInsideToTheRight()) 1263 throw new RuntimeException("Join areas internal error."); 1264 1265 } while (curWay != startWay); 1266 1267 //build result 1268 traverser.removeWays(boundary); 1269 result.add(new AssembledPolygon(boundary)); 1270 } 1271 1272 for (WayInPolygon way : discardedWays) { 1273 discardedResult.add(way.way); 1274 } 1275 1276 //split inner polygons that have several touching parts. 1277 result = fixTouchingPolygons(result); 756 1278 757 1279 return result; 758 1280 } 1281 1282 1283 /** 1284 * This method checks if polygons have several touching parts and splits them in several polygons. 1285 * @param polygon the polygon to process. 1286 */ 1287 public static List<AssembledPolygon> fixTouchingPolygons(List<AssembledPolygon> polygons) 1288 { 1289 List<AssembledPolygon> newPolygons = new ArrayList<AssembledPolygon>(); 1290 1291 for (AssembledPolygon innerPart : polygons) { 1292 WayTraverser traverser = new WayTraverser(innerPart.ways); 1293 1294 while (traverser.hasWays()) { 1295 1296 WayInPolygon startWay = traverser.startNewWay(); 1297 List<WayInPolygon> boundary = new ArrayList<WayInPolygon>(); 1298 WayInPolygon curWay = startWay; 1299 1300 Node startNode = traverser.getLastWayStartNode(); 1301 boundary.add(curWay); 1302 1303 while (startNode != traverser.getLastWayEndNode()) { 1304 curWay = traverser.advanceNextLeftmostWay(); 1305 boundary.add(curWay); 1306 1307 //should not happen 1308 if (curWay == null || !traverser.isLastWayInsideToTheRight()) 1309 throw new RuntimeException("Join areas internal error."); 1310 } 1311 1312 //build result 1313 traverser.removeWays(boundary); 1314 newPolygons.add(new AssembledPolygon(boundary)); 1315 } 1316 } 1317 1318 return newPolygons; 1319 } 1320 759 1321 760 1322 /** … … 766 1328 * @return true if to the right side, false otherwise 767 1329 */ 768 public static boolean isToTheRightSideOfLine(Node lineP1, Node lineP2, Node lineP3, Node testPoint) 769 { 1330 public static boolean isToTheRightSideOfLine(Node lineP1, Node lineP2, Node lineP3, Node testPoint) { 770 1331 boolean pathBendToRight = angleIsClockwise(lineP1, lineP2, lineP3); 771 1332 boolean rightOfSeg1 = angleIsClockwise(lineP1, lineP2, testPoint); … … 785 1346 * @return true if first vector is clockwise before second vector. 786 1347 */ 787 public static boolean angleIsClockwise(Node commonNode, Node firstNode, Node secondNode) 788 { 789 double dla1 = (firstNode.getCoor().lat() - commonNode.getCoor().lat()); 790 double dla2 = (secondNode.getCoor().lat() - commonNode.getCoor().lat()); 791 double dlo1 = (firstNode.getCoor().lon() - commonNode.getCoor().lon()); 792 double dlo2 = (secondNode.getCoor().lon() - commonNode.getCoor().lon()); 793 794 return dla1 * dlo2 - dlo1 * dla2 > 0; 1348 1349 public static boolean angleIsClockwise(Node commonNode, Node firstNode, Node secondNode) { 1350 double dy1 = (firstNode.getEastNorth().getY() - commonNode.getEastNorth().getY()); 1351 double dy2 = (secondNode.getEastNorth().getY() - commonNode.getEastNorth().getY()); 1352 double dx1 = (firstNode.getEastNorth().getX() - commonNode.getEastNorth().getX()); 1353 double dx2 = (secondNode.getEastNorth().getX() - commonNode.getEastNorth().getX()); 1354 1355 return dy1 * dx2 - dx1 * dy2 > 0; 1356 } 1357 1358 1359 /** 1360 * Tests if way is inside other way 1361 * @param outside 1362 * @param inside 1363 * @return 1364 */ 1365 public static boolean wayInsideWay(AssembledPolygon inside, AssembledPolygon outside) { 1366 Set<Node> outsideNodes = new HashSet<Node>(outside.getNodes()); 1367 1368 for (Node insideNode : inside.getNodes()) { 1369 1370 if (!outsideNodes.contains(insideNode)) 1371 //simply test the one node 1372 return nodeInsidePolygon(insideNode, outside.getNodes()); 1373 } 1374 1375 //all nodes shared. 1376 return false; 795 1377 } 796 1378 … … 802 1384 * FIXME: this should probably be moved to tools.. 803 1385 */ 804 public static boolean nodeInsidePolygon(ArrayList<Node> polygonNodes, Node point) 805 { 1386 public static boolean nodeInsidePolygon(Node point, List<Node> polygonNodes) { 806 1387 if (polygonNodes.size() < 3) 807 1388 return false; … … 813 1394 Node oldPoint = polygonNodes.get(polygonNodes.size() - 1); 814 1395 815 for(Node newPoint: polygonNodes) 816 { 1396 for (Node newPoint : polygonNodes) { 817 1397 //skip duplicate points 818 1398 if (newPoint.equals(oldPoint)) { … … 821 1401 822 1402 //order points so p1.lat <= p2.lat; 823 if (newPoint.getCoor().lat() > oldPoint.getCoor().lat()) 824 { 1403 if (newPoint.getEastNorth().getY() > oldPoint.getEastNorth().getY()) { 825 1404 p1 = oldPoint; 826 1405 p2 = newPoint; 827 } 828 else 829 { 1406 } else { 830 1407 p1 = newPoint; 831 1408 p2 = oldPoint; … … 833 1410 834 1411 //test if the line is crossed and if so invert the inside flag. 835 if ((newPoint.get Coor().lat() < point.getCoor().lat()) == (point.getCoor().lat() <= oldPoint.getCoor().lat())836 && (point.get Coor().lon() - p1.getCoor().lon()) * (p2.getCoor().lat() - p1.getCoor().lat())837 < (p2.get Coor().lon() - p1.getCoor().lon()) * (point.getCoor().lat() - p1.getCoor().lat()))1412 if ((newPoint.getEastNorth().getY() < point.getEastNorth().getY()) == (point.getEastNorth().getY() <= oldPoint.getEastNorth().getY()) 1413 && (point.getEastNorth().getX() - p1.getEastNorth().getX()) * (p2.getEastNorth().getY() - p1.getEastNorth().getY()) 1414 < (p2.getEastNorth().getX() - p1.getEastNorth().getX()) * (point.getEastNorth().getY() - p1.getEastNorth().getY())) 838 1415 { 839 1416 inside = !inside; … … 845 1422 return inside; 846 1423 } 1424 1425 1426 /** 1427 * Joins the lists of ways. 1428 * @param Collection<Way> The list of outer ways that belong to that multigon. 1429 * @return Way The newly created outer way 1430 */ 1431 private Multipolygon joinPolygon(AssembledMultipolygon polygon) throws UserCancelException { 1432 Multipolygon result = new Multipolygon(joinWays(polygon.outerWay.ways)); 1433 1434 for (AssembledPolygon pol : polygon.innerWays) { 1435 result.innerWays.add(joinWays(pol.ways)); 1436 } 1437 1438 return result; 1439 } 1440 847 1441 848 1442 /** … … 851 1445 * @return Way The newly created outer way 852 1446 */ 853 private Way join OuterWays(ArrayList<WayInPath> outerWays){1447 private Way joinWays(List<WayInPolygon> ways) throws UserCancelException { 854 1448 855 1449 //leave original orientation, if all paths are reverse. 856 1450 boolean allReverse = true; 857 for (WayInPath way: outerWays) {858 allReverse &= way.insideToTheLeft;1451 for (WayInPolygon way : ways) { 1452 allReverse &= !way.insideToTheRight; 859 1453 } 860 1454 861 1455 if (allReverse) { 862 for(WayInPath way: outerWays){ 863 way.insideToTheLeft = !way.insideToTheLeft; 864 } 865 } 866 867 commitCommands(marktr("Join Areas: Remove Short Ways")); 868 Way joinedWay = joinOrientedWays(outerWays); 869 if (joinedWay != null) 870 return closeWay(joinedWay); 871 else 872 return null; 873 } 874 875 /** 876 * Ensures a way is closed. If it isn't, last and first node are connected. 877 * @param Way the way to ensure it's closed 878 * @return Way The joined way. 879 */ 880 private Way closeWay(Way w) { 881 if(w.isClosed()) 882 return w; 883 Main.main.getCurrentDataSet().setSelected(w); 884 Way wnew = new Way(w); 885 wnew.addNode(wnew.firstNode()); 886 cmds.add(new ChangeCommand(w, wnew)); 887 commitCommands(marktr("Closed Way")); 888 return (Way)(Main.main.getCurrentDataSet().getSelectedWays().toArray())[0]; 889 } 1456 for (WayInPolygon way : ways) { 1457 way.insideToTheRight = !way.insideToTheRight; 1458 } 1459 } 1460 1461 Way joinedWay = joinOrientedWays(ways); 1462 1463 //should not happen 1464 if (joinedWay == null || !joinedWay.isClosed()) 1465 throw new RuntimeException("Join areas internal error."); 1466 1467 return joinedWay; 1468 } 1469 890 1470 891 1471 /** … … 894 1474 * @return Way The newly created way 895 1475 */ 896 private Way joinOrientedWays( ArrayList<WayInPath> ways){897 if (ways.size() < 2)1476 private Way joinOrientedWays(List<WayInPolygon> ways) throws UserCancelException{ 1477 if (ways.size() < 2) 898 1478 return ways.get(0).way; 899 1479 … … 901 1481 // the user about this. 902 1482 1483 //TODO: ReverseWay and Combine way are really slow and we use them a lot here. This slows down large joins. 903 1484 List<Way> actionWays = new ArrayList<Way>(ways.size()); 904 1485 905 for (WayInPathway : ways) {1486 for (WayInPolygon way : ways) { 906 1487 actionWays.add(way.way); 907 1488 908 if ( way.insideToTheLeft) {909 Main.main.getCurrentDataSet().setSelected(way.way);910 new ReverseWayAction().actionPerformed(null);1489 if (!way.insideToTheRight) { 1490 ReverseWayResult res = ReverseWayAction.reverseWay(way.way); 1491 Main.main.undoRedo.add(res.getReverseCommand()); 911 1492 cmdsCount++; 912 1493 } 913 1494 } 914 1495 915 Way result = new CombineWayAction().combineWays(actionWays); 916 917 if(result != null) { 918 cmdsCount++; 919 } 1496 Pair<Way, Command> result = CombineWayAction.combineWaysWorker(actionWays); 1497 1498 Main.main.undoRedo.add(result.b); 1499 cmdsCount ++; 1500 1501 return result.a; 1502 } 1503 1504 1505 /** 1506 * This method analyzes multipolygon relationships of given ways and collects addition inner ways to consider. 1507 * @param selectedWays the selected ways 1508 * @return list of polygons, or null if too complex relation encountered. 1509 */ 1510 private List<Multipolygon> collectMultipolygons(List<Way> selectedWays) { 1511 1512 List<Multipolygon> result = new ArrayList<Multipolygon>(); 1513 1514 //prepare the lists, to minimize memory allocation. 1515 List<Way> outerWays = new ArrayList<Way>(); 1516 List<Way> innerWays = new ArrayList<Way>(); 1517 1518 Set<Way> processedOuterWays = new LinkedHashSet<Way>(); 1519 Set<Way> processedInnerWays = new LinkedHashSet<Way>(); 1520 1521 for (Relation r : CombineWayAction.getParentRelations(selectedWays)) { 1522 if (r.isDeleted() || 1523 r.get("type") == null || 1524 !r.get("type").equalsIgnoreCase("multipolygon")) { 1525 continue; 1526 } 1527 1528 boolean hasKnownOuter = false; 1529 outerWays.clear(); 1530 innerWays.clear(); 1531 1532 for (RelationMember rm : r.getMembers()) { 1533 if (rm.getRole().equalsIgnoreCase("outer")) { 1534 outerWays.add(rm.getWay()); 1535 hasKnownOuter |= selectedWays.contains(rm.getWay()); 1536 } 1537 else if (rm.getRole().equalsIgnoreCase("inner")) { 1538 innerWays.add(rm.getWay()); 1539 } 1540 } 1541 1542 if (!hasKnownOuter) { 1543 continue; 1544 } 1545 1546 if (outerWays.size() > 1) { 1547 JOptionPane.showMessageDialog(Main.parent, tr("Sorry. Cannot handle multipolygon relations with multiple outer ways.")); 1548 return null; 1549 } 1550 1551 Way outerWay = outerWays.get(0); 1552 1553 //retain only selected inner ways 1554 innerWays.retainAll(selectedWays); 1555 1556 if (processedOuterWays.contains(outerWay)) { 1557 JOptionPane.showMessageDialog(Main.parent, tr("Sorry. Cannot handle way that is outer in multiple multipolygon relations.")); 1558 return null; 1559 } 1560 1561 if (processedInnerWays.contains(outerWay)) { 1562 JOptionPane.showMessageDialog(Main.parent, tr("Sorry. Cannot handle way that is both inner and outer in multipolygon relations.")); 1563 return null; 1564 } 1565 1566 for (Way way :innerWays) 1567 { 1568 if (processedOuterWays.contains(way)) { 1569 JOptionPane.showMessageDialog(Main.parent, tr("Sorry. Cannot handle way that is both inner and outer in multipolygon relations.")); 1570 return null; 1571 } 1572 1573 if (processedInnerWays.contains(way)) { 1574 JOptionPane.showMessageDialog(Main.parent, tr("Sorry. Cannot handle way that is inner in multiple multipolygon relations.")); 1575 return null; 1576 } 1577 } 1578 1579 processedOuterWays.add(outerWay); 1580 processedInnerWays.addAll(innerWays); 1581 1582 Multipolygon pol = new Multipolygon(outerWay); 1583 pol.innerWays.addAll(innerWays); 1584 pol.relation = r; 1585 1586 result.add(pol); 1587 } 1588 1589 //add remaining ways, not in relations 1590 for (Way way : selectedWays) { 1591 if (processedOuterWays.contains(way) || processedInnerWays.contains(way)) { 1592 continue; 1593 } 1594 1595 result.add(new Multipolygon(way)); 1596 } 1597 920 1598 return result; 921 1599 } 922 1600 923 /** 924 * Joins a list of ways (using CombineWayAction and ReverseWayAction if necessary to quiet the former) 925 * @param ArrayList<Way> The list of ways to join 926 * @return Way The newly created way 927 */ 928 private Way joinWays(ArrayList<Way> ways) { 929 if(ways.size() < 2) 930 return ways.get(0); 931 932 // This will turn ways so all of them point in the same direction and CombineAction won't bug 933 // the user about this. 934 Way a = null; 935 for (Way b : ways) { 936 if(a == null) { 937 a = b; 938 continue; 939 } 940 if(a.getNode(0).equals(b.getNode(0)) || 941 a.getNode(a.getNodesCount()-1).equals(b.getNode(b.getNodesCount()-1))) { 942 Main.main.getCurrentDataSet().setSelected(b); 943 new ReverseWayAction().actionPerformed(null); 944 cmdsCount++; 945 } 946 a = b; 947 } 948 if ((a = new CombineWayAction().combineWays(ways)) != null) { 949 cmdsCount++; 950 } 951 return a; 952 } 953 954 /** 955 * Finds all ways that may be part of a multipolygon relation and removes them from the given list. 956 * It will automatically combine "good" ways 957 * @param Collection<Way> The list of inner ways to check 958 * @param Way The newly created outer way 959 * @return ArrayList<Way> The List of newly created inner ways 960 */ 961 private ArrayList<Way> fixMultipolygons(Collection<Way> uninterestingWays, Way outerWay, boolean selfintersect) { 962 Collection<Node> innerNodes = getNodesFromWays(uninterestingWays); 963 Collection<Node> outerNodes = outerWay.getNodes(); 964 965 // The newly created inner ways. uninterestingWays is passed by reference and therefore modified in-place 966 ArrayList<Way> newInnerWays = new ArrayList<Way>(); 967 968 // Now we need to find all inner ways that contain a remaining node, but no outer nodes 969 // Remaining nodes are those that contain to more than one way. All nodes that belong to an 970 // inner multigon part will have at least two ways, so we can use this to find which ways do 971 // belong to the multigon. 972 ArrayList<Way> possibleWays = new ArrayList<Way>(); 973 wayIterator: for(Way w : uninterestingWays) { 974 boolean hasInnerNodes = false; 975 for(Node n : w.getNodes()) { 976 if(outerNodes.contains(n)) { 977 if(!selfintersect) { // allow outer point for self intersection 978 continue wayIterator; 979 } 980 } 981 else if(!hasInnerNodes && innerNodes.contains(n)) { 982 hasInnerNodes = true; 983 } 984 } 985 if(!hasInnerNodes || w.getNodesCount() < 2) { 986 continue; 987 } 988 possibleWays.add(w); 989 } 990 991 // This removes unnecessary ways that might have been added. 992 removeAlmostAlikeWays(possibleWays); 993 994 // loop twice 995 // in k == 0 prefer ways which allow no Y-joining (i.e. which have only 1 solution) 996 for(int k = 0; k < 2; ++k) 997 { 998 // Join all ways that have one start/ending node in common 999 Way joined = null; 1000 outerIterator: do { 1001 removePartlyUnconnectedWays(possibleWays); 1002 joined = null; 1003 for(Way w1 : possibleWays) { 1004 if(w1.isClosed()) { 1005 if(!wayIsCollapsed(w1)) { 1006 uninterestingWays.remove(w1); 1007 newInnerWays.add(w1); 1008 } 1009 joined = w1; 1010 possibleWays.remove(w1); 1011 continue outerIterator; 1012 } 1013 ArrayList<Way> secondary = new ArrayList<Way>(); 1014 for(Way w2 : possibleWays) { 1015 int i = 0; 1016 // w2 cannot be closed, otherwise it would have been removed above 1017 if(w1.equals(w2)) { 1018 continue; 1019 } 1020 if(w2.isFirstLastNode(w1.firstNode())) { 1021 ++i; 1022 } 1023 if(w2.isFirstLastNode(w1.lastNode())) { 1024 ++i; 1025 } 1026 if(i == 2) // this way closes w1 - take it! 1027 { 1028 if(secondary.size() > 0) { 1029 secondary.clear(); 1030 } 1031 secondary.add(w2); 1032 break; 1033 } 1034 else if(i > 0) { 1035 secondary.add(w2); 1036 } 1037 } 1038 if(k == 0 ? secondary.size() == 1 : secondary.size() > 0) 1039 { 1040 ArrayList<Way> joinThem = new ArrayList<Way>(); 1041 joinThem.add(w1); 1042 joinThem.add(secondary.get(0)); 1043 // Although we joined the ways, we cannot simply assume that they are closed 1044 if((joined = joinWays(joinThem)) != null) 1045 { 1046 uninterestingWays.removeAll(joinThem); 1047 possibleWays.removeAll(joinThem); 1048 1049 //List<Node> nodes = joined.getNodes(); 1050 // check if we added too much 1051 /*for(int i = 1; i < nodes.size()-2; ++i) 1052 { 1053 if(nodes.get(i) == nodes.get(nodes.size()-1)) 1054 System.out.println("Joining of ways produced unexpecteded result\n"); 1055 }*/ 1056 uninterestingWays.add(joined); 1057 possibleWays.add(joined); 1058 continue outerIterator; 1059 } 1060 } 1061 } 1062 } while(joined != null); 1063 } 1064 return newInnerWays; 1065 } 1066 1067 /** 1068 * Removes almost alike ways (= ways that are on top of each other for all nodes) 1069 * @param ArrayList<Way> the ways to remove almost-duplicates from 1070 */ 1071 private void removeAlmostAlikeWays(ArrayList<Way> ways) { 1072 Collection<Way> removables = new ArrayList<Way>(); 1073 outer: for(int i=0; i < ways.size(); i++) { 1074 Way a = ways.get(i); 1075 for(int j=i+1; j < ways.size(); j++) { 1076 Way b = ways.get(j); 1077 List<Node> revNodes = new ArrayList<Node>(b.getNodes()); 1078 Collections.reverse(revNodes); 1079 if(a.getNodes().equals(b.getNodes()) || a.getNodes().equals(revNodes)) { 1080 removables.add(a); 1081 continue outer; 1082 } 1083 } 1084 } 1085 ways.removeAll(removables); 1086 } 1087 1088 /** 1089 * Removes ways from the given list whose starting or ending node doesn't 1090 * connect to other ways from the same list (it's like removing spikes). 1091 * @param ArrayList<Way> The list of ways to remove "spikes" from 1092 */ 1093 private void removePartlyUnconnectedWays(ArrayList<Way> ways) { 1094 List<Way> removables = new ArrayList<Way>(); 1095 for(Way a : ways) { 1096 if(a.isClosed()) { 1097 continue; 1098 } 1099 boolean connectedStart = false; 1100 boolean connectedEnd = false; 1101 for(Way b : ways) { 1102 if(a.equals(b)) { 1103 continue; 1104 } 1105 if(b.isFirstLastNode(a.firstNode())) { 1106 connectedStart = true; 1107 } 1108 if(b.isFirstLastNode(a.lastNode())) { 1109 connectedEnd = true; 1110 } 1111 } 1112 if(!connectedStart || !connectedEnd) { 1113 removables.add(a); 1114 } 1115 } 1116 ways.removeAll(removables); 1117 } 1118 1119 /** 1120 * Checks if a way is collapsed (i.e. looks like <---->) 1121 * @param Way A *closed* way to check if it is collapsed 1122 * @return boolean If the closed way is collapsed or not 1123 */ 1124 private boolean wayIsCollapsed(Way w) { 1125 if(w.getNodesCount() <= 3) return true; 1126 1127 // If a way contains more than one node twice, it must be collapsed (only start/end node may be the same) 1128 Way x = new Way(w); 1129 int count = 0; 1130 for(Node n : w.getNodes()) { 1131 x.removeNode(n); 1132 if(x.containsNode(n)) { 1133 count++; 1134 } 1135 if(count == 2) return true; 1136 } 1137 return false; 1138 } 1601 1602 /** 1603 * This method filters the list of relations that form the multipolygons. 1604 * @param relations 1605 * @param polygons 1606 * @return 1607 */ 1608 private List<Relation> filterOwnMultipolygonRelations(Collection<Relation> relations, List<Multipolygon> polygons) { 1609 1610 List<Relation> relationsToRemove = new ArrayList<Relation>(); 1611 1612 for (Multipolygon m : polygons) { 1613 if (m.relation != null) { 1614 relationsToRemove.add(m.relation); 1615 } 1616 } 1617 1618 List<Relation> result = new ArrayList<Relation>(); 1619 1620 result.addAll(relations); 1621 result.removeAll(relationsToRemove); 1622 return result; 1623 } 1624 1139 1625 1140 1626 /** … … 1145 1631 */ 1146 1632 private void addOwnMultigonRelation(Collection<Way> inner, Way outer, ArrayList<RelationRole> rels) { 1147 if (inner.size() == 0) return;1633 if (inner.size() == 0) return; 1148 1634 // Create new multipolygon relation and add all inner ways to it 1149 1635 Relation newRel = new Relation(); 1150 1636 newRel.put("type", "multipolygon"); 1151 for (Way w : inner) {1637 for (Way w : inner) { 1152 1638 newRel.addMember(new RelationMember("inner", w)); 1153 1639 } … … 1161 1647 } 1162 1648 1649 1650 /** 1651 * Removes a given OsmPrimitive from all relations 1652 * @param OsmPrimitive Element to remove from all relations 1653 * @return ArrayList<RelationRole> List of relations with roles the primitives was part of 1654 */ 1655 private ArrayList<RelationRole> removeFromAllRelations(OsmPrimitive osm) { 1656 ArrayList<RelationRole> result = new ArrayList<RelationRole>(); 1657 1658 for (Relation r : Main.main.getCurrentDataSet().getRelations()) { 1659 if (r.isDeleted()) { 1660 continue; 1661 } 1662 for (RelationMember rm : r.getMembers()) { 1663 if (rm.getMember() != osm) { 1664 continue; 1665 } 1666 1667 Relation newRel = new Relation(r); 1668 List<RelationMember> members = newRel.getMembers(); 1669 members.remove(rm); 1670 newRel.setMembers(members); 1671 1672 cmds.add(new ChangeCommand(r, newRel)); 1673 RelationRole saverel = new RelationRole(r, rm.getRole()); 1674 if (!result.contains(saverel)) { 1675 result.add(saverel); 1676 } 1677 break; 1678 } 1679 } 1680 1681 commitCommands(marktr("Removed Element from Relations")); 1682 return result; 1683 } 1684 1685 1163 1686 /** 1164 1687 * Adds the previously removed relations again to the outer way. If there are multiple multipolygon … … 1170 1693 private void fixRelations(ArrayList<RelationRole> rels, Way outer) { 1171 1694 ArrayList<RelationRole> multiouters = new ArrayList<RelationRole>(); 1172 for (RelationRole r : rels) {1173 if (r.rel.get("type") != null &&1695 for (RelationRole r : rels) { 1696 if (r.rel.get("type") != null && 1174 1697 r.rel.get("type").equalsIgnoreCase("multipolygon") && 1175 1698 r.role.equalsIgnoreCase("outer") … … 1185 1708 1186 1709 Relation newRel = null; 1187 switch (multiouters.size()) {1710 switch (multiouters.size()) { 1188 1711 case 0: 1189 1712 return; … … 1197 1720 // Create a new relation with all previous members and (Way)outer as outer. 1198 1721 newRel = new Relation(); 1199 for (RelationRole r : multiouters) {1722 for (RelationRole r : multiouters) { 1200 1723 // Add members 1201 for (RelationMember rm : r.rel.getMembers())1202 if (!newRel.getMembers().contains(rm)) {1724 for (RelationMember rm : r.rel.getMembers()) 1725 if (!newRel.getMembers().contains(rm)) { 1203 1726 newRel.addMember(rm); 1204 1727 } … … 1219 1742 */ 1220 1743 private void stripTags(Collection<Way> ways) { 1221 for (Way w: ways) {1744 for (Way w : ways) { 1222 1745 stripTags(w); 1223 1746 } … … 1229 1752 */ 1230 1753 private void stripTags(Way x) { 1231 if(x.getKeys() == null) return; 1754 if (x.getKeys() == null) 1755 return; 1232 1756 Way y = new Way(x); 1233 1757 for (String key : x.keySet()) { … … 1246 1770 cmds.clear(); 1247 1771 int i = Math.max(ur.commands.size() - cmdsCount, 0); 1248 for (; i < ur.commands.size(); i++) {1772 for (; i < ur.commands.size(); i++) { 1249 1773 cmds.add(ur.commands.get(i)); 1250 1774 } 1251 1775 1252 for (i = 0; i < cmds.size(); i++) {1776 for (i = 0; i < cmds.size(); i++) { 1253 1777 ur.undo(); 1254 1778 }
Note:
See TracChangeset
for help on using the changeset viewer.