Ticket #5179: osm-join-areas-6.patch
File osm-join-areas-6.patch, 29.1 KB (added by , 15 years ago) |
---|
-
src/org/openstreetmap/josm/actions/CombineWayAction.java
14 14 import java.util.Collections; 15 15 import java.util.HashMap; 16 16 import java.util.HashSet; 17 import java.util.LinkedHashMap; 18 import java.util.LinkedHashSet; 17 19 import java.util.LinkedList; 18 20 import java.util.List; 19 21 import java.util.Set; … … 112 114 if (ways == null || ways.isEmpty()) 113 115 return null; 114 116 ways.remove(null); // just in case - remove all null ways from the collection 115 ways = new HashSet<Way>(ways); // remove duplicates116 117 118 // remove duplicates, preserving order 119 ways = new LinkedHashSet<Way>(ways); 120 117 121 // try to build a new way which includes all the combined 118 122 // ways 119 123 // … … 473 477 } 474 478 475 479 protected void prepare() { 476 Set<NodePair> undirectedEdges = new HashSet<NodePair>();477 successors = new HashMap<Node, List<NodePair>>();478 predecessors = new HashMap<Node, List<NodePair>>();480 Set<NodePair> undirectedEdges = new LinkedHashSet<NodePair>(); 481 successors = new LinkedHashMap<Node, List<NodePair>>(); 482 predecessors = new LinkedHashMap<Node, List<NodePair>>(); 479 483 480 484 for (NodePair pair: edges) { 481 485 if (!undirectedEdges.contains(pair) && ! undirectedEdges.contains(pair.swap())) { … … 488 492 } 489 493 490 494 public NodeGraph() { 491 edges = new HashSet<NodePair>();495 edges = new LinkedHashSet<NodePair>(); 492 496 } 493 497 494 498 public void add(NodePair pair) { … … 513 517 } 514 518 515 519 protected Set<Node> getTerminalNodes() { 516 Set<Node> ret = new HashSet<Node>();520 Set<Node> ret = new LinkedHashSet<Node>(); 517 521 for (Node n: getNodes()) { 518 522 if (isTerminalNode(n)) { 519 523 ret.add(n); … … 523 527 } 524 528 525 529 protected Set<Node> getNodes(Stack<NodePair> pairs) { 526 HashSet<Node> nodes = new HashSet<Node>();530 HashSet<Node> nodes = new LinkedHashSet<Node>(); 527 531 for (NodePair pair: pairs) { 528 532 nodes.add(pair.getA()); 529 533 nodes.add(pair.getB()); … … 543 547 } 544 548 545 549 protected Set<Node> getNodes() { 546 Set<Node> nodes = new HashSet<Node>();550 Set<Node> nodes = new LinkedHashSet<Node>(); 547 551 for (NodePair pair: edges) { 548 552 nodes.add(pair.getA()); 549 553 nodes.add(pair.getB()); -
src/org/openstreetmap/josm/actions/JoinAreasAction.java
6 6 import static org.openstreetmap.josm.tools.I18n.trn; 7 7 8 8 import java.awt.GridBagLayout; 9 import java.awt.Polygon;10 9 import java.awt.event.ActionEvent; 11 10 import java.awt.event.KeyEvent; 12 11 import java.awt.geom.Area; … … 15 14 import java.util.Collection; 16 15 import java.util.Collections; 17 16 import java.util.HashMap; 17 import java.util.HashSet; 18 18 import java.util.LinkedList; 19 19 import java.util.List; 20 20 import java.util.Map; … … 30 30 import javax.swing.JPanel; 31 31 32 32 import org.openstreetmap.josm.Main; 33 import org.openstreetmap.josm.actions.SplitWayAction.SplitWayResult; 33 34 import org.openstreetmap.josm.command.AddCommand; 34 35 import org.openstreetmap.josm.command.ChangeCommand; 35 36 import org.openstreetmap.josm.command.Command; … … 54 55 private LinkedList<Command> cmds = new LinkedList<Command>(); 55 56 private int cmdsCount = 0; 56 57 58 59 /** 60 * This helper class describes join ares action result. 61 * @author viesturs 62 * 63 */ 64 public static class JoinAreasResult{ 65 66 public Way outerWay; 67 public List<Way> innerWays; 68 69 public boolean mergeSuccessful; 70 public boolean hasChanges; 71 public boolean hasRelationProblems; 72 } 73 57 74 // HelperClass 58 75 // Saves a node and two positions where to insert the node into the ways 59 76 private static class NodeToSegs implements Comparable<NodeToSegs> { … … 109 126 } 110 127 } 111 128 129 //HelperClass 130 //saves a way and the "inside" side 131 // insideToTheLeft: if true left side is "in", false -right side is "in". Left and right are determined along the orientation of way. 132 private static class WayInPath{ 133 public final Way way; 134 public boolean insideToTheLeft; 135 136 public WayInPath(Way _way, boolean _insideLeft){ 137 this.way = _way; 138 this.insideToTheLeft = _insideLeft; 139 } 140 141 @Override 142 public int hashCode() { 143 return way.hashCode(); 144 } 145 146 @Override 147 public boolean equals(Object other) { 148 if (!(other instanceof WayInPath)) return false; 149 WayInPath otherMember = (WayInPath) other; 150 return otherMember.way.equals(this.way) && otherMember.insideToTheLeft == this.insideToTheLeft; 151 } 152 } 153 112 154 // Adds the menu entry, Shortcuts, etc. 113 155 public JoinAreasAction() { 114 156 super(tr("Join overlapping Areas"), "joinareas", tr("Joins areas that overlap each other"), Shortcut.registerShortcut("tools:joinareas", tr("Tool: {0}", tr("Join overlapping Areas")), … … 163 205 } 164 206 } 165 207 166 if(joinAreas(ways.getFirst(), ways.getLast())) { 208 if(checkForTagConflicts(ways.getFirst(), ways.getLast())) 209 { 210 //do nothing. 211 } 212 213 JoinAreasResult result = joinAreas(ways.getFirst(), ways.getLast()); 214 215 if (result.hasChanges) { 167 216 Main.map.mapView.repaint(); 168 217 DataSet ds = Main.main.getCurrentDataSet(); 169 218 ds.fireSelectionChanged(); … … 178 227 * @param Way Second way/area 179 228 * @return boolean Whether to display the "no operation" message 180 229 */ 181 private boolean joinAreas(Way a, Way b) { 230 private JoinAreasResult joinAreas(Way a, Way b) { 231 232 JoinAreasResult result = new JoinAreasResult(); 233 result.hasChanges = false; 234 182 235 // Fix self-overlapping first or other errors 183 236 boolean same = a.equals(b); 184 boolean hadChanges = false;185 237 if(!same) { 186 238 int i = 0; 187 if(checkForTagConflicts(a, b)) return true; // User aborted, so don't warn again 188 if(joinAreas(a, a)) { 239 240 //join each area with itself, fixing self-crossings. 241 JoinAreasResult resultA = joinAreas(a, a); 242 JoinAreasResult resultB = joinAreas(b, b); 243 244 if (resultA.mergeSuccessful){ 245 a = resultA.outerWay; 189 246 ++i; 190 247 } 191 if(joinAreas(b, b)) { 248 if(resultB.mergeSuccessful) { 249 b = resultB.outerWay; 192 250 ++i; 193 251 } 194 hadChanges = i > 0; 252 253 result.hasChanges = i > 0; 195 254 cmdsCount = i; 196 255 } 197 256 198 ArrayList<OsmPrimitive> nodes = addIntersections(a, b); 199 if(nodes.size() == 0) return hadChanges; 257 ArrayList<Node> nodes = addIntersections(a, b); 258 259 //no intersections, return. 260 if(nodes.size() == 0) return result; 200 261 commitCommands(marktr("Added node on all intersections")); 201 262 202 263 // Remove ways from all relations so ways can be combined/split quietly … … 208 269 // Don't warn now, because it will really look corrupted 209 270 boolean warnAboutRelations = relations.size() > 0; 210 271 211 Collection<Way> allWays = splitWaysOnNodes(a, b, nodes);272 ArrayList<Way> allWays = splitWaysOnNodes(a, b, nodes); 212 273 213 // Find all nodes andinner ways save them to a list214 Collection<Node> allNodes = getNodesFromWays(allWays);215 Collection<Way> innerWays = findInnerWays(allWays, allNodes);274 // Find inner ways save them to a list 275 ArrayList<WayInPath> outerWays = findOuterWays(allWays); 276 ArrayList<Way> innerWays = findInnerWays(allWays, outerWays); 216 277 217 278 // Join outer ways 218 Way outerWay = joinOuterWays(allWays, innerWays); 219 if (outerWay == null) 220 return true; 279 Way outerWay = joinOuterWays(outerWays); 221 280 222 281 // Fix Multipolygons if there are any 223 Collection<Way> newInnerWays = fixMultipolygons(innerWays, outerWay, same);282 List<Way> newInnerWays = fixMultipolygons(innerWays, outerWay, same); 224 283 225 284 // Delete the remaining inner ways 226 285 if(innerWays != null && innerWays.size() > 0) { … … 234 293 commitCommands(marktr("Fix relations")); 235 294 236 295 stripTags(newInnerWays); 296 297 237 298 makeCommitsOneAction( 238 299 same 239 300 ? marktr("Joined self-overlapping area") … … 244 305 JOptionPane.showMessageDialog(Main.parent, tr("Some of the ways were part of relations that have been modified. Please verify no errors have been introduced.")); 245 306 } 246 307 247 return true; 308 result.mergeSuccessful = true; 309 result.outerWay = outerWay; 310 result.innerWays = newInnerWays; 311 312 return result; 248 313 } 249 314 250 315 /** … … 330 395 * @param Way Second way 331 396 * @return ArrayList<OsmPrimitive> List of new nodes 332 397 */ 333 private ArrayList< OsmPrimitive> addIntersections(Way a, Way b) {398 private ArrayList<Node> addIntersections(Way a, Way b) { 334 399 boolean same = a.equals(b); 335 400 int nodesSizeA = a.getNodesCount(); 336 401 int nodesSizeB = b.getNodesCount(); 337 402 338 // We use OsmPrimitive here instead of Node because we later need to split a way at these nodes. 339 // With OsmPrimitve we can simply add the way and don't have to loop over the nodes 340 ArrayList<OsmPrimitive> nodes = new ArrayList<OsmPrimitive>(); 403 ArrayList<Node> nodes = new ArrayList<Node>(); 341 404 ArrayList<NodeToSegs> nodesA = new ArrayList<NodeToSegs>(); 342 405 ArrayList<NodeToSegs> nodesB = new ArrayList<NodeToSegs>(); 343 406 … … 488 551 } 489 552 490 553 /** 491 * This is a hacky implementation to make use of the splitWayAction code and 492 * should be improved. SplitWayAction needs to expose its splitWay function though. 554 * This is a method splits ways into smaller parts, using the prepared nodes list as split points. 555 * Uses SplitWayAction.splitWay for the heavy lifting. 556 * @return list of split ways (or original ways if no splitting is done). 493 557 */ 494 private Collection<Way> splitWaysOnNodes(Way a, Way b, Collection<OsmPrimitive> nodes) { 495 ArrayList<Way> ways = new ArrayList<Way>(); 558 private ArrayList<Way> splitWaysOnNodes(Way a, Way b, Collection<Node> nodes) { 559 560 ArrayList<Way> result = new ArrayList<Way>(); 561 List<Way> ways = new ArrayList<Way>(); 496 562 ways.add(a); 497 if(!a.equals(b)) { 498 ways.add(b); 563 ways.add(b); 564 565 for (Way way: ways){ 566 List<List<Node>> chunks = buildNodeChunks(way, nodes); 567 SplitWayResult split = SplitWayAction.splitWay(Main.map.mapView.getEditLayer(), way, chunks, Collections.<OsmPrimitive>emptyList()); 568 569 //execute the command, we need the results 570 Main.main.undoRedo.add(split.getCommand()); 571 cmdsCount ++; 572 573 result.add(split.getOriginalWay()); 574 result.addAll(split.getNewWays()); 499 575 } 500 576 501 List<OsmPrimitive> affected = new ArrayList<OsmPrimitive>(); 502 for (Way way : ways) { 503 nodes.add(way); 504 Main.main.getCurrentDataSet().setSelected(nodes); 505 nodes.remove(way); 506 new SplitWayAction().actionPerformed(null); 507 cmdsCount++; 508 affected.addAll(Main.main.getCurrentDataSet().getSelectedWays()); 509 } 510 return osmprim2way(affected); 577 return result; 511 578 } 512 579 580 513 581 /** 514 * Converts a list of OsmPrimitives to a list of Ways 515 * @param Collection<OsmPrimitive> The OsmPrimitives list that's needed as a list of Ways 516 * @return Collection<Way> The list as list of Ways 582 * Simple chunking version. Does not care about circular ways and result being proper, we will glue it all back together later on. 583 * @param way the way to chunk 584 * @param splitNodes the places where to cut. 585 * @return list of node segments to produce. 517 586 */ 518 static private Collection<Way> osmprim2way(Collection<OsmPrimitive> ways) { 519 Collection<Way> result = new ArrayList<Way>(); 520 for(OsmPrimitive w: ways) { 521 if(w instanceof Way) { 522 result.add((Way) w); 587 private List<List<Node>> buildNodeChunks(Way way, Collection<Node> splitNodes) 588 { 589 List<List<Node>> result = new ArrayList<List<Node>>(); 590 List<Node> curList = new ArrayList<Node>(); 591 592 for(Node node: way.getNodes()){ 593 curList.add(node); 594 if (curList.size() > 1 && splitNodes.contains(node)){ 595 result.add(curList); 596 curList = new ArrayList<Node>(); 597 curList.add(node); 523 598 } 524 599 } 600 601 if (curList.size() > 1) 602 { 603 result.add(curList); 604 } 605 525 606 return result; 526 607 } 527 608 609 528 610 /** 529 611 * Returns all nodes for given ways 530 612 * @param Collection<Way> The list of ways which nodes are to be returned … … 538 620 return allNodes; 539 621 } 540 622 623 541 624 /** 542 * Finds all inner ways for a given list of Ways and Nodes from a multigon by constructing a polygon 543 * for each way, looking for inner nodes that are not part of this way. If a node is found, all ways 544 * containing this node are added to the list 625 * Gets all inner ways given all ways and outer ways. 626 * @param multigonWays 627 * @param outerWays 628 * @return list of inner ways. 629 */ 630 private ArrayList<Way> findInnerWays(Collection<Way> multigonWays, Collection<WayInPath> outerWays) { 631 ArrayList<Way> innerWays = new ArrayList<Way>(); 632 Set<Way> outerSet = new HashSet<Way>(); 633 634 for(WayInPath w: outerWays) { 635 outerSet.add(w.way); 636 } 637 638 for(Way way: multigonWays) { 639 if (!outerSet.contains(way)) { 640 innerWays.add(way); 641 } 642 } 643 644 return innerWays; 645 } 646 647 648 /** 649 * Finds all ways for a given list of Ways that form the outer hull. 650 * This works by starting with one node and traversing the multigon clockwise, always picking the leftmost path. 651 * Prerequisites - the ways must not intersect and have common end nodes where they meet. 545 652 * @param Collection<Way> A list of (splitted) ways that form a multigon 546 * @param Collection<Node> A list of nodes that belong to the multigon 547 * @return Collection<Way> A list of ways that are positioned inside the outer borders of the multigon 653 * @return Collection<Way> A list of ways that form the outer boundary of the multigon. 548 654 */ 549 private Collection<Way> findInnerWays(Collection<Way> multigonWays, Collection<Node> multigonNodes) { 550 Collection<Way> innerWays = new ArrayList<Way>(); 551 for(Way w: multigonWays) { 552 Polygon poly = new Polygon(); 553 for(Node n: (w).getNodes()) { 554 poly.addPoint(latlonToXY(n.getCoor().lat()), latlonToXY(n.getCoor().lon())); 655 public static ArrayList<WayInPath> findOuterWays(Collection<Way> multigonWays) { 656 657 //find the node with minimum lat - it's guaranteed to be outer. (What about the south pole?) 658 Way bestWay = null; 659 Node topNode = null; 660 int topIndex = 0; 661 double minLat = Double.POSITIVE_INFINITY; 662 663 for(Way way: multigonWays) { 664 for (int pos = 0; pos < way.getNodesCount(); pos ++){ 665 Node node = way.getNode(pos); 666 667 if (node.getCoor().lat() < minLat){ 668 minLat = node.getCoor().lat(); 669 bestWay = way; 670 topNode = node; 671 topIndex = pos; 672 } 555 673 } 674 } 556 675 557 for(Node n: multigonNodes) { 558 if(!(w).containsNode(n) && poly.contains(latlonToXY(n.getCoor().lat()), latlonToXY(n.getCoor().lon()))) { 559 getWaysByNode(innerWays, multigonWays, n); 676 //get two final nodes from best way to mark as starting point and orientation. 677 Node headNode = null; 678 Node prevNode = null; 679 680 if (topNode.equals(bestWay.firstNode()) || topNode.equals(bestWay.lastNode())) 681 { 682 //node is in split point 683 headNode = topNode; 684 //make a fake node that is downwards from head node (smaller latitude). It will be a division point between paths. 685 prevNode = new Node(new LatLon(headNode.getCoor().lat() - 1000, headNode.getCoor().lon())); 686 } 687 else 688 { 689 //node is inside way - pick the clockwise going end. 690 Node prev = bestWay.getNode(topIndex - 1); 691 Node next = bestWay.getNode(topIndex + 1); 692 693 if (angleIsClockwise(prev, topNode, next)){ 694 headNode = bestWay.lastNode(); 695 prevNode = bestWay.getNode(bestWay.getNodesCount() - 2); 696 } 697 else 698 { 699 headNode = bestWay.firstNode(); 700 prevNode = bestWay.getNode(1); 701 } 702 } 703 704 Set<Way> outerWays = new HashSet<Way>(); 705 ArrayList<WayInPath> result = new ArrayList<WayInPath>(); 706 707 //iterate till full circle is reached 708 while (true){ 709 710 bestWay = null; 711 Node bestWayNextNode = null; 712 boolean bestWayReverse = false; 713 714 for (Way way: multigonWays) 715 { 716 boolean wayReverse; 717 Node nextNode; 718 719 if (way.firstNode().equals(headNode)){ 720 //start adjacent to headNode 721 nextNode = way.getNode(1); 722 wayReverse = false; 723 724 if (nextNode.equals(prevNode)) 725 { 726 //this is the path we came from - ignore it. 727 } 728 else if (bestWay == null || !isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode)) 729 { 730 //the new way is better 731 bestWay = way; 732 bestWayReverse = wayReverse; 733 bestWayNextNode = nextNode; 734 } 560 735 } 736 737 if (way.lastNode().equals(headNode)) 738 { 739 //end adjacent to headNode 740 nextNode = way.getNode(way.getNodesCount() - 2); 741 wayReverse = true; 742 743 if (nextNode.equals(prevNode)) 744 { 745 //this is the path we came from - ignore it. 746 } 747 else if (bestWay == null || !isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode)) 748 { 749 //the new way is better 750 bestWay = way; 751 bestWayReverse = wayReverse; 752 bestWayNextNode = nextNode; 753 } 754 } 561 755 } 756 757 if (bestWay == null) 758 //this should not happen. Internal error here. 759 return null; 760 else if (outerWays.contains(bestWay)){ 761 //full circle reached, terminate. 762 break; 763 } 764 else 765 { 766 //add to outer ways, repeat. 767 outerWays.add(bestWay); 768 result.add(new WayInPath(bestWay, bestWayReverse)); 769 headNode = bestWayReverse ? bestWay.firstNode() : bestWay.lastNode(); 770 prevNode = bestWayReverse ? bestWay.getNode(1) : bestWay.getNode(bestWay.getNodesCount() - 2); 771 } 562 772 } 563 773 564 return innerWays;774 return result; 565 775 } 566 776 567 // Polygon only supports int coordinates, so convert them 568 private int latlonToXY(double val) { 569 return (int)Math.round(val*1000000); 777 /** 778 * Tests if given point is to the right side of path consisting of 3 points. 779 * @param lineP1 first point in path 780 * @param lineP2 second point in path 781 * @param lineP3 third point in path 782 * @param testPoint 783 * @return true if to the right side, false otherwise 784 */ 785 public static boolean isToTheRightSideOfLine(Node lineP1, Node lineP2, Node lineP3, Node testPoint) 786 { 787 boolean pathBendToRight = angleIsClockwise(lineP1, lineP2, lineP3); 788 boolean rightOfSeg1 = angleIsClockwise(lineP1, lineP2, testPoint); 789 boolean rightOfSeg2 = angleIsClockwise(lineP2, lineP3, testPoint); 790 791 if (pathBendToRight) 792 return rightOfSeg1 && rightOfSeg2; 793 else 794 return !(!rightOfSeg1 && !rightOfSeg2); 570 795 } 571 796 572 797 /** 573 * Finds all ways that contain the given node. 574 * @param Collection<Way> A list to which matching ways will be added 575 * @param Collection<Way> A list of ways to check 576 * @param Node The node the ways should be checked against 798 * This method tests if secondNode is clockwise to first node. 799 * @param commonNode starting point for both vectors 800 * @param firstNode first vector end node 801 * @param secondNode second vector end node 802 * @return true if first vector is clockwise before second vector. 577 803 */ 578 private void getWaysByNode(Collection<Way> innerWays, Collection<Way> w, Node n) { 579 for(Way way : w) { 580 if(!(way).containsNode(n)) { 804 public static boolean angleIsClockwise(Node commonNode, Node firstNode, Node secondNode) 805 { 806 double dla1 = (firstNode.getCoor().lat() - commonNode.getCoor().lat()); 807 double dla2 = (secondNode.getCoor().lat() - commonNode.getCoor().lat()); 808 double dlo1 = (firstNode.getCoor().lon() - commonNode.getCoor().lon()); 809 double dlo2 = (secondNode.getCoor().lon() - commonNode.getCoor().lon()); 810 811 return dla1 * dlo2 - dlo1 * dla2 > 0; 812 } 813 814 815 /** 816 * Tests if point is inside a polygon. The polygon can be self-intersecting. In such case the contains function works in xor-like manner. 817 * @param polygonNodes list of nodes from polygon path. 818 * @param point the point to test 819 * @return true if the point is inside polygon. 820 * FIXME: this should probably be moved to tools.. 821 */ 822 public static boolean nodeInsidePolygon(ArrayList<Node> polygonNodes, Node point) 823 { 824 if (polygonNodes.size() < 3) 825 return false; 826 827 boolean inside = false; 828 Node p1, p2; 829 830 //iterate each side of the polygon, start with the last segment 831 Node oldPoint = polygonNodes.get(polygonNodes.size() - 1); 832 833 for(Node newPoint: polygonNodes) 834 { 835 //skip duplicate points 836 if (newPoint.equals(oldPoint)) { 581 837 continue; 582 838 } 583 if(!innerWays.contains(way)) { 584 innerWays.add(way); // Will need this later for multigons 839 840 //order points so p1.lat <= p2.lat; 841 if (newPoint.getCoor().lat() > oldPoint.getCoor().lat()) 842 { 843 p1 = oldPoint; 844 p2 = newPoint; 585 845 } 846 else 847 { 848 p1 = newPoint; 849 p2 = oldPoint; 850 } 851 852 //test if the line is crossed and if so invert the inside flag. 853 if ((newPoint.getCoor().lat() < point.getCoor().lat()) == (point.getCoor().lat() <= oldPoint.getCoor().lat()) 854 && (point.getCoor().lon() - p1.getCoor().lon()) * (p2.getCoor().lat() - p1.getCoor().lat()) 855 < (p2.getCoor().lon() - p1.getCoor().lon()) * (point.getCoor().lat() - p1.getCoor().lat())) 856 { 857 inside = !inside; 858 } 859 860 oldPoint = newPoint; 586 861 } 862 863 return inside; 587 864 } 588 865 866 867 868 589 869 /** 590 * Joins the two outer ways and deletes all short ways that can't be part of a multipolygon anyway 591 * @param Collection<OsmPrimitive> The list of all ways that belong to that multigon 592 * @param Collection<Way> The list of inner ways that belong to that multigon 870 * Joins the outer ways and deletes all short ways that can't be part of a multipolygon anyway. 871 * @param Collection<Way> The list of outer ways that belong to that multigon. 593 872 * @return Way The newly created outer way 594 873 */ 595 private Way joinOuterWays(Collection<Way> multigonWays, Collection<Way> innerWays) { 596 ArrayList<Way> join = new ArrayList<Way>(); 597 for(Way w: multigonWays) { 598 // Skip inner ways 599 if(innerWays.contains(w)) { 600 continue; 601 } 874 private Way joinOuterWays(ArrayList<WayInPath> outerWays) { 602 875 603 if(w.getNodesCount() <= 2) { 604 cmds.add(new DeleteCommand(w)); 605 } else { 606 join.add(w); 876 //leave original orientation, if all paths are reverse. 877 boolean allReverse = true; 878 for(WayInPath way: outerWays){ 879 allReverse &= way.insideToTheLeft; 880 } 881 882 if (allReverse){ 883 for(WayInPath way: outerWays){ 884 way.insideToTheLeft = !way.insideToTheLeft; 607 885 } 608 886 } 609 887 888 610 889 commitCommands(marktr("Join Areas: Remove Short Ways")); 611 Way joinedWay = join Ways(join);890 Way joinedWay = joinOrientedWays(outerWays); 612 891 if (joinedWay != null) 613 892 return closeWay(joinedWay); 614 893 else … … 632 911 } 633 912 634 913 /** 914 * Joins a list of ways (using CombineWayAction and ReverseWayAction as specified in WayInPath) 915 * @param ArrayList<Way> The list of ways to join and reverse 916 * @return Way The newly created way 917 */ 918 private Way joinOrientedWays(ArrayList<WayInPath> ways) { 919 if(ways.size() < 2) return ways.get(0).way; 920 921 // This will turn ways so all of them point in the same direction and CombineAction won't bug 922 // the user about this. 923 924 List<Way> actionWays = new ArrayList<Way>(ways.size()); 925 926 for(WayInPath way : ways) { 927 actionWays.add(way.way); 928 929 if (way.insideToTheLeft) 930 { 931 Main.main.getCurrentDataSet().setSelected(way.way); 932 new ReverseWayAction().actionPerformed(null); 933 cmdsCount++; 934 } 935 } 936 937 Way result = new CombineWayAction().combineWays(actionWays); 938 939 if(result != null) { 940 cmdsCount++; 941 } 942 return result; 943 } 944 945 /** 635 946 * Joins a list of ways (using CombineWayAction and ReverseWayAction if necessary to quiet the former) 636 947 * @param ArrayList<Way> The list of ways to join 637 948 * @return Way The newly created way … … 661 972 return a; 662 973 } 663 974 975 664 976 /** 665 977 * Finds all ways that may be part of a multipolygon relation and removes them from the given list. 666 978 * It will automatically combine "good" ways … … 980 1292 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) { 981 1293 setEnabled(selection != null && !selection.isEmpty()); 982 1294 } 983 } 1295 1296 } 1297 No newline at end of file -
test/unit/actions/JoinAreasActionTest.java
1 // License: GPL. For details, see LICENSE file. 2 package actions; 3 4 import org.junit.Assert; 5 import org.junit.Test; 6 import org.openstreetmap.josm.actions.JoinAreasAction; 7 import org.openstreetmap.josm.data.coor.LatLon; 8 import org.openstreetmap.josm.data.osm.Node; 9 10 11 public class JoinAreasActionTest { 12 13 private Node makeNode(double lat, double lon) 14 { 15 Node node = new Node(new LatLon(lat, lon)); 16 return node; 17 } 18 19 @Test 20 public void testAngleIsClockwise() 21 { 22 Assert.assertTrue(JoinAreasAction.angleIsClockwise(makeNode(0,0), makeNode(1,1), makeNode(0,1))); 23 Assert.assertTrue(JoinAreasAction.angleIsClockwise(makeNode(1,1), makeNode(0,1), makeNode(0,0))); 24 Assert.assertTrue(!JoinAreasAction.angleIsClockwise(makeNode(1,1), makeNode(0,1), makeNode(1,0))); 25 } 26 27 @Test 28 public void testisToTheRightSideOfLine() 29 { 30 Assert.assertTrue(JoinAreasAction.isToTheRightSideOfLine(makeNode(0,0), makeNode(1,1), makeNode(0,1), makeNode(0, 0.5))); 31 Assert.assertTrue(!JoinAreasAction.isToTheRightSideOfLine(makeNode(0,0), makeNode(1,1), makeNode(0,1), makeNode(1, 0))); 32 Assert.assertTrue(!JoinAreasAction.isToTheRightSideOfLine(makeNode(1,1), makeNode(0,1), makeNode(1,0), makeNode(0,0))); 33 Assert.assertTrue(JoinAreasAction.isToTheRightSideOfLine(makeNode(1,1), makeNode(0,1), makeNode(1,0), makeNode(2, 0))); 34 } 35 36 }