diff --git a/matching-core/src/main/java/com/graphhopper/matching/EdgeMatch.java b/matching-core/src/main/java/com/graphhopper/matching/EdgeMatch.java index 1c1a906a..e04e0fa4 100644 --- a/matching-core/src/main/java/com/graphhopper/matching/EdgeMatch.java +++ b/matching-core/src/main/java/com/graphhopper/matching/EdgeMatch.java @@ -18,6 +18,8 @@ package com.graphhopper.matching; import com.graphhopper.util.EdgeIteratorState; +import com.graphhopper.util.PointList; + import java.util.List; /** @@ -28,6 +30,7 @@ public class EdgeMatch { private final EdgeIteratorState edgeState; private final List gpxExtensions; + private PointList wayGeometry; public EdgeMatch(EdgeIteratorState edgeState, List gpxExtension) { this.edgeState = edgeState; @@ -68,6 +71,43 @@ public double getMinDistance() { return min; } + public void setWayGeometry(PointList wayGeometry) { + this.wayGeometry = wayGeometry; + } + + /** + * 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. + *

+ * + * @param mode can be

+ * @return pillar nodes + */ + public PointList fetchWayGeometry(int mode) { + 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 fetchWayGeometry() { + return fetchWayGeometry(3); + } + @Override public String toString() { return "edge:" + edgeState + ", extensions:" + gpxExtensions; 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 f443202d..af88f65f 100644 --- a/matching-core/src/main/java/com/graphhopper/matching/MapMatching.java +++ b/matching-core/src/main/java/com/graphhopper/matching/MapMatching.java @@ -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 gpxList) { + public MatchResult doWork(List gpxList, boolean addStartAndEndWayGeometry) { int currentIndex = 0; if (gpxList.size() < 2) { throw new IllegalStateException("gpx list needs at least 2 points!"); @@ -183,7 +183,7 @@ public MatchResult doWork(List 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 result = subMatch.getEdgeMatches(); matchResult.setMatchLength(matchResult.getMatchLength() + subMatch.getMatchLength()); matchResult.setMatchMillis(matchResult.getMatchMillis() + subMatch.getMatchMillis()); @@ -230,6 +230,10 @@ public MatchResult doWork(List gpxList) { return matchResult; } + public MatchResult doWork(List 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 @@ -239,7 +243,7 @@ public MatchResult doWork(List gpxList) { * doEnd is true, then the original edge is added */ MatchResult doWork(List firstQueryResults, - List gpxList, double gpxLength, boolean doEnd) { + List gpxList, double gpxLength, boolean doEnd, boolean addStartWayGeometry, boolean addEndWayGeometry) { int guessedEdgesPerPoint = 4; List edgeMatches = new ArrayList(); final TIntObjectHashMap> extensionMap @@ -410,6 +414,22 @@ public boolean execute(int edge, List 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 list = new ArrayList(pathEdgeList.size()); @@ -442,10 +462,13 @@ public boolean execute(int edge, List list) { //////// Match Phase (4) //////// int minGPXIndex = startIndex; - for (EdgeIteratorState edge : pathEdgeList) { + for (int i = 0; i < pathEdgeList.size(); i++) { + EdgeIteratorState edge = pathEdgeList.get(i); List gpxExtensionList = extensionMap.get(edge.getEdge()); if (gpxExtensionList == null) { - edgeMatches.add(new EdgeMatch(edge, Collections.emptyList())); + EdgeMatch edgeMatch = new EdgeMatch(edge, Collections.emptyList()); + addWayGeometry(i, pathEdgeList.size() - 1, wayGeometryToFirstTowerNode, wayGeometryToLastGPXPoint, edgeMatch); + edgeMatches.add(edgeMatch); continue; } @@ -462,6 +485,7 @@ public boolean execute(int edge, List list) { } minGPXIndex = newMinGPXIndex; EdgeMatch edgeMatch = new EdgeMatch(edge, clonedList); + addWayGeometry(i, pathEdgeList.size() - 1, wayGeometryToFirstTowerNode, wayGeometryToLastGPXPoint, edgeMatch); edgeMatches.add(edgeMatch); } @@ -472,6 +496,15 @@ public boolean execute(int edge, List list) { return res; } + private void addWayGeometry(int i, int lastIndex, PointList wayGeometryToFirstTowerNode, PointList wayGeometryToLastGPXPoint, EdgeMatch edgeMatch) { + if (i == 0 && wayGeometryToFirstTowerNode != null) { + edgeMatch.setWayGeometry(wayGeometryToFirstTowerNode); + } + if (i == lastIndex && wayGeometryToLastGPXPoint != null) { + edgeMatch.setWayGeometry(wayGeometryToLastGPXPoint); + } + } + private boolean isVirtualNode(int node) { return node >= nodeCount; } diff --git a/matching-core/src/main/java/com/graphhopper/matching/MatchResult.java b/matching-core/src/main/java/com/graphhopper/matching/MatchResult.java index cfc00de9..c4447bdf 100644 --- a/matching-core/src/main/java/com/graphhopper/matching/MatchResult.java +++ b/matching-core/src/main/java/com/graphhopper/matching/MatchResult.java @@ -17,6 +17,7 @@ */ package com.graphhopper.matching; +import com.graphhopper.util.PointList; import java.util.List; /** diff --git a/matching-core/src/test/java/com/graphhopper/matching/MapMatchingTest.java b/matching-core/src/test/java/com/graphhopper/matching/MapMatchingTest.java index e0882e6f..df5e0a4e 100644 --- a/matching-core/src/test/java/com/graphhopper/matching/MapMatchingTest.java +++ b/matching-core/src/test/java/com/graphhopper/matching/MapMatchingTest.java @@ -27,16 +27,7 @@ import com.graphhopper.storage.NodeAccess; import com.graphhopper.storage.index.LocationIndex; import com.graphhopper.storage.index.LocationIndexTree; -import com.graphhopper.util.BreadthFirstSearch; -import com.graphhopper.util.EdgeExplorer; -import com.graphhopper.util.EdgeIteratorState; -import com.graphhopper.util.GHUtility; -import com.graphhopper.util.GPXEntry; -import com.graphhopper.util.Helper; -import com.graphhopper.util.InstructionList; -import com.graphhopper.util.PathMerger; -import com.graphhopper.util.Translation; -import com.graphhopper.util.TranslationMap; +import com.graphhopper.util.*; import com.graphhopper.util.shapes.GHPoint; import java.util.ArrayList; import java.util.Arrays; @@ -44,6 +35,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; @@ -289,6 +281,106 @@ 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 inputGPXEntries = new GPXFile().doImport("./src/test/resources/testStartEndGeometry.gpx").getEntries(); + MatchResult mr = mapMatching.doWork(inputGPXEntries, true); + + NodeAccess nodeAccess = graph.getNodeAccess(); + + List edgeMatches = mr.getEdgeMatches(); + for (int i = 0; i < edgeMatches.size(); i++) { + EdgeMatch match = edgeMatches.get(i); + assertNotNull(match.fetchWayGeometry()); + if (i == 0) { + assertEquals(2, match.fetchWayGeometry().size()); + assertEquals(51.341708, match.fetchWayGeometry().getLat(0), 1E-5); + assertEquals(12.385272, match.fetchWayGeometry().getLon(0), 1E-5); + assertEquals(nodeAccess.getLat(mr.getEdgeMatches().get(0).getEdgeState().getAdjNode()), match.fetchWayGeometry().getLat(1), 1E-5); + assertEquals(nodeAccess.getLon(mr.getEdgeMatches().get(0).getEdgeState().getAdjNode()), match.fetchWayGeometry().getLon(1), 1E-5); + } else if (i == edgeMatches.size() - 1) { + assertEquals(2, match.fetchWayGeometry().size()); + assertEquals(nodeAccess.getLat(mr.getEdgeMatches().get(mr.getEdgeMatches().size() - 1).getEdgeState().getBaseNode()), match.fetchWayGeometry().getLat(0), 1E-5); + assertEquals(nodeAccess.getLon(mr.getEdgeMatches().get(mr.getEdgeMatches().size() - 1).getEdgeState().getBaseNode()), match.fetchWayGeometry().getLon(0), 1E-5); + assertEquals(51.340622, match.fetchWayGeometry().getLat(1), 1E-5); + assertEquals(12.388405, match.fetchWayGeometry().getLon(1), 1E-5); + } + } + + assertEquals(mr.getGpxEntriesLength(), mr.getMatchLength(), 1); + // TODO why is there such a big difference for millis? + assertEquals(mr.getGpxEntriesMillis(), mr.getMatchMillis(), 24000); + } + + @Test + public void testWayGeometryAtStartAndEndPointOneEdge() { + 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.341497%2C12.385304&point=51.341198%2C12.38534&locale=de-DE&vehicle=car&weighting=fastest&elevation=true&layer=Omniscale + List inputGPXEntries = new GPXFile().doImport("./src/test/resources/testStartEndGeometryOneEdge.gpx").getEntries(); + MatchResult mr = mapMatching.doWork(inputGPXEntries, true); + + List edgeMatches = mr.getEdgeMatches(); + assertEquals(1, edgeMatches.size()); + EdgeMatch match = edgeMatches.get(0); + PointList wayGeometry = match.fetchWayGeometry(3); + assertEquals(51.341497, wayGeometry.getLat(0), 1E-5); + assertEquals(12.385304, wayGeometry.getLon(0), 1E-5); + assertEquals(51.341198, wayGeometry.getLat(1), 1E-5); + assertEquals(12.38534, wayGeometry.getLon(1), 1E-5); + + assertEquals(mr.getGpxEntriesLength(), mr.getMatchLength(), 0.1); + // TODO why is there such a big difference for millis? + assertEquals(mr.getGpxEntriesMillis(), mr.getMatchMillis(), 1000); + } + + @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 inputGPXEntries = new GPXFile().doImport("./src/test/resources/testStartEndGeometry2.gpx").getEntries(); + MatchResult mr = mapMatching.doWork(inputGPXEntries, true); + + + NodeAccess nodeAccess = graph.getNodeAccess(); + + List edgeMatches = mr.getEdgeMatches(); + for (int i = 0; i < edgeMatches.size(); i++) { + EdgeMatch match = edgeMatches.get(i); + assertNotNull(match.fetchWayGeometry()); + if (i == 0) { + assertEquals(2, match.fetchWayGeometry().size()); + assertEquals(51.328439, match.fetchWayGeometry().getLat(0), 1E-5); + assertEquals(12.335785, match.fetchWayGeometry().getLon(0), 1E-5); + assertEquals(nodeAccess.getLat(mr.getEdgeMatches().get(0).getEdgeState().getAdjNode()), match.fetchWayGeometry().getLat(1), 1E-5); + assertEquals(nodeAccess.getLon(mr.getEdgeMatches().get(0).getEdgeState().getAdjNode()), match.fetchWayGeometry().getLon(1), 1E-5); + } else if (i == edgeMatches.size() - 1) { + assertEquals(4, match.fetchWayGeometry().size()); + assertEquals(nodeAccess.getLat(mr.getEdgeMatches().get(mr.getEdgeMatches().size() - 1).getEdgeState().getBaseNode()), match.fetchWayGeometry().getLat(0), 1E-5); + assertEquals(nodeAccess.getLon(mr.getEdgeMatches().get(mr.getEdgeMatches().size() - 1).getEdgeState().getBaseNode()), match.fetchWayGeometry().getLon(0), 1E-5); + assertEquals(51.364306, match.fetchWayGeometry().getLat(3), 1E-5); + assertEquals(12.459582, match.fetchWayGeometry().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 fetchStreets(List emList) { List list = new ArrayList(); int prevNode = -1; diff --git a/matching-core/src/test/resources/testStartEndGeometry.gpx b/matching-core/src/test/resources/testStartEndGeometry.gpx new file mode 100644 index 00000000..e886f928 --- /dev/null +++ b/matching-core/src/test/resources/testStartEndGeometry.gpx @@ -0,0 +1,12 @@ + +GraphHopper GPX +GraphHopper Track +116.0 +115.8 +115.2 +115.0 +116.8 +116.8 + + + \ No newline at end of file diff --git a/matching-core/src/test/resources/testStartEndGeometry2.gpx b/matching-core/src/test/resources/testStartEndGeometry2.gpx new file mode 100644 index 00000000..755c73f6 --- /dev/null +++ b/matching-core/src/test/resources/testStartEndGeometry2.gpx @@ -0,0 +1,132 @@ + +GraphHopper GPX +GraphHopper Track +116.9 +117.4 +117.4 +119.4 +118.0 +115.4 +114.4 +114.4 +114.4 +112.2 +112.2 +112.2 +111.8 +112.2 +112.2 +112.6 +111.0 +111.0 +111.0 +110.2 +110.6 +110.0 +110.0 +110.0 +110.0 +104.6 +105.6 +105.6 +108.0 +109.4 +109.8 +110.0 +110.4 +110.4 +111.2 +113.4 +113.4 +113.4 +113.2 +109.6 +110.8 +111.0 +111.0 +111.0 +114.6 +117.2 +116.0 +114.0 +113.6 +113.6 +110.8 +110.8 +110.8 +111.2 +112.2 +112.2 +112.2 +114.6 +113.4 +113.4 +114.4 +113.8 +113.8 +113.8 +113.8 +113.8 +114.2 +114.2 +113.0 +113.0 +113.0 +113.0 +113.2 +113.2 +112.0 +112.2 +112.2 +112.2 +112.2 +112.2 +112.2 +112.4 +112.4 +112.4 +112.4 +112.8 +113.0 +111.8 +111.8 +109.8 +109.8 +110.8 +115.4 +120.2 +119.6 +119.4 +121.0 +121.0 +122.6 +123.0 +119.4 +120.8 +121.6 +122.8 +122.8 +123.2 +124.0 +125.8 +126.0 +126.6 +127.2 +128.0 +127.6 +131.2 +128.4 +128.8 +128.4 +128.6 +127.4 +127.4 +127.4 +127.2 +127.2 +126.8 +126.4 +126.5 + + + \ No newline at end of file diff --git a/matching-core/src/test/resources/testStartEndGeometryOneEdge.gpx b/matching-core/src/test/resources/testStartEndGeometryOneEdge.gpx new file mode 100644 index 00000000..e6dcb50f --- /dev/null +++ b/matching-core/src/test/resources/testStartEndGeometryOneEdge.gpx @@ -0,0 +1,8 @@ + +GraphHopper GPX +GraphHopper Track +116.0 +116.0 + + + \ No newline at end of file diff --git a/matching-web/src/main/java/com/graphhopper/matching/http/MatchResultToJson.java b/matching-web/src/main/java/com/graphhopper/matching/http/MatchResultToJson.java index bbecde90..648ca048 100644 --- a/matching-web/src/main/java/com/graphhopper/matching/http/MatchResultToJson.java +++ b/matching-web/src/main/java/com/graphhopper/matching/http/MatchResultToJson.java @@ -40,7 +40,7 @@ public JSONObject exportTo() { JSONObject geometry = new JSONObject(); EdgeMatch edgeMatch = result.getEdgeMatches().get(emIndex); - PointList pointList = edgeMatch.getEdgeState().fetchWayGeometry(emIndex == 0 ? 3 : 2); + PointList pointList = edgeMatch.fetchWayGeometry(emIndex == 0 ? 3 : 2); if(pointList.size() < 2) { geometry.put("coordinates", pointList.toGeoJson().get(0));