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

Enable GPS-precise map matching #51

Closed
wants to merge 8 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
package com.graphhopper.matching;

import com.graphhopper.util.EdgeIteratorState;
import com.graphhopper.util.PointList;

import java.util.List;

/**
Expand All @@ -28,9 +30,11 @@ public class EdgeMatch {

private final EdgeIteratorState edgeState;
private final List<GPXExtension> gpxExtensions;
private final PointList wayGeometry;

public EdgeMatch(EdgeIteratorState edgeState, List<GPXExtension> gpxExtension) {
public EdgeMatch(EdgeIteratorState edgeState, List<GPXExtension> gpxExtension, PointList wayGeometry) {
this.edgeState = edgeState;
this.wayGeometry = wayGeometry;
Copy link
Member

Choose a reason for hiding this comment

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

Hmmh, I would prefer a setWayGeometry, this way we can init the EdgeMatch and call setWayGeometry for the certain conditions. Which will make "Match Phase (4)" a little less indirect - i.e. we can avoid PointList wayGeometry = null;


if (edgeState == null) {
throw new IllegalStateException("Cannot fetch null EdgeState");
Expand All @@ -42,6 +46,10 @@ public EdgeMatch(EdgeIteratorState edgeState, List<GPXExtension> gpxExtension) {
}
}

public EdgeMatch(EdgeIteratorState edgeState, List<GPXExtension> gpxExtension){
this(edgeState, gpxExtension, null);
}

public boolean isEmpty() {
return gpxExtensions.isEmpty();
}
Expand All @@ -68,6 +76,38 @@ public double getMinDistance() {
return min;
}

/**
* For OSM a way is often a curve not just a straight line. These nodes are called pillar nodes
* and are between tower nodes (which are used for routing), they are necessary to have a more
* exact geometry. Updates to the returned list are not reflected in the graph, for that you've
* to use setWayGeometry.
* <p>
* @param mode can be <ul> <li>0 = only pillar nodes, no tower nodes</li> <li>1 = inclusive the
* base tower node only</li> <li>2 = inclusive the adjacent tower node only</li> <li>3 =
* inclusive the base and adjacent tower node</li> </ul>
* @return pillar nodes
*/
public PointList getWayGeometry(int mode){
Copy link
Member

Choose a reason for hiding this comment

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

here I would use the same naming 'fetchWayGeometry' as in the edge interface because it is also here not always a cheap 'get' (?)

if(wayGeometry != null)
switch (mode){
case 0:
return wayGeometry.copy(1,wayGeometry.size() - 1);
case 1:
return wayGeometry.copy(0,wayGeometry.size() - 1);
case 2:
return wayGeometry.copy(1,wayGeometry.size());
case 3:
default:
return wayGeometry;
}
else
return edgeState.fetchWayGeometry(mode);
}

public PointList getWayGeometry() {
return getWayGeometry(3);
}

@Override
public String toString() {
return "edge:" + edgeState + ", extensions:" + gpxExtensions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public void setForceRepair(boolean forceRepair) {
* @param gpxList the input list with GPX points which should match to edges
* of the graph specified in the constructor
*/
public MatchResult doWork(List<GPXEntry> gpxList) {
public MatchResult doWork(List<GPXEntry> gpxList, boolean addStartAndEndWayGeometry) {
int currentIndex = 0;
if (gpxList.size() < 2) {
throw new IllegalStateException("gpx list needs at least 2 points!");
Expand Down Expand Up @@ -183,7 +183,7 @@ public MatchResult doWork(List<GPXEntry> gpxList) {
}

boolean doEnd = currentIndex >= gpxList.size();
MatchResult subMatch = doWork(firstQueryResults, gpxSublist, gpxLength, doEnd);
MatchResult subMatch = doWork(firstQueryResults, gpxSublist, gpxLength, doEnd, addStartAndEndWayGeometry && (separatedListStartIndex == 0) , addStartAndEndWayGeometry && doEnd);
List<EdgeMatch> result = subMatch.getEdgeMatches();
matchResult.setMatchLength(matchResult.getMatchLength() + subMatch.getMatchLength());
matchResult.setMatchMillis(matchResult.getMatchMillis() + subMatch.getMatchMillis());
Expand Down Expand Up @@ -230,6 +230,10 @@ public MatchResult doWork(List<GPXEntry> gpxList) {
return matchResult;
}

public MatchResult doWork(List<GPXEntry> gpxList) {
return doWork(gpxList, false);
}

/**
* This method creates a matching for the specified sublist, it uses the
* firstQueryResults to do the initialization for the start nodes, or just a
Expand All @@ -239,7 +243,7 @@ public MatchResult doWork(List<GPXEntry> gpxList) {
* doEnd is true, then the original edge is added
*/
MatchResult doWork(List<QueryResult> firstQueryResults,
List<GPXEntry> gpxList, double gpxLength, boolean doEnd) {
List<GPXEntry> gpxList, double gpxLength, boolean doEnd, boolean addStartWayGeometry, boolean addEndWayGeometry) {
int guessedEdgesPerPoint = 4;
List<EdgeMatch> edgeMatches = new ArrayList<EdgeMatch>();
final TIntObjectHashMap<List<GPXExtension>> extensionMap
Expand Down Expand Up @@ -410,6 +414,22 @@ public boolean execute(int edge, List<GPXExtension> list) {
+ ", all results:" + allQRs + ", end results:" + endQRList);
}

//Save the way geometry before we remove the virtualNodes
PointList wayGeometryToFirstTowerNode = null;
PointList wayGeometryToLastGPXPoint = null;
if (addStartWayGeometry){
EdgeIteratorState firstEdge = pathEdgeList.get(0);
if (isVirtualNode(firstEdge.getBaseNode())){
wayGeometryToFirstTowerNode = firstEdge.fetchWayGeometry(3);
}
}
if (addEndWayGeometry) {
EdgeIteratorState lastEdge = pathEdgeList.get(pathEdgeList.size() - 1);
if (isVirtualNode(lastEdge.getAdjNode())) {
wayGeometryToLastGPXPoint = lastEdge.fetchWayGeometry(3);
}
}

//
// replace virtual edges with original *full edge* at start and end!
List<EdgeIteratorState> list = new ArrayList<EdgeIteratorState>(pathEdgeList.size());
Expand Down Expand Up @@ -442,10 +462,18 @@ public boolean execute(int edge, List<GPXExtension> list) {

//////// Match Phase (4) ////////
int minGPXIndex = startIndex;
for (EdgeIteratorState edge : pathEdgeList) {
for (int i = 0; i < pathEdgeList.size(); i++) {
EdgeIteratorState edge = pathEdgeList.get(i);
PointList wayGeometry = null;
if (i == 0){
wayGeometry = wayGeometryToFirstTowerNode;
}
if (i == pathEdgeList.size() - 1){
wayGeometry = wayGeometryToLastGPXPoint;
}
List<GPXExtension> gpxExtensionList = extensionMap.get(edge.getEdge());
if (gpxExtensionList == null) {
edgeMatches.add(new EdgeMatch(edge, Collections.<GPXExtension>emptyList()));
edgeMatches.add(new EdgeMatch(edge, Collections.<GPXExtension>emptyList(), wayGeometry));
continue;
}

Expand All @@ -461,7 +489,7 @@ public boolean execute(int edge, List<GPXExtension> list) {
}
}
minGPXIndex = newMinGPXIndex;
EdgeMatch edgeMatch = new EdgeMatch(edge, clonedList);
EdgeMatch edgeMatch = new EdgeMatch(edge, clonedList, wayGeometry);
edgeMatches.add(edgeMatch);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
package com.graphhopper.matching;

import com.graphhopper.util.PointList;
import java.util.List;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import java.util.List;
import org.junit.AfterClass;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import org.junit.BeforeClass;
import org.junit.Test;
Expand Down Expand Up @@ -289,6 +290,81 @@ public void testRepairUTurn() {
mm.checkOrCleanup(res, false);
}

@Test
public void testWayGeometryAtStartAndEndPoint() {
GraphHopperStorage graph = hopper.getGraphHopperStorage();
LocationIndexMatch locationIndex = new LocationIndexMatch(graph,
(LocationIndexTree) hopper.getLocationIndex());
MapMatching mapMatching = new MapMatching(graph, locationIndex, encoder);

// https://graphhopper.com/maps/?point=51.341708%2C12.385272&point=51.340622%2C12.388405&locale=de-DE&vehicle=car&weighting=fastest&elevation=true&layer=Omniscale
List<GPXEntry> inputGPXEntries = new GPXFile().doImport("./src/test/resources/testStartEndGeometry.gpx").getEntries();
MatchResult mr = mapMatching.doWork(inputGPXEntries,true);

NodeAccess nodeAccess = graph.getNodeAccess();

List<EdgeMatch> edgeMatches = mr.getEdgeMatches();
for (int i = 0; i < edgeMatches.size(); i++) {
EdgeMatch match = edgeMatches.get(i);
assertNotNull(match.getWayGeometry());
if (i == 0) {
assertEquals(2, match.getWayGeometry().size());
assertEquals(51.341708, match.getWayGeometry().getLat(0), 1E-5);
assertEquals(12.385272, match.getWayGeometry().getLon(0), 1E-5);
assertEquals(nodeAccess.getLat(mr.getEdgeMatches().get(0).getEdgeState().getAdjNode()), match.getWayGeometry().getLat(1), 1E-5);
assertEquals(nodeAccess.getLon(mr.getEdgeMatches().get(0).getEdgeState().getAdjNode()), match.getWayGeometry().getLon(1), 1E-5);
} else if (i == edgeMatches.size() - 1) {
assertEquals(2, match.getWayGeometry().size());
assertEquals(nodeAccess.getLat(mr.getEdgeMatches().get(mr.getEdgeMatches().size() - 1).getEdgeState().getBaseNode()), match.getWayGeometry().getLat(0), 1E-5);
assertEquals(nodeAccess.getLon(mr.getEdgeMatches().get(mr.getEdgeMatches().size() - 1).getEdgeState().getBaseNode()), match.getWayGeometry().getLon(0), 1E-5);
assertEquals(51.340622, match.getWayGeometry().getLat(1), 1E-5);
assertEquals(12.388405, match.getWayGeometry().getLon(1), 1E-5);
}
}

assertEquals(mr.getGpxEntriesLength(), mr.getMatchLength(), 1);
// TODO why is there such a big difference for millis?
Copy link
Member

Choose a reason for hiding this comment

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

what do you mean with this TODO and compared to which value this is a 'big' difference?

Copy link
Author

Choose a reason for hiding this comment

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

the Mapmatched paths is 24sec different in length (I don't remember if shorter or longer) -> see the third param of the assertEquals. Acctually I don't know why that is the case. But the other tests also have such a difference (and such a ToDo-comment, that I copied from there).

assertEquals(mr.getGpxEntriesMillis(), mr.getMatchMillis(), 24000);
}

@Test
public void testWayGeometryAtStartAndEndPoint2() {
GraphHopperStorage graph = hopper.getGraphHopperStorage();
LocationIndexMatch locationIndex = new LocationIndexMatch(graph,
(LocationIndexTree) hopper.getLocationIndex());
MapMatching mapMatching = new MapMatching(graph, locationIndex, encoder);

// https://graphhopper.com/maps/?point=51.328198%2C12.335672&point=51.364285%2C12.459623&locale=de-DE&vehicle=car&weighting=fastest&elevation=true&layer=Omniscale
List<GPXEntry> inputGPXEntries = new GPXFile().doImport("./src/test/resources/testStartEndGeometry2.gpx").getEntries();
MatchResult mr = mapMatching.doWork(inputGPXEntries,true);


NodeAccess nodeAccess = graph.getNodeAccess();

List<EdgeMatch> edgeMatches = mr.getEdgeMatches();
for (int i = 0; i < edgeMatches.size(); i++) {
EdgeMatch match = edgeMatches.get(i);
assertNotNull(match.getWayGeometry());
if (i == 0) {
assertEquals(2, match.getWayGeometry().size());
assertEquals(51.328439, match.getWayGeometry().getLat(0), 1E-5);
assertEquals(12.335785, match.getWayGeometry().getLon(0), 1E-5);
assertEquals(nodeAccess.getLat(mr.getEdgeMatches().get(0).getEdgeState().getAdjNode()), match.getWayGeometry().getLat(1), 1E-5);
assertEquals(nodeAccess.getLon(mr.getEdgeMatches().get(0).getEdgeState().getAdjNode()), match.getWayGeometry().getLon(1), 1E-5);
} else if (i == edgeMatches.size() - 1) {
assertEquals(4, match.getWayGeometry().size());
assertEquals(nodeAccess.getLat(mr.getEdgeMatches().get(mr.getEdgeMatches().size() - 1).getEdgeState().getBaseNode()), match.getWayGeometry().getLat(0), 1E-5);
assertEquals(nodeAccess.getLon(mr.getEdgeMatches().get(mr.getEdgeMatches().size() - 1).getEdgeState().getBaseNode()), match.getWayGeometry().getLon(0), 1E-5);
assertEquals(51.364306, match.getWayGeometry().getLat(3), 1E-5);
assertEquals(12.459582, match.getWayGeometry().getLon(3), 1E-5);
}
}

assertEquals(mr.getGpxEntriesLength(), mr.getMatchLength(), 4);
// TODO why is there such a big difference for millis?
assertEquals(mr.getGpxEntriesMillis(), mr.getMatchMillis(), 242175);
}

List<String> fetchStreets(List<EdgeMatch> emList) {
List<String> list = new ArrayList<String>();
int prevNode = -1;
Expand Down
12 changes: 12 additions & 0 deletions matching-core/src/test/resources/testStartEndGeometry.gpx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no" ?><gpx xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" creator="Graphhopper version 0.7" version="1.1" xmlns:gh="https://graphhopper.com/public/schema/gpx/1.1">
<metadata><copyright author="OpenStreetMap contributors"/><link href="http://graphhopper.com"><text>GraphHopper GPX</text></link><time>2016-05-13T07:08:02Z</time></metadata>
<trk><name>GraphHopper Track</name><trkseg>
<trkpt lat="51.341708" lon="12.385276"><ele>116.0</ele><time>2016-05-13T07:08:02Z</time></trkpt>
<trkpt lat="51.341198" lon="12.38534"><ele>115.8</ele><time>2016-05-13T07:08:10Z</time></trkpt>
<trkpt lat="51.341074" lon="12.38777"><ele>115.2</ele><time>2016-05-13T07:08:48Z</time></trkpt>
<trkpt lat="51.341034" lon="12.388462"><ele>115.0</ele><time>2016-05-13T07:08:59Z</time></trkpt>
<trkpt lat="51.340752" lon="12.388414"><ele>116.8</ele><time>2016-05-13T07:09:06Z</time></trkpt>
<trkpt lat="51.340623" lon="12.388395"><ele>116.8</ele><time>2016-05-13T07:09:10Z</time></trkpt>
</trkseg>
</trk>
</gpx>
Loading