590 | | final Way[] joinArray = waysToJoin.toArray(new Way[0]); |
591 | | int left = waysToJoin.size(); |
592 | | while (left > 0) { |
593 | | Way w = null; |
594 | | boolean selected = false; |
595 | | List<Node> nodes = null; |
596 | | Set<Long> wayIds = new HashSet<>(); |
597 | | boolean joined = true; |
598 | | while (joined && left > 0) { |
599 | | joined = false; |
600 | | for (int i = 0; i < joinArray.length && left != 0; ++i) { |
601 | | if (joinArray[i] != null) { |
602 | | Way c = joinArray[i]; |
603 | | if (c.getNodesCount() == 0) { |
604 | | continue; |
605 | | } |
606 | | if (w == null) { |
607 | | w = c; |
608 | | selected = w.isSelected(); |
609 | | joinArray[i] = null; |
610 | | --left; |
611 | | } else { |
612 | | int mode = 0; |
613 | | int cl = c.getNodesCount()-1; |
614 | | int nl; |
615 | | if (nodes == null) { |
616 | | nl = w.getNodesCount()-1; |
617 | | if (w.getNode(nl) == c.getNode(0)) { |
618 | | mode = 21; |
619 | | } else if (w.getNode(nl) == c.getNode(cl)) { |
620 | | mode = 22; |
621 | | } else if (w.getNode(0) == c.getNode(0)) { |
622 | | mode = 11; |
623 | | } else if (w.getNode(0) == c.getNode(cl)) { |
624 | | mode = 12; |
625 | | } |
626 | | } else { |
627 | | nl = nodes.size()-1; |
628 | | if (nodes.get(nl) == c.getNode(0)) { |
629 | | mode = 21; |
630 | | } else if (nodes.get(0) == c.getNode(cl)) { |
631 | | mode = 12; |
632 | | } else if (nodes.get(0) == c.getNode(0)) { |
633 | | mode = 11; |
634 | | } else if (nodes.get(nl) == c.getNode(cl)) { |
635 | | mode = 22; |
636 | | } |
637 | | } |
638 | | if (mode != 0) { |
639 | | joinArray[i] = null; |
640 | | joined = true; |
641 | | if (c.isSelected()) { |
642 | | selected = true; |
643 | | } |
644 | | --left; |
645 | | if (nodes == null) { |
646 | | nodes = new ArrayList<>(w.getNodes()); |
647 | | wayIds.add(w.getUniqueId()); |
648 | | } |
649 | | if (mode == 21) { |
650 | | nodes.addAll(c.getNodes().subList(1, cl + 1)); |
651 | | } else if (mode == 12) { |
652 | | nodes.addAll(0, c.getNodes().subList(0, cl)); |
653 | | } else { |
654 | | ArrayList<Node> reversed = new ArrayList<>(c.getNodes()); |
655 | | Collections.reverse(reversed); |
656 | | if (mode == 22) { |
657 | | nodes.addAll(reversed.subList(1, cl + 1)); |
658 | | } else /* mode == 11 */ { |
659 | | nodes.addAll(0, reversed.subList(0, cl)); |
660 | | } |
661 | | } |
662 | | wayIds.add(c.getUniqueId()); |
663 | | } |
664 | | } |
665 | | } |
| 593 | if (waysToJoin.isEmpty()) |
| 594 | return result; |
| 595 | |
| 596 | LinkedList<Way> todo = new LinkedList<>(); |
| 597 | |
| 598 | for (Way w : waysToJoin) { |
| 599 | if (w.getNodesCount() > 0) { |
| 600 | if (w.isClosed()) { |
| 601 | result.add(new JoinedWay(w.getNodes(), Collections.singleton(w.getUniqueId()), w.isSelected())); |
| 602 | } else { |
| 603 | todo.add(w); |
669 | | if (nodes == null && w != null) { |
670 | | nodes = w.getNodes(); |
671 | | wayIds.add(w.getUniqueId()); |
| 608 | Map<Node, List<Way>> endPoints = new HashMap<>(); |
| 609 | for (Way w : todo) { |
| 610 | endPoints.computeIfAbsent(w.firstNode(), k -> new LinkedList<>()).add(w); |
| 611 | endPoints.computeIfAbsent(w.lastNode(), k -> new LinkedList<>()).add(w); |
| 612 | } |
| 613 | Way curr = null; |
| 614 | List<Node> nodes = new ArrayList<>(); |
| 615 | Set<Long> wayIds = new HashSet<>(); |
| 616 | boolean selected = false; |
| 617 | while (!todo.isEmpty()) { |
| 618 | if (curr == null) { |
| 619 | curr = todo.remove(); |
| 620 | endPoints.get(curr.firstNode()).remove(curr); |
| 621 | endPoints.get(curr.lastNode()).remove(curr); |
| 622 | nodes.clear(); |
| 623 | nodes.addAll(curr.getNodes()); |
| 624 | selected = curr.isSelected(); |
| 625 | wayIds.clear(); |
| 626 | wayIds.add(curr.getUniqueId()); |
| 628 | List<Way> candidates = endPoints.getOrDefault(nodes.get(nodes.size() - 1), Collections.emptyList()); |
| 629 | if (!candidates.isEmpty()) { |
| 630 | Way best = candidates.iterator().next(); |
| 631 | if (candidates.size() > 1) { |
| 632 | // See #18861: find best candidate to continue when multiple open ways share one point |
| 633 | Node prevNode = nodes.get(nodes.size() - 2); |
| 634 | Node headNode = nodes.get(nodes.size() - 1); |
| 635 | double headAngle = Math.atan2(headNode.getEastNorth().east() - prevNode.getEastNorth().east(), |
| 636 | headNode.getEastNorth().north() - prevNode.getEastNorth().north()); |
| 637 | double bestAngle = Double.NEGATIVE_INFINITY; |
| 638 | for (Way candidate : candidates) { |
| 639 | Node nextNode = (headNode == candidate.firstNode()) ? candidate.getNode(1) |
| 640 | : candidate.getNode(candidate.getNodesCount() - 2); |
| 641 | double angle; |
| 642 | if (nextNode == prevNode) |
| 643 | angle = -Math.PI; // avoid to create a spike |
| 644 | else |
| 645 | angle = Math.atan2(nextNode.getEastNorth().east() - headNode.getEastNorth().east(), |
| 646 | nextNode.getEastNorth().north() - headNode.getEastNorth().north()) - headAngle; |
| 647 | if (angle >= Math.PI) |
| 648 | angle -= 2 * Math.PI; |
| 649 | if (angle < -Math.PI) |
| 650 | angle += 2 * Math.PI; |
674 | | if (nodes != null) { |
675 | | result.add(new JoinedWay(nodes, wayIds, selected)); |
| 652 | // Now we have a valid candidate way, is it better than the previous one ? |
| 653 | if (angle > bestAngle) { |
| 654 | //the new way is better, |
| 655 | best = candidate; |
| 656 | bestAngle = angle; |
| 657 | } |
| 658 | } |
| 659 | } |
| 660 | todo.remove(best); |
| 661 | endPoints.get(best.firstNode()).remove(best); |
| 662 | endPoints.get(best.lastNode()).remove(best); |
| 663 | List<Node> toAdd = best.getNodes(); |
| 664 | if (nodes.get(nodes.size() - 1) != best.firstNode()) { |
| 665 | Collections.reverse(toAdd); |
| 666 | } |
| 667 | nodes.addAll(toAdd.subList(1, toAdd.size())); |
| 668 | wayIds.add(best.getUniqueId()); |
| 669 | selected |= best.isSelected(); |
| 670 | if (nodes.get(0) == nodes.get(nodes.size()-1)) { |
| 671 | result.add(new JoinedWay(nodes, wayIds, selected)); |
| 672 | curr = null; |
| 673 | } |
| 674 | continue; |