Changeset 15554 in josm


Ignore:
Timestamp:
2019-12-02T19:47:42+01:00 (4 years ago)
Author:
GerdP
Message:

see #18368: Drastically improve performance of NodeGraph.buildSpanningPath() used in combine way 'C' and parallel ways map mode
In special cases the old code was extremely slow, for example when you try to connect the ways of a complex multipolygon containing inner rings
which always should fail.

  • detect unconnected graph
  • use extra HashSet for check if a NodePair is already in the path to avoid sequential search
  • avoid to test start points which cannot be the start point in a "spanning path"
File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/data/osm/NodeGraph.java

    r12478 r15554  
    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;
     
    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;
     
    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    }
     
    240237    }
    241238
    242     protected boolean isSpanningWay(Stack<NodePair> way) {
     239    protected boolean isSpanningWay(Collection<NodePair> way) {
    243240        return numUndirectedEges == way.size();
    244241    }
     
    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()));
     
    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
     
    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}
Note: See TracChangeset for help on using the changeset viewer.