Ticket #18368: 18368-v3b.patch

File 18368-v3b.patch, 7.4 KB (added by GerdP, 6 years ago)

further improvements: detect unconnected graph, only iterate over those nodes which can be start nodes in a spanning way. This works in fractions of a second for all cases I can think of, even with > 40000 nodes.

  • src/org/openstreetmap/josm/data/osm/NodeGraph.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.data.osm;
    33
     4import java.util.ArrayDeque;
    45import java.util.ArrayList;
    56import java.util.Collection;
    67import java.util.Collections;
     8import java.util.Comparator;
     9import java.util.Deque;
     10import java.util.HashMap;
     11import java.util.HashSet;
    712import java.util.LinkedHashMap;
    813import java.util.LinkedHashSet;
    914import java.util.LinkedList;
    1015import java.util.List;
    1116import java.util.Map;
     17import java.util.Map.Entry;
    1218import java.util.Optional;
    1319import java.util.Set;
    1420import java.util.Stack;
     21import java.util.TreeMap;
    1522
    1623import org.openstreetmap.josm.tools.Pair;
    1724
     
    135142    private final Map<Node, List<NodePair>> predecessors = new LinkedHashMap<>();
    136143
    137144    protected void rememberSuccessor(NodePair pair) {
    138         if (successors.containsKey(pair.getA())) {
    139             if (!successors.get(pair.getA()).contains(pair)) {
    140                 successors.get(pair.getA()).add(pair);
    141             }
    142         } else {
    143             List<NodePair> l = new ArrayList<>();
     145        List<NodePair> l = successors.computeIfAbsent(pair.getA(), k -> new ArrayList<>());
     146        if (!l.contains(pair)) {
    144147            l.add(pair);
    145             successors.put(pair.getA(), l);
    146148        }
    147149    }
    148150
    149151    protected void rememberPredecessors(NodePair pair) {
    150         if (predecessors.containsKey(pair.getB())) {
    151             if (!predecessors.get(pair.getB()).contains(pair)) {
    152                 predecessors.get(pair.getB()).add(pair);
    153             }
    154         } else {
    155             List<NodePair> l = new ArrayList<>();
     152        List<NodePair> l = predecessors.computeIfAbsent(pair.getB(), k -> new ArrayList<>());
     153        if (!l.contains(pair)) {
    156154            l.add(pair);
    157             predecessors.put(pair.getB(), l);
    158155        }
    159156    }
    160157
     
    239236        return nodes;
    240237    }
    241238
    242     protected boolean isSpanningWay(Stack<NodePair> way) {
     239    protected boolean isSpanningWay(Collection<NodePair> way) {
    243240        return numUndirectedEges == way.size();
    244241    }
    245242
     
    262259     */
    263260    protected List<Node> buildSpanningPath(Node startNode) {
    264261        if (startNode != null) {
     262            // do not simply replace `Stack` by `ArrayDeque` because of different iteration behaviour, see #11957
    265263            Stack<NodePair> path = new Stack<>();
     264            Set<NodePair> dupCheck = new HashSet<>();
    266265            Stack<NodePair> nextPairs = new Stack<>();
    267266            nextPairs.addAll(getOutboundPairs(startNode));
    268267            while (!nextPairs.isEmpty()) {
    269268                NodePair cur = nextPairs.pop();
    270                 if (!path.contains(cur) && !path.contains(cur.swap())) {
     269                if (!dupCheck.contains(cur) && !dupCheck.contains(cur.swap())) {
    271270                    while (!path.isEmpty() && !path.peek().isPredecessorOf(cur)) {
    272                         path.pop();
     271                        dupCheck.remove(path.pop());
    273272                    }
    274273                    path.push(cur);
     274                    dupCheck.add(cur);
    275275                    if (isSpanningWay(path)) return buildPathFromNodePairs(path);
    276276                    nextPairs.addAll(getOutboundPairs(path.peek()));
    277277                }
     
    283283    /**
    284284     * Tries to find a path through the graph which visits each edge (i.e.
    285285     * the segment of a way) exactly once.
     286     * <p><b>Note that duplicated edges are removed first!</b>
    286287     *
    287288     * @return the path; null, if no path was found
    288289     */
    289290    public List<Node> buildSpanningPath() {
    290291        prepare();
    291         // try to find a path from each "terminal node", i.e. from a
    292         // node which is connected by exactly one undirected edges (or
    293         // two directed edges in opposite direction) to the graph. A
    294         // graph built up from way segments is likely to include such
    295         // nodes, unless all ways are closed.
    296         // In the worst case this loops over all nodes which is very slow for large ways.
    297         //
    298         Set<Node> nodes = getTerminalNodes();
    299         nodes = nodes.isEmpty() ? getNodes() : nodes;
    300         for (Node n: nodes) {
    301             List<Node> path = buildSpanningPath(n);
    302             if (!path.isEmpty())
    303                 return path;
     292        if(numUndirectedEges > 0 && isConnected()) {
     293            // try to find a path from each "terminal node", i.e. from a
     294            // node which is connected by exactly one undirected edges (or
     295            // two directed edges in opposite direction) to the graph. A
     296            // graph built up from way segments is likely to include such
     297            // nodes, unless the edges build one or more closed rings.
     298            // We order the nodes to start with the best candidates, but
     299            // it might take very long if there is no valid path as we iterate over all nodes
     300            // to find out.
     301            Set<Node> nodes = getTerminalNodes();
     302            nodes = nodes.isEmpty() ? getMostFrequentVisitedNodesFirst() : nodes;
     303            for (Node n : nodes) {
     304                List<Node> path = buildSpanningPath(n);
     305                if (!path.isEmpty())
     306                    return path;
     307            }
    304308        }
    305309        return null;
    306310    }
     311
     312    /**
     313     * Find out if the graph is connected.
     314     * @return true if it is connected.
     315     */
     316    private boolean isConnected() {
     317        Set<Node> nodes = getNodes();
     318        if (nodes.isEmpty())
     319            return false;
     320        Deque<Node> toVisit = new ArrayDeque<>();
     321        HashSet<Node> visited = new HashSet<>();
     322        toVisit.add(nodes.iterator().next());
     323        while(!toVisit.isEmpty()) {
     324            Node n = toVisit.pop();
     325            if (!visited.contains(n)) {
     326                List<NodePair> neighbours = getOutboundPairs(n);
     327                for (NodePair pair : neighbours) {
     328                    toVisit.addLast(pair.getA());
     329                    toVisit.addLast(pair.getB());
     330                }
     331                visited.add(n);
     332            }
     333        }
     334        return nodes.size() == visited.size();
     335    }
     336
     337    /**
     338     * Sort the nodes by number of appearances in the edges.
     339     * @return set of nodes which can be start nodes in a spanning way.
     340     */
     341    private Set<Node> getMostFrequentVisitedNodesFirst() {
     342        if (edges.isEmpty())
     343            return Collections.emptySet();
     344        // count appearance of nodes in edges
     345        Map<Node, Integer> counters = new HashMap<>();
     346        for (NodePair pair : edges) {
     347            Integer c = counters.get(pair.getA());
     348            counters.put(pair.getA(), c == null ? 1 : c + 1);
     349            c = counters.get(pair.getB());
     350            counters.put(pair.getB(), c == null ? 1 : c + 1);
     351        }
     352        // group by counters
     353        TreeMap<Integer, Set<Node>> sortedMap = new TreeMap<>(Comparator.reverseOrder());
     354        for (Entry<Node, Integer> e : counters.entrySet()) {
     355            sortedMap.computeIfAbsent(e.getValue(), LinkedHashSet::new).add(e.getKey());
     356        }
     357        LinkedHashSet<Node> result = new LinkedHashSet<>();
     358        for (Entry<Integer, Set<Node>> e : sortedMap.entrySet()) {
     359            if (e.getKey() > 4 || result.isEmpty()) {
     360                result.addAll(e.getValue());
     361            }
     362        }
     363        return result;
     364    }
     365
    307366}