Skip to content
This repository has been archived by the owner on Mar 1, 2021. It is now read-only.

prevent inner-link U-turns #83

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added map-data/issue-70.osm.gz
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
*/
package com.graphhopper.matching;

import java.util.List;

import com.graphhopper.routing.VirtualEdgeIteratorState;
import com.graphhopper.storage.index.QueryResult;
import com.graphhopper.util.GPXEntry;

Expand All @@ -27,19 +30,30 @@
public class GPXExtension {
final GPXEntry entry;
final QueryResult queryResult;
final int gpxListIndex;
private boolean directed;
public VirtualEdgeIteratorState incomingVirtualEdge;
public VirtualEdgeIteratorState outgoingVirtualEdge;

public GPXExtension(GPXEntry entry, QueryResult queryResult, int gpxListIndex) {
this.entry = entry;
public GPXExtension(GPXEntry entry, QueryResult queryResult) {
this.entry = entry;
this.queryResult = queryResult;
this.gpxListIndex = gpxListIndex;
this.directed = false;
}

public GPXExtension(GPXEntry entry, QueryResult queryResult, VirtualEdgeIteratorState incomingVirtualEdge, VirtualEdgeIteratorState outgoingVirtualEdge) {
this(entry, queryResult);
this.incomingVirtualEdge = incomingVirtualEdge;
this.outgoingVirtualEdge = outgoingVirtualEdge;
this.directed = true;
}

public boolean isDirected() {
return directed;
}

@Override
public String toString() {
return "entry:" + entry
+ ", query distance:" + queryResult.getQueryDistance()
+ ", gpxListIndex:" + gpxListIndex;
return "entry:" + entry + ", query distance:" + queryResult.getQueryDistance();
}

public QueryResult getQueryResult() {
Expand All @@ -49,4 +63,4 @@ public QueryResult getQueryResult() {
public GPXEntry getEntry() {
return entry;
}
}
}
181 changes: 127 additions & 54 deletions matching-core/src/main/java/com/graphhopper/matching/MapMatching.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
package com.graphhopper.matching;

import com.graphhopper.routing.VirtualEdgeIteratorState;
import com.graphhopper.GraphHopper;
import com.graphhopper.matching.util.HmmProbabilities;
import com.graphhopper.matching.util.TimeStep;
Expand Down Expand Up @@ -161,70 +162,133 @@ public MatchResult doWork(List<GPXEntry> gpxList) {
+ gpxList.size() + "). Correct format?");
}

final EdgeFilter edgeFilter = new DefaultEdgeFilter(algoOptions.getWeighting().getFlagEncoder());

// Compute all candidates first.
// TODO: Generate candidates on-the-fly within computeViterbiSequence() if this does not
// degrade performance.
final List<QueryResult> allCandidates = new ArrayList<>();
List<TimeStep<GPXExtension, GPXEntry, Path>> timeSteps = createTimeSteps(gpxList,
edgeFilter, allCandidates);

if (allCandidates.size() < 2) {
throw new IllegalArgumentException("Too few matching coordinates ("
+ allCandidates.size() + "). Wrong region imported?");
// filter the entries:
List<GPXEntry> filteredGPXEntries = filterGPXEntries(gpxList);
if (filteredGPXEntries.size() < 2) {
throw new IllegalStateException("Only " + filteredGPXEntries.size() + " filtered GPX entries (from " + gpxList.size() + "), but two or more are needed");
}
if (timeSteps.size() < 2) {
throw new IllegalStateException("Coordinates produced too few time steps "
+ timeSteps.size() + ", gpxList:" + gpxList.size());
}


// now find each of the entries in the graph:
final EdgeFilter edgeFilter = new DefaultEdgeFilter(algoOptions.getWeighting().getFlagEncoder());
List<List<QueryResult>> queriesPerEntry = findGPXEntriesInGraph(filteredGPXEntries, edgeFilter);

// now look up the entries up in the graph:
final QueryGraph queryGraph = new QueryGraph(routingGraph).setUseEdgeExplorerCache(true);
queryGraph.lookup(allCandidates);
List<QueryResult> allQueryResults = new ArrayList<QueryResult>();
for (List<QueryResult> qrs: queriesPerEntry)
allQueryResults.addAll(qrs);
queryGraph.lookup(allQueryResults);

// create candidates from the entries in the graph (a candidate is basically an entry + direction):
List<TimeStep<GPXExtension, GPXEntry, Path>> timeSteps = createTimeSteps(filteredGPXEntries, queriesPerEntry, queryGraph);

List<SequenceState<GPXExtension, GPXEntry, Path>> seq = computeViterbiSequence(timeSteps,
gpxList, queryGraph);
// viterbify:
List<SequenceState<GPXExtension, GPXEntry, Path>> seq = computeViterbiSequence(timeSteps, gpxList, queryGraph);

// finally, extract the result:
final EdgeExplorer explorer = queryGraph.createEdgeExplorer(edgeFilter);
MatchResult matchResult = computeMatchResult(seq, gpxList, allCandidates, explorer);
MatchResult matchResult = computeMatchResult(seq, filteredGPXEntries, queriesPerEntry, explorer);

return matchResult;
}


/**
* Filters GPX entries to only those which will be used for map matching (i.e. those which
* are separated by at least 2 * measurementErrorSigman
*/
private List<GPXEntry> filterGPXEntries(List<GPXEntry> gpxList) {
List<GPXEntry> filtered = new ArrayList<GPXEntry>();
GPXEntry prevEntry = null;
int last = gpxList.size() - 1;
for (int i = 0; i <= last; i++) {
GPXEntry gpxEntry = gpxList.get(i);
if (i == 0 || i == last || distanceCalc.calcDist(
prevEntry.getLat(), prevEntry.getLon(),
gpxEntry.getLat(), gpxEntry.getLon()) > 2 * measurementErrorSigma) {
filtered.add(gpxEntry);
prevEntry = gpxEntry;
}
}
return filtered;
}
/**
* Find the possible locations of each qpxEntry in the graph.
*/
private List<List<QueryResult>> findGPXEntriesInGraph(List<GPXEntry> gpxList, EdgeFilter edgeFilter) {

List<List<QueryResult>> gpxEntryLocations = new ArrayList<List<QueryResult>>();
for (GPXEntry gpxEntry : gpxList) {
gpxEntryLocations.add(locationIndex.findNClosest(gpxEntry.lat, gpxEntry.lon, edgeFilter, measurementErrorSigma));
}
return gpxEntryLocations;
}


/**
* Creates TimeSteps for the GPX entries but does not create emission or
* transition probabilities.
*
* @param outAllCandidates output parameter for all candidates, must be an
* empty list.
*/
private List<TimeStep<GPXExtension, GPXEntry, Path>> createTimeSteps(List<GPXEntry> gpxList,
EdgeFilter edgeFilter, List<QueryResult> outAllCandidates) {
int indexGPX = 0;
TimeStep<GPXExtension, GPXEntry, Path> prevTimeStep = null;
private List<TimeStep<GPXExtension, GPXEntry, Path>> createTimeSteps(List<GPXEntry> filteredGPXEntries,
List<List<QueryResult>> queriesPerEntry, QueryGraph queryGraph) {

final List<TimeStep<GPXExtension, GPXEntry, Path>> timeSteps = new ArrayList<>();

for (GPXEntry gpxEntry : gpxList) {
if (prevTimeStep == null
|| distanceCalc.calcDist(
prevTimeStep.observation.getLat(), prevTimeStep.observation.getLon(),
gpxEntry.getLat(), gpxEntry.getLon()) > 2 * measurementErrorSigma
// always include last point
|| indexGPX == gpxList.size() - 1) {
final List<QueryResult> queryResults = locationIndex.findNClosest(
gpxEntry.lat, gpxEntry.lon,
edgeFilter, measurementErrorSigma);
outAllCandidates.addAll(queryResults);
final List<GPXExtension> candidates = new ArrayList<>();
for (QueryResult candidate : queryResults) {
candidates.add(new GPXExtension(gpxEntry, candidate, indexGPX));
}
final TimeStep<GPXExtension, GPXEntry, Path> timeStep
= new TimeStep<>(gpxEntry, candidates);
timeSteps.add(timeStep);
prevTimeStep = timeStep;
}
indexGPX++;
int n = filteredGPXEntries.size();
assert queriesPerEntry.size() == n;
for (int i = 0; i < n; i++) {

GPXEntry gpxEntry = filteredGPXEntries.get(i);
List<QueryResult> queryResults = queriesPerEntry.get(i);

// as discussed in #51, if the closest node is virtual (i.e. inner-link) then we need to create two candidates:
// one for each direction of each virtual edge. For example, in A---X---B, we'd add the edges A->X and B->X. Note
// that we add the edges with an incoming direction (i.e. A->X not X->A). We can choose to enforce the incoming/outgoing
// direction with the third argument of queryGraph.enforceHeading
List<GPXExtension> candidates = new ArrayList<GPXExtension>();
for (QueryResult qr: queryResults) {
int closestNode = qr.getClosestNode();
if (queryGraph.isVirtualNode(closestNode)) {
// get virtual edges:
List<VirtualEdgeIteratorState> virtualEdges = new ArrayList<VirtualEdgeIteratorState>();
EdgeIterator iter = queryGraph.createEdgeExplorer().setBaseNode(closestNode);
while (iter.next()) {
if (queryGraph.isVirtualEdge(iter.getEdge())) {
virtualEdges.add((VirtualEdgeIteratorState) queryGraph.getEdgeIteratorState(iter.getEdge(), iter.getAdjNode()));
}
}
assert virtualEdges.size() == 2;

// create a candidate for each: the candidate being the querypoint plus the virtual edge to favour. Note
// that we favour the virtual edge by *unfavoring* the rest, so we need to record these.
VirtualEdgeIteratorState e1 = virtualEdges.get(0);
VirtualEdgeIteratorState e2 = virtualEdges.get(1);
for (int j = 0; j < 2; j++) {
// get favored/unfavored edges:
VirtualEdgeIteratorState incomingVirtualEdge = j == 0 ? e1 : e2;
VirtualEdgeIteratorState outgoingVirtualEdge = j == 0 ? e2 : e1;
// create candidate
QueryResult vqr = new QueryResult(qr.getQueryPoint().lat, qr.getQueryPoint().lon);
vqr.setQueryDistance(qr.getQueryDistance());
vqr.setClosestNode(qr.getClosestNode());
vqr.setWayIndex(qr.getWayIndex());
vqr.setSnappedPosition(qr.getSnappedPosition());
vqr.setClosestEdge(qr.getClosestEdge());
vqr.calcSnappedPoint(distanceCalc);
GPXExtension candidate = new GPXExtension(gpxEntry, vqr, incomingVirtualEdge, outgoingVirtualEdge);
candidates.add(candidate);
}
} else {
// just add the real edge, undirected
GPXExtension candidate = new GPXExtension(gpxEntry, qr);
candidates.add(candidate);
}
}

final TimeStep<GPXExtension, GPXEntry, Path> timeStep = new TimeStep<>(gpxEntry, candidates);
timeSteps.add(timeStep);
}
return timeSteps;
}
Expand Down Expand Up @@ -301,9 +365,16 @@ private void computeTransitionProbabilities(TimeStep<GPXExtension, GPXEntry, Pat
for (GPXExtension from : prevTimeStep.candidates) {
for (GPXExtension to : timeStep.candidates) {
RoutingAlgorithm algo = algoFactory.createAlgo(queryGraph, algoOptions);
// System.out.println("algo " + algo.getName());
final Path path = algo.calcPath(from.getQueryResult().getClosestNode(),
to.getQueryResult().getClosestNode());
// enforce heading if required:
if (from.isDirected()) {
from.incomingVirtualEdge.setUnfavored(true);
Copy link
Contributor

@stefanholder stefanholder Nov 25, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, we need to unfavor the reverse edge to make sure that the router does not take the wrong outgoing virtual edge from the virtual "from" node.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes - forgot both directions on the edge. As per new commit?

}
if (to.isDirected()) {
// unfavor the favour virtual edge
to.outgoingVirtualEdge.setUnfavored(true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above. We need to make sure that the router does not take the wrong ingoing virtual edge to the virtual "to" node.

}
final Path path = algo.calcPath(from.getQueryResult().getClosestNode(), to.getQueryResult().getClosestNode());
queryGraph.clearUnfavoredStatus();
if (path.isFound()) {
timeStep.addRoadPath(from, to, path);
final double transitionLogProbability = probabilities
Expand All @@ -315,13 +386,15 @@ private void computeTransitionProbabilities(TimeStep<GPXExtension, GPXEntry, Pat
}

private MatchResult computeMatchResult(List<SequenceState<GPXExtension, GPXEntry, Path>> seq,
List<GPXEntry> gpxList, List<QueryResult> allCandidates,
List<GPXEntry> gpxList, List<List<QueryResult>> queriesPerEntry,
EdgeExplorer explorer) {
// every virtual edge maps to its real edge where the orientation is already correct!
// TODO use traversal key instead of string!
final Map<String, EdgeIteratorState> virtualEdgesMap = new HashMap<>();
for (QueryResult candidate : allCandidates) {
fillVirtualEdges(virtualEdgesMap, explorer, candidate);
for (List<QueryResult> queryResults: queriesPerEntry) {
for (QueryResult qr: queryResults) {
fillVirtualEdges(virtualEdgesMap, explorer, qr);
}
}

MatchResult matchResult = computeMatchedEdges(seq, virtualEdgesMap);
Expand Down Expand Up @@ -538,4 +611,4 @@ public Path calcPath(MatchResult mr) {
return p;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,26 @@ public void testIssue13() {
assertEquals(mr.getGpxEntriesLength(), mr.getMatchLength(), 2.5);
assertEquals(28790, mr.getMatchMillis(), 50);
}

@Test
public void testIssue70() {
CarFlagEncoder encoder = new CarFlagEncoder();
TestGraphHopper hopper = new TestGraphHopper();
hopper.setDataReaderFile("../map-data/issue-70.osm.gz");
hopper.setGraphHopperLocation("../target/mapmatchingtest-70");
hopper.setEncodingManager(new EncodingManager(encoder));
hopper.importOrLoad();

AlgorithmOptions opts = AlgorithmOptions.start().build();
MapMatching mapMatching = new MapMatching(hopper, opts);

List<GPXEntry> inputGPXEntries = new GPXFile().
doImport("./src/test/resources/issue-70.gpx").getEntries();
MatchResult mr = mapMatching.doWork(inputGPXEntries);

assertEquals(Arrays.asList("Милана Видака", "Милана Видака", "Милана Видака",
"Бранка Радичевића", "Бранка Радичевића", "Здравка Челара"),
fetchStreets(mr.getEdgeMatches()));
// TODO: length/time
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public static Collection<Object[]> algoOptions() {

// force CH
AlgorithmOptions chOpts = AlgorithmOptions.start()
.maxVisitedNodes(40)
.maxVisitedNodes(1000)
.hints(new PMap().put(Parameters.CH.DISABLE, false))
.build();

Expand Down Expand Up @@ -208,7 +208,7 @@ public void testSmallSeparatedSearchDistance() {
MatchResult mr = mapMatching.doWork(inputGPXEntries);
assertEquals(Arrays.asList("Weinligstraße", "Weinligstraße", "Weinligstraße",
"Fechnerstraße", "Fechnerstraße"), fetchStreets(mr.getEdgeMatches()));
assertEquals(mr.getGpxEntriesLength(), mr.getMatchLength(), 11);
assertEquals(mr.getGpxEntriesLength(), mr.getMatchLength(), 11); // TODO: this should be around 300m according to Google ... need to check
assertEquals(mr.getGpxEntriesMillis(), mr.getMatchMillis(), 3000);
}

Expand Down
25 changes: 25 additions & 0 deletions matching-core/src/test/resources/issue-70.gpx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<gpx xmlns="http://www.topografix.com/GPX/1/1" creator="MapSource 6.16.3" version="1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
<trk>
<name>converted track</name>
<trkseg>
<trkpt lat="45.24443688057394" lon="19.70705632120371"><ele>382</ele><time>2010-01-01T01:00:53Z</time></trkpt>
<trkpt lat="45.244557744418756" lon="19.70795754343271"><ele>382</ele><time>2010-01-01T01:01:53Z</time></trkpt>
<trkpt lat="45.24481457923517" lon="19.709523953497406"><ele>382</ele><time>2010-01-01T01:02:53Z</time></trkpt>
<trkpt lat="45.24516205978563" lon="19.71006039530039"><ele>382</ele><time>2010-01-01T01:03:53Z</time></trkpt>
<trkpt lat="45.24513184416995" lon="19.710961617529392"><ele>382</ele><time>2010-01-01T01:04:53Z</time></trkpt>
<trkpt lat="45.24540378413238" lon="19.71250656992197"><ele>382</ele><time>2010-01-01T01:05:53Z</time></trkpt>
<trkpt lat="45.245690830458216" lon="19.713021554052826"><ele>382</ele><time>2010-01-01T01:06:53Z</time></trkpt>
<trkpt lat="45.24563039977332" lon="19.713944233953953"><ele>382</ele><time>2010-01-01T01:07:53Z</time></trkpt>
<trkpt lat="45.24517716758746" lon="19.71370819956064"><ele>382</ele><time>2010-01-01T01:08:53Z</time></trkpt>
<trkpt lat="45.24482968712937" lon="19.71398714929819"><ele>382</ele><time>2010-01-01T01:09:53Z</time></trkpt>
<trkpt lat="45.24458796033977" lon="19.713622368872166"><ele>382</ele><time>2010-01-01T01:10:53Z</time></trkpt>
<trkpt lat="45.244210260171826" lon="19.713515080511566"><ele>382</ele><time>2010-01-01T01:11:53Z</time></trkpt>
<trkpt lat="45.244557744418756" lon="19.71428755670786"><ele>382</ele><time>2010-01-01T01:12:53Z</time></trkpt>
<trkpt lat="45.2443764485551" lon="19.714502133429047"><ele>382</ele><time>2010-01-01T01:13:53Z</time></trkpt>
<trkpt lat="45.244708823863164" lon="19.71527460962534"><ele>382</ele><time>2010-01-01T01:14:53Z</time></trkpt>
<trkpt lat="45.24460306829425" lon="19.71548918634653"><ele>382</ele><time>2010-01-01T01:15:53Z</time></trkpt>
<trkpt lat="45.244905226540226" lon="19.716197289526463"><ele>382</ele><time>2010-01-01T01:16:53Z</time></trkpt>
</trkseg>
</trk>
</gpx>
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public class MatchServlet extends GraphHopperServlet {
public void doPost(HttpServletRequest httpReq, HttpServletResponse httpRes)
throws ServletException, IOException {

logger.info("posted");
String infoStr = httpReq.getRemoteAddr() + " " + httpReq.getLocale();
String inType = "gpx";
String contentType = httpReq.getContentType();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ public GHPoint3D getSnappedPoint() {
}
};

list.add(new GPXExtension(new GPXEntry(-3.4446, -38.9996, 100000), queryResult1, 1));
list.add(new GPXExtension(new GPXEntry(-3.4448, -38.9999, 100001), queryResult2, 1));
list.add(new GPXExtension(new GPXEntry(-3.4446, -38.9996, 100000), queryResult1));
list.add(new GPXExtension(new GPXEntry(-3.4448, -38.9999, 100001), queryResult2));
return list;
}

Expand Down