Ticket #18861: 18861.1.patch

File 18861.1.patch, 9.3 KB (added by GerdP, 6 years ago)
  • src/org/openstreetmap/josm/data/osm/visitor/paint/relations/Multipolygon.java

     
    77import java.util.ArrayList;
    88import java.util.Collection;
    99import java.util.Collections;
     10import java.util.HashMap;
    1011import java.util.HashSet;
    1112import java.util.Iterator;
     13import java.util.LinkedList;
    1214import java.util.List;
     15import java.util.Map;
    1316import java.util.Optional;
    1417import java.util.Set;
    1518
     
    587590     */
    588591    public static Collection<JoinedWay> joinWays(Collection<Way> waysToJoin) {
    589592        final Collection<JoinedWay> result = new ArrayList<>();
    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);
    666604                }
    667605            }
     606        }
    668607
    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());
    672627            }
     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;
    673651
    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;
    676675            }
     676            // if we get here there is an open end
     677            candidates = endPoints.getOrDefault(curr.firstNode(), Collections.emptyList());
     678            if (candidates.isEmpty()) {
     679                result.add(new JoinedWay(curr.getNodes(), Collections.singleton(curr.getUniqueId()), curr.isSelected()));
     680            }
     681            curr = null;
    677682        }
    678683
    679684        return result;