From f4d6a71677200bd4bc119dce2026e21efde30dce Mon Sep 17 00:00:00 2001 From: Stefan Holder Date: Mon, 9 Jan 2017 17:23:53 +0100 Subject: [PATCH] Dedupe QueryResults by closest node --- CONTRIBUTORS.md | 4 +- .../matching/LocationIndexMatch.java | 14 +++-- .../com/graphhopper/matching/MapMatching.java | 54 ++++++++++++++----- .../graphhopper/matching/MapMatchingMain.java | 2 + 4 files changed, 55 insertions(+), 19 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 84980bf5..82a29074 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -5,7 +5,7 @@ * michaz, very important hidden markov improvement via hmm-lib, see #49 * rory, support milisecond gpx timestamps, see #4 * stefanholder, Stefan Holder, BMW AG, creating and integrating the hmm-lib (#49, #66, #69) and - penalizing inner-link U-turns (#70) - * kodonnell, adding support for CH and other algorithms (#60) and penalizing inner-link U-turns (#70) + penalizing inner-link U-turns (#88, #91) + * kodonnell, adding support for CH and other algorithms (#60) and penalizing inner-link U-turns (#88) For GraphHopper contributors see [here](https://github.com/graphhopper/graphhopper/blob/master/CONTRIBUTORS.md). diff --git a/matching-core/src/main/java/com/graphhopper/matching/LocationIndexMatch.java b/matching-core/src/main/java/com/graphhopper/matching/LocationIndexMatch.java index 7e45da36..df35caa2 100644 --- a/matching-core/src/main/java/com/graphhopper/matching/LocationIndexMatch.java +++ b/matching-core/src/main/java/com/graphhopper/matching/LocationIndexMatch.java @@ -52,16 +52,24 @@ public LocationIndexMatch(GraphHopperStorage graph, LocationIndexTree index) { this.index = index; } - public List findNClosest(final double queryLat, final double queryLon, final EdgeFilter edgeFilter, - double gpxAccuracyInMetern) { + /** + * Returns all edges that are within the specified radius around the queried position. + * Searches at most 9 cells to avoid performance problems. Hence, if the radius is larger than + * the cell width then not all edges might be returned. + * + * @param radius in meters + */ + public List findNClosest(final double queryLat, final double queryLon, + final EdgeFilter edgeFilter, double radius) { // Return ALL results which are very close and e.g. within the GPS signal accuracy. // Also important to get all edges if GPS point is close to a junction. - final double returnAllResultsWithin = distCalc.calcNormalizedDist(gpxAccuracyInMetern); + final double returnAllResultsWithin = distCalc.calcNormalizedDist(radius); // implement a cheap priority queue via List, sublist and Collections.sort final List queryResults = new ArrayList(); GHIntHashSet set = new GHIntHashSet(); + // Doing 2 iterations means searching 9 tiles. for (int iteration = 0; iteration < 2; iteration++) { // should we use the return value of earlyFinish? index.findNetworkEntries(queryLat, queryLon, set, iteration); diff --git a/matching-core/src/main/java/com/graphhopper/matching/MapMatching.java b/matching-core/src/main/java/com/graphhopper/matching/MapMatching.java index f2b53a2f..7c4c8896 100644 --- a/matching-core/src/main/java/com/graphhopper/matching/MapMatching.java +++ b/matching-core/src/main/java/com/graphhopper/matching/MapMatching.java @@ -183,18 +183,29 @@ public MatchResult doWork(List gpxList) { // now find each of the entries in the graph: final EdgeFilter edgeFilter = new DefaultEdgeFilter(algoOptions.getWeighting().getFlagEncoder()); - List> queriesPerEntry = lookupGPXEntries(filteredGPXEntries, edgeFilter); + List> queriesPerEntry = + lookupGPXEntries(filteredGPXEntries, edgeFilter); - // now look up the entries up in the graph: + // Add virtual nodes and edges to the graph so that candidates on edges can be represented + // by virtual nodes. final QueryGraph queryGraph = new QueryGraph(routingGraph).setUseEdgeExplorerCache(true); List allQueryResults = new ArrayList<>(); - for (List qrs: queriesPerEntry) + for (Collection qrs: queriesPerEntry) allQueryResults.addAll(qrs); queryGraph.lookup(allQueryResults); + // Different QueryResults can have the same tower node as their closest node. + // Hence, we now dedupe the query results of each GPX entry by their closest node (#91). + // This must be done after calling queryGraph.lookup() since this replaces some of the + // QueryResult nodes with virtual nodes. Virtual nodes are not deduped since there is at + // most one QueryResult per edge and virtual nodes are inserted into the middle of an edge. + // Reducing the number of QueryResults improves performance since less shortest/fastest + // routes need to be computed. + queriesPerEntry = dedupeQueryResultsByClosestNode(queriesPerEntry); + logger.debug("================= Query results ================="); int i = 1; - for (List entries : queriesPerEntry) { + for (Collection entries : queriesPerEntry) { logger.debug("Query results for GPX entry {}", i++); for (QueryResult qr : entries) { logger.debug("Node id: {}, virtual: {}, snapped on: {}, pos: {},{}, " @@ -269,26 +280,41 @@ private List filterGPXEntries(List gpxList) { } /** - * Find the possible locations of each qpxEntry in the graph. + * Find the possible locations (edges) of each GPXEntry in the graph. */ - private List> lookupGPXEntries(List gpxList, + private List> lookupGPXEntries(List gpxList, EdgeFilter edgeFilter) { - List> gpxEntryLocations = new ArrayList<>(); + final List> gpxEntryLocations = new ArrayList<>(); for (GPXEntry gpxEntry : gpxList) { - gpxEntryLocations.add(locationIndex.findNClosest(gpxEntry.lat, gpxEntry.lon, edgeFilter, - measurementErrorSigma)); + final List queryResults = locationIndex.findNClosest( + gpxEntry.lat, gpxEntry.lon, edgeFilter, measurementErrorSigma); + gpxEntryLocations.add(queryResults); } return gpxEntryLocations; } + private List> dedupeQueryResultsByClosestNode( + List> queriesPerEntry) { + final List> result = new ArrayList<>(queriesPerEntry.size()); + + for (Collection queryResults : queriesPerEntry) { + final Map dedupedQueryResults = new HashMap<>(); + for (QueryResult qr : queryResults) { + dedupedQueryResults.put(qr.getClosestNode(), qr); + } + result.add(dedupedQueryResults.values()); + } + return result; + } + /** * Creates TimeSteps with candidates for the GPX entries but does not create emission or * transition probabilities. Creates directed candidates for virtual nodes and undirected * candidates for real nodes. */ private List> createTimeSteps( - List filteredGPXEntries, List> queriesPerEntry, + List filteredGPXEntries, List> queriesPerEntry, QueryGraph queryGraph) { final int n = filteredGPXEntries.size(); if (queriesPerEntry.size() != n) { @@ -300,7 +326,7 @@ private List> createTimeSteps( for (int i = 0; i < n; i++) { GPXEntry gpxEntry = filteredGPXEntries.get(i); - List queryResults = queriesPerEntry.get(i); + final Collection queryResults = queriesPerEntry.get(i); List candidates = new ArrayList<>(); for (QueryResult qr: queryResults) { @@ -512,7 +538,7 @@ private double penalizedPathDistance(Path path, private MatchResult computeMatchResult(List> seq, List gpxList, - List> queriesPerEntry, + List> queriesPerEntry, EdgeExplorer explorer) { final Map virtualEdgesMap = createVirtualEdgesMap( queriesPerEntry, explorer); @@ -611,10 +637,10 @@ private boolean isVirtualNode(int node) { * Returns a map where every virtual edge maps to its real edge with correct orientation. */ private Map createVirtualEdgesMap( - List> queriesPerEntry, EdgeExplorer explorer) { + List> queriesPerEntry, EdgeExplorer explorer) { // TODO For map key, use the traversal key instead of string! Map virtualEdgesMap = new HashMap<>(); - for (List queryResults: queriesPerEntry) { + for (Collection queryResults: queriesPerEntry) { for (QueryResult qr: queryResults) { if (isVirtualNode(qr.getClosestNode())) { EdgeIterator iter = explorer.setBaseNode(qr.getClosestNode()); diff --git a/matching-core/src/main/java/com/graphhopper/matching/MapMatchingMain.java b/matching-core/src/main/java/com/graphhopper/matching/MapMatchingMain.java index 49479090..ee890165 100644 --- a/matching-core/src/main/java/com/graphhopper/matching/MapMatchingMain.java +++ b/matching-core/src/main/java/com/graphhopper/matching/MapMatchingMain.java @@ -86,6 +86,8 @@ private void start(CmdArgs args) { algorithm(Parameters.Algorithms.DIJKSTRA_BI).traversalMode(hopper.getTraversalMode()). weighting(new FastestWeighting(firstEncoder)). maxVisitedNodes(args.getInt("max_visited_nodes", 1000)). + // Penalizing inner-link U-turns only works with fastest weighting, since + // shortest weighting does not apply penalties to unfavored virtual edges. hints(new HintsMap().put("weighting", "fastest").put("vehicle", firstEncoder.toString())). build(); MapMatching mapMatching = new MapMatching(hopper, opts);