Ticket #22614: 22614_check_eulerian.patch

File 22614_check_eulerian.patch, 9.3 KB (added by GerdP, 3 years ago)
  • src/org/openstreetmap/josm/actions/CombineWayAction.java

     
    232232    }
    233233
    234234    protected static List<Node> tryJoin(Collection<Way> ways) {
    235         List<Node> path = joinWithMultipolygonCode(ways);
     235        List<Node> path;
     236        NodeGraph graph = NodeGraph.createDirectedGraphFromWays(ways);
     237        path = graph.buildSpanningPathNoRemove();
    236238        if (path.isEmpty()) {
    237             NodeGraph graph = NodeGraph.createNearlyUndirectedGraphFromNodeWays(ways);
     239            path = joinWithMultipolygonCode(ways);
     240        }
     241        if (path.isEmpty()) {
     242            graph = NodeGraph.createNearlyUndirectedGraphFromNodeWays(ways);
    238243            path = graph.buildSpanningPathNoRemove();
    239244        }
    240245        return path;
  • src/org/openstreetmap/josm/data/osm/NodeGraph.java

     
    1111import java.util.HashSet;
    1212import java.util.LinkedHashMap;
    1313import java.util.LinkedHashSet;
     14import java.util.LinkedList;
    1415import java.util.List;
    1516import java.util.Map;
    1617import java.util.Map.Entry;
     
    140141    private int numUndirectedEges;
    141142    /** counts the number of edges that were added */
    142143    private int addedEdges;
     144    private LinkedList<Integer>[] adj;
     145    private final List<Node> allNodes = new ArrayList<>();
     146    private final HashMap<Node, Integer> nodeIdMap = new HashMap<>();
    143147    private final Map<Node, List<NodePair>> successors = new LinkedHashMap<>();
    144148    private final Map<Node, List<NodePair>> predecessors = new LinkedHashMap<>();
    145149
     
    182186            rememberPredecessors(pair);
    183187        }
    184188        numUndirectedEges = undirectedEdges.size();
     189
     190        // calculate an index for each node contained in this graph
     191        allNodes.clear();
     192        allNodes.addAll(getNodes());
     193        // calculate the adjen
     194        nodeIdMap.clear();
     195        adj = new LinkedList[allNodes.size()];
     196        for (int i = 0; i < allNodes.size(); i++) {
     197            adj[i] = new LinkedList<>();
     198        }
     199        // map the nodes in this graph to an Integer
     200        for (int i = 0; i < allNodes.size(); i++) {
     201            nodeIdMap.put(allNodes.get(i), i);
     202        }
     203
     204        for (NodePair edge : undirectedEdges) {
     205            int idxA = nodeIdMap.get(edge.getA());
     206            int idxB = nodeIdMap.get(edge.getB());
     207            addEdge(idxA, idxB);
     208        }
    185209    }
    186210
    187211    /**
     
    210234        }
    211235    }
    212236
    213     protected Set<Node> getTerminalNodes() {
    214         return getNodes().stream().filter(this::isTerminalNode).collect(Collectors.toCollection(LinkedHashSet::new));
    215     }
    216 
    217237    private List<NodePair> getConnectedPairs(Node node) {
    218238        List<NodePair> connected = new ArrayList<>();
    219239        connected.addAll(Optional.ofNullable(successors.get(node)).orElseGet(Collections::emptyList));
     
    287307     */
    288308    public List<Node> buildSpanningPath() {
    289309        prepare();
    290         if (numUndirectedEges > 0 && isConnected()) {
    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 the edges build one or more closed rings.
    296             // We order the nodes to start with the best candidates, but
    297             // it might take very long if there is no valid path as we iterate over all nodes
    298             // to find out.
    299             Set<Node> nodes = getTerminalNodes();
    300             nodes = nodes.isEmpty() ? getMostFrequentVisitedNodesFirst() : nodes;
    301             return nodes.stream()
    302                     .map(this::buildSpanningPath)
    303                     .filter(path -> !path.isEmpty())
    304                     .findFirst().orElse(null);
     310        if (numUndirectedEges > 0) {
     311            int indicator = isEulerian();
     312            if (indicator > 0) {
     313                // try to find a path from each "terminal node", i.e. from a
     314                // node which is connected by exactly one undirected edges (or
     315                // two directed edges in opposite direction) to the graph. A
     316                // graph built up from way segments is likely to include such
     317                // nodes, unless the edges build one or more closed rings.
     318                // We order the nodes to start with the best candidates, but
     319                // it might take very long if there is no valid path as we iterate over all nodes
     320                // to find out.
     321                Collection<Node> startNodes = getOddNodes();
     322                startNodes = startNodes.isEmpty() ? getMostFrequentVisitedNodesFirst() : startNodes;
     323                return startNodes.stream()
     324                        .map(this::buildSpanningPath)
     325                        .filter(path -> !path.isEmpty())
     326                        .findFirst().orElse(null);
     327            }
    305328        }
    306329        return null;
    307330    }
     
    327350     * @return true if it is connected.
    328351     */
    329352    private boolean isConnected() {
    330         Set<Node> nodes = getNodes();
    331         if (nodes.isEmpty())
     353        if (allNodes.isEmpty())
    332354            return false;
    333355        Deque<Node> toVisit = new ArrayDeque<>();
    334356        HashSet<Node> visited = new HashSet<>();
    335         toVisit.add(nodes.iterator().next());
     357        toVisit.add(allNodes.iterator().next());
    336358        while (!toVisit.isEmpty()) {
    337359            Node n = toVisit.pop();
    338360            if (!visited.contains(n)) {
     
    345367                visited.add(n);
    346368            }
    347369        }
    348         return nodes.size() == visited.size();
     370        return allNodes.size() == visited.size();
    349371    }
    350372
    351373    /**
     
    377399        return Collections.unmodifiableSet(result);
    378400    }
    379401
     402    private void addEdge(int v, int w) {
     403        //Function to add an edge into the graph
     404        adj[v].add(w); // Add w to v's list.
     405        adj[w].add(v); //The graph is undirected
     406    }
     407
     408    private List<Node> getOddNodes() {
     409        List<Node> odd = new ArrayList<>();
     410        // Count vertices with odd degree
     411        for (int i = 0; i < allNodes.size(); i++) {
     412            if (adj[i].size() % 2 != 0)
     413                odd.add(allNodes.get(i));
     414        }
     415        return odd;
     416    }
     417
     418    /**
     419     * Check if graph has Eulerian path or Circuit or none of both
     420     * Code taken from https://www.geeksforgeeks.org/eulerian-path-and-circuit/
     421     * @return 0 if graph is not Eulerian, 1 if there is a Eulerian Path, 2 if there is an Eulerian Circuit
     422     */
     423    private int isEulerian() {
     424        // Check if all vertices are connected
     425        if (!isConnected())
     426            return 0;
     427
     428        // Count vertices with odd degree
     429        int odd = 0;
     430        for (int i = 0; i < allNodes.size(); i++) {
     431            if (adj[i].size() % 2 != 0)
     432                odd++;
     433        }
     434
     435        // If count is more than 2, then graph is not Eulerian
     436        if (odd > 2)
     437            return 0;
     438
     439        // If odd count is 2, then semi-eulerian.
     440        // If odd count is 0, then eulerian
     441        // Note that odd count can never be 1 for undirected graph
     442        return (odd == 2) ? 1 : 2;
     443    }
    380444}
  • src/org/openstreetmap/josm/data/osm/NodePair.java

     
    7777    public String toString() {
    7878        return new StringBuilder()
    7979        .append('[')
    80         .append(a.getId())
     80        .append(a.getUniqueId())
    8181        .append(',')
    82         .append(b.getId())
     82        .append(b.getUniqueId())
    8383        .append(']')
    8484        .toString();
    8585    }
  • test/unit/org/openstreetmap/josm/actions/CombineWayActionTest.java

     
    33
    44import static org.junit.jupiter.api.Assertions.assertEquals;
    55import static org.junit.jupiter.api.Assertions.assertFalse;
     6import static org.junit.jupiter.api.Assertions.assertTrue;
    67
    78import java.io.IOException;
    89import java.io.InputStream;
     
    1415import java.util.List;
    1516import java.util.Set;
    1617
     18import org.junit.jupiter.api.Test;
    1719import org.junit.jupiter.api.extension.RegisterExtension;
    18 import org.junit.jupiter.api.Test;
    1920import org.openstreetmap.josm.TestUtils;
    2021import org.openstreetmap.josm.data.osm.DataSet;
    2122import org.openstreetmap.josm.data.osm.Node;
     
    137138            List<Way> reversedWays = new LinkedList<>();
    138139            List<Way> unreversedWays = new LinkedList<>();
    139140            CombineWayAction.detectReversedWays(selection, path, reversedWays, unreversedWays);
    140             assertFalse(reversedWays.isEmpty());
     141            assertTrue(reversedWays.isEmpty());
    141142        }
    142143    }
    143144