diff --git a/CHANGELOG.md b/CHANGELOG.md index 4eda9dfa8b1..82f72125b27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +### 5.0 [not yet released] + ### 4.0 [29 Sep 2021] - faster node-based CH preparation (~20%), (#2390) diff --git a/client-hc/pom.xml b/client-hc/pom.xml index 1d07077fe74..4f85b8c648c 100644 --- a/client-hc/pom.xml +++ b/client-hc/pom.xml @@ -22,21 +22,21 @@ 4.0.0 directions-api-client-hc - 4.9-SNAPSHOT + 5.0-SNAPSHOT jar GraphHopper Directions API hand-crafted Java Client. com.github.GIScience.graphhopper graphhopper-parent - 4.9-SNAPSHOT + 5.0-SNAPSHOT com.github.GIScience.graphhopper graphhopper-web-api - 4.9-SNAPSHOT + 5.0-SNAPSHOT com.squareup.okhttp3 diff --git a/config-example.yml b/config-example.yml index 0432d559945..a933e485550 100644 --- a/config-example.yml +++ b/config-example.yml @@ -164,9 +164,9 @@ graphhopper: # custom_areas.directory: path/to/custom_areas ##### Country Rules ##### - # GraphHopper applies country-specific routing rules (enabled by default) during import. - # Use this flag to disable these rules (you need to repeat the import for changes to take effect) - # country_rules.enable: false + # GraphHopper applies country-specific routing rules during import (not enabled by default). + # You need to redo the import for changes to take effect. + # country_rules.enabled: true # Dropwizard server configuration server: diff --git a/core/pom.xml b/core/pom.xml index 89f60d17b18..4b6131b7293 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -5,7 +5,7 @@ graphhopper-core GraphHopper Core - 4.12-SNAPSHOT + 5.0-SNAPSHOT jar GraphHopper is a fast and memory efficient Java road routing engine @@ -14,7 +14,7 @@ com.github.GIScience.graphhopper graphhopper-parent - 4.12-SNAPSHOT + 5.0-SNAPSHOT @@ -38,7 +38,7 @@ com.github.GIScience.graphhopper graphhopper-web-api - 4.12-SNAPSHOT + 5.0-SNAPSHOT com.carrotsearch diff --git a/core/src/main/java/com/graphhopper/GraphHopper.java b/core/src/main/java/com/graphhopper/GraphHopper.java index 69f9efeabd5..42ba7e36cc6 100644 --- a/core/src/main/java/com/graphhopper/GraphHopper.java +++ b/core/src/main/java/com/graphhopper/GraphHopper.java @@ -493,10 +493,7 @@ public GraphHopper init(GraphHopperConfig ghConfig) { graphHopperFolder = pruneFileEnd(osmFile) + "-gh"; } - if (ghConfig.has("country_rules.enabled")) { - boolean countryRulesEnabled = ghConfig.getBool("country_rules.enabled", false); - countryRuleFactory = countryRulesEnabled ? new CountryRuleFactory() : null; - } + countryRuleFactory = ghConfig.getBool("country_rules.enabled", false) ? new CountryRuleFactory() : null; customAreasDirectory = ghConfig.getString("custom_areas.directory", customAreasDirectory); // graph diff --git a/core/src/main/java/com/graphhopper/routing/HeadingResolver.java b/core/src/main/java/com/graphhopper/routing/HeadingResolver.java index 1548ffabba5..db22126a898 100644 --- a/core/src/main/java/com/graphhopper/routing/HeadingResolver.java +++ b/core/src/main/java/com/graphhopper/routing/HeadingResolver.java @@ -19,12 +19,14 @@ package com.graphhopper.routing; import com.carrotsearch.hppc.IntArrayList; +import com.graphhopper.routing.util.EdgeFilter; import com.graphhopper.storage.Graph; import com.graphhopper.util.*; +import com.graphhopper.util.shapes.GHPoint; public class HeadingResolver { private final EdgeExplorer edgeExplorer; - private double toleranceRad = deg2Rad(100); + private double toleranceRad = Math.toRadians(100); public HeadingResolver(Graph graph) { this.edgeExplorer = graph.createEdgeExplorer(); @@ -63,12 +65,7 @@ public IntArrayList getEdgesWithDifferentHeading(int baseNode, double heading) { * Sets the tolerance for {@link #getEdgesWithDifferentHeading} in degrees. */ public HeadingResolver setTolerance(double tolerance) { - this.toleranceRad = deg2Rad(tolerance); + this.toleranceRad = Math.toRadians(tolerance); return this; } - - private static double deg2Rad(double deg) { - return Math.toRadians(deg); - } - } diff --git a/core/src/main/java/com/graphhopper/routing/Router.java b/core/src/main/java/com/graphhopper/routing/Router.java index 086394e1b17..7e2c11f1d36 100644 --- a/core/src/main/java/com/graphhopper/routing/Router.java +++ b/core/src/main/java/com/graphhopper/routing/Router.java @@ -24,6 +24,7 @@ import com.graphhopper.ResponsePath; import com.graphhopper.config.Profile; import com.graphhopper.routing.ch.CHRoutingAlgorithmFactory; +import com.graphhopper.routing.ev.BooleanEncodedValue; import com.graphhopper.routing.ev.EncodedValueLookup; import com.graphhopper.routing.ev.Subnetwork; import com.graphhopper.routing.lm.LMRoutingAlgorithmFactory; @@ -176,12 +177,12 @@ private void checkHeadings(GHRequest request) { } private void checkPointHints(GHRequest request) { - if (request.getPointHints().size() > 0 && request.getPointHints().size() != request.getPoints().size()) + if (!request.getPointHints().isEmpty() && request.getPointHints().size() != request.getPoints().size()) throw new IllegalArgumentException("If you pass " + POINT_HINT + ", you need to pass exactly one hint for every point, empty hints will be ignored"); } private void checkCurbsides(GHRequest request) { - if (request.getCurbsides().size() > 0 && request.getCurbsides().size() != request.getPoints().size()) + if (!request.getCurbsides().isEmpty() && request.getCurbsides().size() != request.getPoints().size()) throw new IllegalArgumentException("If you pass " + CURBSIDE + ", you need to pass exactly one curbside for every point, empty curbsides will be ignored"); } @@ -207,7 +208,7 @@ protected GHResponse routeRoundTrip(GHRequest request, FlexSolver solver) { StopWatch sw = new StopWatch().start(); double startHeading = request.getHeadings().isEmpty() ? Double.NaN : request.getHeadings().get(0); RoundTripRouting.Params params = new RoundTripRouting.Params(request.getHints(), startHeading, routerConfig.getMaxRoundTripRetries()); - List snaps = RoundTripRouting.lookup(request.getPoints(), solver.getSnapFilter(), locationIndex, params); + List snaps = RoundTripRouting.lookup(request.getPoints(), solver.createSnapFilter(), locationIndex, params); ghRsp.addDebugInfo("idLookup:" + sw.stop().getSeconds() + "s"); // ORS-GH MOD START - additional code checkMaxSearchDistances(request, ghRsp, snaps); @@ -259,7 +260,8 @@ protected GHResponse routeAlt(GHRequest request, Solver solver) { throw new IllegalArgumentException("Currently alternative routes work only with start and end point. You tried to use: " + request.getPoints().size() + " points"); GHResponse ghRsp = new GHResponse(); StopWatch sw = new StopWatch().start(); - List snaps = ViaRouting.lookup(encodingManager, request.getPoints(), solver.getSnapFilter(), locationIndex, request.getSnapPreventions(), request.getPointHints()); + List snaps = ViaRouting.lookup(encodingManager, request.getPoints(), solver.createSnapFilter(), locationIndex, + request.getSnapPreventions(), request.getPointHints(), solver.createDirectedSnapFilter(), request.getHeadings()); ghRsp.addDebugInfo("idLookup:" + sw.stop().getSeconds() + "s"); // ORS-GH MOD START - additional code checkMaxSearchDistances(request, ghRsp, snaps); @@ -308,7 +310,8 @@ protected GHResponse routeAlt(GHRequest request, Solver solver) { protected GHResponse routeVia(GHRequest request, Solver solver) { GHResponse ghRsp = new GHResponse(); StopWatch sw = new StopWatch().start(); - List snaps = ViaRouting.lookup(encodingManager, request.getPoints(), solver.getSnapFilter(), locationIndex, request.getSnapPreventions(), request.getPointHints()); + List snaps = ViaRouting.lookup(encodingManager, request.getPoints(), solver.createSnapFilter(), locationIndex, + request.getSnapPreventions(), request.getPointHints(), solver.createDirectedSnapFilter(), request.getHeadings()); ghRsp.addDebugInfo("idLookup:" + sw.stop().getSeconds() + "s"); // ORS-GH MOD START - additional code checkMaxSearchDistances(request, ghRsp, snaps); @@ -321,7 +324,8 @@ protected GHResponse routeVia(GHRequest request, Solver solver) { boolean forceCurbsides = getForceCurbsides(request.getHints()); // ORS-GH MOD START: enable TD routing long time = getTime(request.getHints()); - ViaRouting.Result result = ViaRouting.calcPaths(request.getPoints(), queryGraph, snaps, solver.weighting, pathCalculator, request.getCurbsides(), forceCurbsides, request.getHeadings(), passThrough, time); + ViaRouting.Result result = ViaRouting.calcPaths(request.getPoints(), queryGraph, snaps, solver.weighting, + pathCalculator, request.getCurbsides(), forceCurbsides, request.getHeadings(), passThrough, time); // ORS-GH MOD END if (request.getPoints().size() != result.paths.size() + 1) @@ -490,10 +494,15 @@ protected void checkProfileCompatibility() { protected abstract Weighting createWeighting(); - protected EdgeFilter getSnapFilter() { + protected EdgeFilter createSnapFilter() { return new DefaultSnapFilter(weighting, lookup.getBooleanEncodedValue(Subnetwork.key(profile.getName()))); } + protected EdgeFilter createDirectedSnapFilter() { + BooleanEncodedValue inSubnetworkEnc = lookup.getBooleanEncodedValue(Subnetwork.key(profile.getName())); + return edgeState -> !edgeState.get(inSubnetworkEnc) && Double.isFinite(weighting.calcEdgeWeightWithAccess(edgeState, false)); + } + protected abstract PathCalculator createPathCalculator(QueryGraph queryGraph); private List getTurnCostProfiles() { @@ -611,7 +620,7 @@ protected FlexiblePathCalculator createPathCalculator(QueryGraph queryGraph) { // ORS-GH MOD START: pass edgeFilter @Override - protected EdgeFilter getSnapFilter() { + protected EdgeFilter createSnapFilter() { EdgeFilter defaultSnapFilter = new DefaultSnapFilter(weighting, lookup.getBooleanEncodedValue(Subnetwork.key(profile.getName()))); if (edgeFilterFactory != null) return edgeFilterFactory.createEdgeFilter(request.getAdditionalHints(), weighting.getFlagEncoder(), ghStorage, defaultSnapFilter); diff --git a/core/src/main/java/com/graphhopper/routing/ViaRouting.java b/core/src/main/java/com/graphhopper/routing/ViaRouting.java index 8f9eb474b49..66ea5751f01 100644 --- a/core/src/main/java/com/graphhopper/routing/ViaRouting.java +++ b/core/src/main/java/com/graphhopper/routing/ViaRouting.java @@ -24,7 +24,7 @@ import com.graphhopper.routing.ev.RoadEnvironment; import com.graphhopper.routing.querygraph.QueryGraph; import com.graphhopper.routing.util.EdgeFilter; -import com.graphhopper.routing.util.FiniteWeightFilter; +import com.graphhopper.routing.util.HeadingEdgeFilter; import com.graphhopper.routing.util.NameSimilarityEdgeFilter; import com.graphhopper.routing.util.SnapPreventionEdgeFilter; import com.graphhopper.routing.weighting.Weighting; @@ -54,27 +54,36 @@ public class ViaRouting { /** * @throws MultiplePointsNotFoundException in case one or more points could not be resolved */ - public static List lookup(EncodedValueLookup lookup, List points, EdgeFilter edgeFilter, LocationIndex locationIndex, List snapPreventions, List pointHints) { + public static List lookup(EncodedValueLookup lookup, List points, EdgeFilter snapFilter, + LocationIndex locationIndex, List snapPreventions, List pointHints, + EdgeFilter directedSnapFilter, List headings) { if (points.size() < 2) throw new IllegalArgumentException("At least 2 points have to be specified, but was:" + points.size()); final EnumEncodedValue roadClassEnc = lookup.getEnumEncodedValue(RoadClass.KEY, RoadClass.class); final EnumEncodedValue roadEnvEnc = lookup.getEnumEncodedValue(RoadEnvironment.KEY, RoadEnvironment.class); EdgeFilter strictEdgeFilter = snapPreventions.isEmpty() - ? edgeFilter - : new SnapPreventionEdgeFilter(edgeFilter, roadClassEnc, roadEnvEnc, snapPreventions); + ? snapFilter + : new SnapPreventionEdgeFilter(snapFilter, roadClassEnc, roadEnvEnc, snapPreventions); List snaps = new ArrayList<>(points.size()); IntArrayList pointsNotFound = new IntArrayList(); for (int placeIndex = 0; placeIndex < points.size(); placeIndex++) { GHPoint point = points.get(placeIndex); Snap snap = null; - if (!pointHints.isEmpty()) + if (placeIndex < headings.size() && !Double.isNaN(headings.get(placeIndex))) { + if (!pointHints.isEmpty() && !Helper.isEmpty(pointHints.get(placeIndex))) + throw new IllegalArgumentException("Cannot specify heading and point_hint at the same time. " + + "Make sure you specify either an empty point_hint (String) or a NaN heading (double) for point " + placeIndex); + snap = locationIndex.findClosest(point.lat, point.lon, new HeadingEdgeFilter(directedSnapFilter, headings.get(placeIndex), point)); + } else if (!pointHints.isEmpty()) { snap = locationIndex.findClosest(point.lat, point.lon, new NameSimilarityEdgeFilter(strictEdgeFilter, pointHints.get(placeIndex), point, 100)); - else if (!snapPreventions.isEmpty()) + } else if (!snapPreventions.isEmpty()) { snap = locationIndex.findClosest(point.lat, point.lon, strictEdgeFilter); + } + if (snap == null || !snap.isValid()) - snap = locationIndex.findClosest(point.lat, point.lon, edgeFilter); + snap = locationIndex.findClosest(point.lat, point.lon, snapFilter); if (!snap.isValid()) pointsNotFound.add(placeIndex); diff --git a/core/src/main/java/com/graphhopper/routing/util/HeadingEdgeFilter.java b/core/src/main/java/com/graphhopper/routing/util/HeadingEdgeFilter.java new file mode 100644 index 00000000000..a6b986fe5ad --- /dev/null +++ b/core/src/main/java/com/graphhopper/routing/util/HeadingEdgeFilter.java @@ -0,0 +1,68 @@ +package com.graphhopper.routing.util; + +import com.graphhopper.util.*; +import com.graphhopper.util.shapes.GHPoint; + +public class HeadingEdgeFilter implements EdgeFilter { + + private final double heading; + private final EdgeFilter directedEdgeFilter; + private final GHPoint pointNearHeading; + + public HeadingEdgeFilter(EdgeFilter directedEdgeFilter, double heading, GHPoint pointNearHeading) { + this.directedEdgeFilter = directedEdgeFilter; + this.heading = heading; + this.pointNearHeading = pointNearHeading; + } + + @Override + public boolean accept(EdgeIteratorState edgeState) { + final double tolerance = 30; + // we only accept edges that are not too far away. It might happen that only far away edges match the heading + // in which case we rather rely on the fallback snapping than return a match here. + final double maxDistance = 20; + double headingOfEdge = getHeadingOfGeometryNearPoint(edgeState, pointNearHeading, maxDistance); + if (Double.isNaN(headingOfEdge)) + // this edge is too far away. we do not accept it. + return false; + // we accept the edge if either of the two directions roughly has the right heading + return Math.abs(headingOfEdge - heading) < tolerance && directedEdgeFilter.accept(edgeState) || + Math.abs((headingOfEdge + 180) % 360 - heading) < tolerance && directedEdgeFilter.accept(edgeState.detach(true)); + } + + /** + * Calculates the heading (in degrees) of the given edge in fwd direction near the given point. If the point is + * too far away from the edge (according to the maxDistance parameter) it returns Double.NaN. + */ + static double getHeadingOfGeometryNearPoint(EdgeIteratorState edgeState, GHPoint point, double maxDistance) { + final DistanceCalc calcDist = DistanceCalcEarth.DIST_EARTH; + double closestDistance = Double.POSITIVE_INFINITY; + PointList points = edgeState.fetchWayGeometry(FetchMode.ALL); + int closestPoint = -1; + for (int i = 1; i < points.size(); i++) { + double fromLat = points.getLat(i - 1), fromLon = points.getLon(i - 1); + double toLat = points.getLat(i), toLon = points.getLon(i); + // the 'distance' between the point and an edge segment is either the vertical distance to the segment or + // the distance to the closer one of the two endpoints. here we save one call to calcDist per segment, + // because each endpoint appears in two segments (except the first and last). + double distance = calcDist.validEdgeDistance(point.lat, point.lon, fromLat, fromLon, toLat, toLon) + ? calcDist.calcDenormalizedDist(calcDist.calcNormalizedEdgeDistance(point.lat, point.lon, fromLat, fromLon, toLat, toLon)) + : calcDist.calcDist(fromLat, fromLon, point.lat, point.lon); + if (i == points.size() - 1) + distance = Math.min(distance, calcDist.calcDist(toLat, toLon, point.lat, point.lon)); + if (distance > maxDistance) + continue; + if (distance < closestDistance) { + closestDistance = distance; + closestPoint = i; + } + } + if (closestPoint < 0) + return Double.NaN; + + double fromLat = points.getLat(closestPoint - 1), fromLon = points.getLon(closestPoint - 1); + double toLat = points.getLat(closestPoint), toLon = points.getLon(closestPoint); + // calcOrientation returns value relative to East, but heading is relative to North + return (Math.toDegrees(AngleCalc.ANGLE_CALC.calcOrientation(fromLat, fromLon, toLat, toLon)) + 90) % 360; + } +} diff --git a/core/src/main/java/com/graphhopper/storage/BaseGraph.java b/core/src/main/java/com/graphhopper/storage/BaseGraph.java index 9658aaefe7e..5f1b3ef323b 100644 --- a/core/src/main/java/com/graphhopper/storage/BaseGraph.java +++ b/core/src/main/java/com/graphhopper/storage/BaseGraph.java @@ -26,7 +26,6 @@ import com.graphhopper.util.shapes.BBox; import java.util.Collections; -import java.util.Locale; import static com.graphhopper.util.Helper.nf; @@ -42,110 +41,40 @@ * loadExisting, (4) usage, (5) flush, (6) close */ class BaseGraph implements Graph { - // Currently distances are stored as 4 byte integers. using a conversion factor of 1000 the minimum distance - // that is not considered zero is 0.0005m (=0.5mm) and the maximum distance per edge is about 2.147.483m=2147km. - // See OSMReader.addEdge and #1871. - private static final double INT_DIST_FACTOR = 1000d; - static double MAX_DIST = Integer.MAX_VALUE / INT_DIST_FACTOR; - - final DataAccess edges; - final DataAccess nodes; - final BBox bounds; - final NodeAccess nodeAccess; private final static String STRING_IDX_NAME_KEY = "name"; + final BaseGraphNodesAndEdges store; + final NodeAccess nodeAccess; final StringIndex stringIndex; // can be null if turn costs are not supported final TurnCostStorage turnCostStorage; final BitUtil bitUtil; - private final int intsForFlags; // length | nodeA | nextNode | ... | nodeB - // as we use integer index in 'egdes' area => 'geometry' area is limited to 4GB (we use pos&neg values!) + // as we use integer index in 'edges' area => 'geometry' area is limited to 4GB (we use pos&neg values!) private final DataAccess wayGeometry; private final Directory dir; - /** - * interval [0,n) - */ - protected int edgeCount; - // node memory layout: - protected int N_EDGE_REF, N_LAT, N_LON, N_ELE, N_TC; - // edge memory layout: - int E_NODEA, E_NODEB, E_LINKA, E_LINKB, E_FLAGS, E_DIST, E_GEO, E_NAME; - /** - * Specifies how many entries (integers) are used per edge. - */ - int edgeEntryBytes; - /** - * Specifies how many entries (integers) are used per node - */ - int nodeEntryBytes; private boolean initialized = false; - /** - * interval [0,n) - */ - private int nodeCount; - private int edgeEntryIndex, nodeEntryIndex; private long maxGeoRef; - private boolean frozen = false; public BaseGraph(Directory dir, int intsForFlags, boolean withElevation, boolean withTurnCosts, int segmentSize) { this.dir = dir; - this.intsForFlags = intsForFlags; this.bitUtil = BitUtil.LITTLE; this.wayGeometry = dir.create("geometry", segmentSize); this.stringIndex = new StringIndex(dir, 1000, segmentSize); - this.nodes = dir.create("nodes", dir.getDefaultType("nodes", true), segmentSize); - this.edges = dir.create("edges", dir.getDefaultType("edges", true), segmentSize); - this.bounds = BBox.createInverse(withElevation); - this.nodeAccess = new GHNodeAccess(this, withElevation); + this.store = new BaseGraphNodesAndEdges(dir, intsForFlags, withElevation, withTurnCosts, segmentSize); + this.nodeAccess = new GHNodeAccess(store); this.turnCostStorage = withTurnCosts ? new TurnCostStorage(this, dir.create("turn_costs", dir.getDefaultType("turn_costs", true), segmentSize)) : null; if (segmentSize >= 0) { checkNotInitialized(); } } - private void setEdgeRef(int nodeId, int edgeId) { - nodes.setInt(toNodePointer(nodeId) + N_EDGE_REF, edgeId); - } - - int getEdgeRef(int nodeId) { - return nodes.getInt(toNodePointer(nodeId) + N_EDGE_REF); - } - - private int getNodeA(long edgePointer) { - return edges.getInt(edgePointer + E_NODEA); - } - - private int getNodeB(long edgePointer) { - return edges.getInt(edgePointer + E_NODEB); - } - - private int getLinkA(long edgePointer) { - return edges.getInt(edgePointer + E_LINKA); - } - - private int getLinkB(long edgePointer) { - return edges.getInt(edgePointer + E_LINKB); - } - - long toNodePointer(int node) { - if (node < 0 || node >= nodeCount) - throw new IllegalArgumentException("node: " + node + " out of bounds [0," + nodeCount + "["); - return (long) node * nodeEntryBytes; - } - - private long toEdgePointer(int edge) { - if (edge < 0 || edge >= edgeCount) - throw new IllegalArgumentException("edge: " + edge + " out of bounds [0," + edgeCount + "["); - return (long) edge * edgeEntryBytes; - } - private int getOtherNode(int nodeThis, long edgePointer) { - int nodeA = getNodeA(edgePointer); - return nodeThis == nodeA ? getNodeB(edgePointer) : nodeA; + int nodeA = store.getNodeA(edgePointer); + return nodeThis == nodeA ? store.getNodeB(edgePointer) : nodeA; } private boolean isAdjacentToNode(int node, long edgePointer) { - return getNodeA(edgePointer) == node || getNodeB(edgePointer) == node; + return store.getNodeA(edgePointer) == node || store.getNodeB(edgePointer) == node; } private static boolean isTestingEnabled() { @@ -165,90 +94,16 @@ void checkNotInitialized() { + "after calling create or loadExisting. Calling one of the methods twice is also not allowed."); } - void checkInitialized() { - if (!initialized) - throw new IllegalStateException("The graph has not yet been initialized."); - } - - private void loadNodesHeader() { - nodeEntryBytes = nodes.getHeader(0 * 4); - nodeCount = nodes.getHeader(1 * 4); - bounds.minLon = Helper.intToDegree(nodes.getHeader(2 * 4)); - bounds.maxLon = Helper.intToDegree(nodes.getHeader(3 * 4)); - bounds.minLat = Helper.intToDegree(nodes.getHeader(4 * 4)); - bounds.maxLat = Helper.intToDegree(nodes.getHeader(5 * 4)); - - if (bounds.hasElevation()) { - bounds.minEle = Helper.intToEle(nodes.getHeader(6 * 4)); - bounds.maxEle = Helper.intToEle(nodes.getHeader(7 * 4)); - } - - frozen = nodes.getHeader(8 * 4) == 1; - } - - private void setNodesHeader() { - nodes.setHeader(0 * 4, nodeEntryBytes); - nodes.setHeader(1 * 4, nodeCount); - nodes.setHeader(2 * 4, Helper.degreeToInt(bounds.minLon)); - nodes.setHeader(3 * 4, Helper.degreeToInt(bounds.maxLon)); - nodes.setHeader(4 * 4, Helper.degreeToInt(bounds.minLat)); - nodes.setHeader(5 * 4, Helper.degreeToInt(bounds.maxLat)); - if (bounds.hasElevation()) { - nodes.setHeader(6 * 4, Helper.eleToInt(bounds.minEle)); - nodes.setHeader(7 * 4, Helper.eleToInt(bounds.maxEle)); - } - - nodes.setHeader(8 * 4, isFrozen() ? 1 : 0); - } - - protected void loadEdgesHeader() { - edgeEntryBytes = edges.getHeader(0 * 4); - edgeCount = edges.getHeader(1 * 4); - } - - protected void setEdgesHeader() { - edges.setHeader(0, edgeEntryBytes); - edges.setHeader(1 * 4, edgeCount); - } - - protected int loadWayGeometryHeader() { + private void loadWayGeometryHeader() { maxGeoRef = bitUtil.combineIntsToLong(wayGeometry.getHeader(0), wayGeometry.getHeader(4)); - return 1; } - protected int setWayGeometryHeader() { + private void setWayGeometryHeader() { wayGeometry.setHeader(0, bitUtil.getIntLow(maxGeoRef)); wayGeometry.setHeader(4, bitUtil.getIntHigh(maxGeoRef)); - return 1; } - void initStorage() { - edgeEntryIndex = 0; - nodeEntryIndex = 0; - E_NODEA = nextEdgeEntryIndex(4); - E_NODEB = nextEdgeEntryIndex(4); - E_LINKA = nextEdgeEntryIndex(4); - E_LINKB = nextEdgeEntryIndex(4); - E_FLAGS = nextEdgeEntryIndex(intsForFlags * 4); - - E_DIST = nextEdgeEntryIndex(4); - E_GEO = nextEdgeEntryIndex(4); - E_NAME = nextEdgeEntryIndex(4); - - N_EDGE_REF = nextNodeEntryIndex(4); - N_LAT = nextNodeEntryIndex(4); - N_LON = nextNodeEntryIndex(4); - if (nodeAccess.is3D()) - N_ELE = nextNodeEntryIndex(4); - else - N_ELE = -1; - - if (supportsTurnCosts()) - N_TC = nextNodeEntryIndex(4); - else - N_TC = -1; - - initNodeAndEdgeEntrySize(); + private void setInitialized() { initialized = true; } @@ -256,64 +111,14 @@ boolean supportsTurnCosts() { return turnCostStorage != null; } - /** - * Initializes the node storage such that each node has no edge and no turn cost entry - */ - void initNodeRefs(long oldCapacity, long newCapacity) { - for (long pointer = oldCapacity + N_EDGE_REF; pointer < newCapacity; pointer += nodeEntryBytes) { - nodes.setInt(pointer, EdgeIterator.NO_EDGE); - } - if (supportsTurnCosts()) { - for (long pointer = oldCapacity + N_TC; pointer < newCapacity; pointer += nodeEntryBytes) { - nodes.setInt(pointer, TurnCostStorage.NO_TURN_ENTRY); - } - } - } - - protected final int nextEdgeEntryIndex(int sizeInBytes) { - int tmp = edgeEntryIndex; - edgeEntryIndex += sizeInBytes; - return tmp; - } - - protected final int nextNodeEntryIndex(int sizeInBytes) { - int tmp = nodeEntryIndex; - nodeEntryIndex += sizeInBytes; - return tmp; - } - - protected final void initNodeAndEdgeEntrySize() { - nodeEntryBytes = nodeEntryIndex; - edgeEntryBytes = edgeEntryIndex; - } - - /** - * Check if byte capacity of DataAcess nodes object is sufficient to include node index, else - * extend byte capacity - */ - final void ensureNodeIndex(int nodeIndex) { - checkInitialized(); - - if (nodeIndex < nodeCount) - return; - - long oldNodes = nodeCount; - nodeCount = nodeIndex + 1; - boolean capacityIncreased = nodes.ensureCapacity((long) nodeCount * nodeEntryBytes); - if (capacityIncreased) { - long newBytesCapacity = nodes.getCapacity(); - initNodeRefs(oldNodes * nodeEntryBytes, newBytesCapacity); - } - } - @Override public int getNodes() { - return nodeCount; + return store.getNodes(); } @Override public int getEdges() { - return edgeCount; + return store.getEdges(); } @Override @@ -323,23 +128,21 @@ public NodeAccess getNodeAccess() { @Override public BBox getBounds() { - return bounds; + return store.getBounds(); } synchronized void freeze() { if (isFrozen()) throw new IllegalStateException("base graph already frozen"); - - frozen = true; + store.setFrozen(true); } synchronized boolean isFrozen() { - return frozen; + return store.getFrozen(); } void create(long initSize) { - nodes.create(initSize); - edges.create(initSize); + store.create(initSize); initSize = Math.min(initSize, 2000); wayGeometry.create(initSize); @@ -347,51 +150,15 @@ void create(long initSize) { if (supportsTurnCosts()) { turnCostStorage.create(initSize); } - initStorage(); + setInitialized(); // 0 stands for no separate geoRef maxGeoRef = 4; - - initNodeRefs(0, nodes.getCapacity()); } String toDetailsString() { - return "edges:" + nf(edgeCount) + "(" + edges.getCapacity() / Helper.MB + "MB), " - + "nodes:" + nf(getNodes()) + "(" + nodes.getCapacity() / Helper.MB + "MB), " + return store.toDetailsString() + ", " + "name:(" + stringIndex.getCapacity() / Helper.MB + "MB), " - + "geo:" + nf(maxGeoRef) + "(" + wayGeometry.getCapacity() / Helper.MB + "MB), " - + "bounds:" + bounds; - } - - public void debugPrint() { - final int printMax = 100; - System.out.println("nodes:"); - String formatNodes = "%12s | %12s | %12s | %12s \n"; - System.out.format(Locale.ROOT, formatNodes, "#", "N_EDGE_REF", "N_LAT", "N_LON"); - NodeAccess nodeAccess = getNodeAccess(); - for (int i = 0; i < Math.min(nodeCount, printMax); ++i) { - System.out.format(Locale.ROOT, formatNodes, i, getEdgeRef(i), nodeAccess.getLat(i), nodeAccess.getLon(i)); - } - if (nodeCount > printMax) { - System.out.format(Locale.ROOT, " ... %d more nodes\n", nodeCount - printMax); - } - System.out.println("edges:"); - String formatEdges = "%12s | %12s | %12s | %12s | %12s | %12s | %12s \n"; - System.out.format(Locale.ROOT, formatEdges, "#", "E_NODEA", "E_NODEB", "E_LINKA", "E_LINKB", "E_FLAGS", "E_DIST"); - IntsRef intsRef = new IntsRef(intsForFlags); - for (int i = 0; i < Math.min(edgeCount, printMax); ++i) { - long edgePointer = toEdgePointer(i); - readFlags(edgePointer, intsRef); - System.out.format(Locale.ROOT, formatEdges, i, - getNodeA(edgePointer), - getNodeB(edgePointer), - getLinkA(edgePointer), - getLinkB(edgePointer), - intsRef, - getDist(edgePointer)); - } - if (edgeCount > printMax) { - System.out.printf(Locale.ROOT, " ... %d more edges", edgeCount - printMax); - } + + "geo:" + nf(maxGeoRef) + "(" + wayGeometry.getCapacity() / Helper.MB + "MB)"; } /** @@ -416,10 +183,7 @@ public void flush() { if (!stringIndex.isClosed()) stringIndex.flush(); - setNodesHeader(); - setEdgesHeader(); - edges.flush(); - nodes.flush(); + store.flush(); if (supportsTurnCosts()) { turnCostStorage.flush(); } @@ -430,15 +194,14 @@ public void close() { wayGeometry.close(); if (!stringIndex.isClosed()) stringIndex.close(); - edges.close(); - nodes.close(); + store.close(); if (supportsTurnCosts()) { turnCostStorage.close(); } } long getCapacity() { - return edges.getCapacity() + nodes.getCapacity() + stringIndex.getCapacity() + return store.getCapacity() + stringIndex.getCapacity() + wayGeometry.getCapacity() + (supportsTurnCosts() ? turnCostStorage.getCapacity() : 0); } @@ -447,16 +210,13 @@ long getMaxGeoRef() { } void loadExisting(String dim) { - if (!nodes.loadExisting()) - throw new IllegalStateException("Cannot load nodes. corrupt file or directory? " + dir); + if (!store.loadExisting()) + throw new IllegalStateException("Cannot load edges or nodes. corrupt file or directory? " + dir); if (!dim.equalsIgnoreCase("" + nodeAccess.getDimension())) throw new IllegalStateException("Configured dimension (" + nodeAccess.getDimension() + ") is not equal " + "to dimension of loaded graph (" + dim + ")"); - if (!edges.loadExisting()) - throw new IllegalStateException("Cannot load edges. corrupt file or directory? " + dir); - if (!wayGeometry.loadExisting()) throw new IllegalStateException("Cannot load geometry. corrupt file or directory? " + dir); @@ -466,12 +226,7 @@ void loadExisting(String dim) { if (supportsTurnCosts() && !turnCostStorage.loadExisting()) throw new IllegalStateException("Cannot load turn cost storage. corrupt file or directory? " + dir); - // first define header indices of this storage - initStorage(); - - // now load some properties from stored data - loadNodesHeader(); - loadEdgesHeader(); + setInitialized(); loadWayGeometryHeader(); } @@ -481,8 +236,8 @@ void loadExisting(String dim) { * @return the updated iterator the properties where copied to. */ EdgeIteratorState copyProperties(EdgeIteratorState from, EdgeIteratorStateImpl to) { - long edgePointer = toEdgePointer(to.getEdge()); - writeFlags(edgePointer, from.getFlags()); + long edgePointer = store.toEdgePointer(to.getEdge()); + store.writeFlags(edgePointer, from.getFlags()); // copy the rest with higher level API to.setDistance(from.getDistance()). @@ -501,71 +256,13 @@ EdgeIteratorState copyProperties(EdgeIteratorState from, EdgeIteratorStateImpl t public EdgeIteratorState edge(int nodeA, int nodeB) { if (isFrozen()) throw new IllegalStateException("Cannot create edge if graph is already frozen"); - - ensureNodeIndex(Math.max(nodeA, nodeB)); - int edgeId = internalEdgeAdd(nextEdgeId(), nodeA, nodeB); + int edgeId = store.edge(nodeA, nodeB); EdgeIteratorStateImpl edge = new EdgeIteratorStateImpl(this); boolean valid = edge.init(edgeId, nodeB); assert valid; return edge; } - /** - * Writes a new edge to the array of edges and adds it to the linked list of edges at nodeA and nodeB - */ - final int internalEdgeAdd(int newEdgeId, int nodeA, int nodeB) { - writeEdge(newEdgeId, nodeA, nodeB); - long edgePointer = toEdgePointer(newEdgeId); - - int edge = getEdgeRef(nodeA); - if (edge > EdgeIterator.NO_EDGE) - edges.setInt(E_LINKA + edgePointer, edge); - setEdgeRef(nodeA, newEdgeId); - - if (nodeA != nodeB) { - edge = getEdgeRef(nodeB); - if (edge > EdgeIterator.NO_EDGE) - edges.setInt(E_LINKB + edgePointer, edge); - setEdgeRef(nodeB, newEdgeId); - } - return newEdgeId; - } - - /** - * Writes plain edge information to the edges index - */ - private long writeEdge(int edgeId, int nodeA, int nodeB) { - if (!EdgeIterator.Edge.isValid(edgeId)) - throw new IllegalStateException("Cannot write edge with illegal ID:" + edgeId + "; nodeA:" + nodeA + ", nodeB:" + nodeB); - - long edgePointer = toEdgePointer(edgeId); - edges.setInt(edgePointer + E_NODEA, nodeA); - edges.setInt(edgePointer + E_NODEB, nodeB); - edges.setInt(edgePointer + E_LINKA, EdgeIterator.NO_EDGE); - edges.setInt(edgePointer + E_LINKB, EdgeIterator.NO_EDGE); - return edgePointer; - } - - // for test only - void setEdgeCount(int cnt) { - edgeCount = cnt; - } - - /** - * Determine next free edgeId and ensure byte capacity to store edge - * - * @return next free edgeId - */ - protected int nextEdgeId() { - int nextEdge = edgeCount; - edgeCount++; - if (edgeCount < 0) - throw new IllegalStateException("too many edges. new edge id would be negative. " + toString()); - - edges.ensureCapacity(((long) edgeCount + 1) * edgeEntryBytes); - return nextEdge; - } - @Override public EdgeIteratorState getEdgeIteratorState(int edgeId, int adjNode) { EdgeIteratorStateImpl edge = new EdgeIteratorStateImpl(this); @@ -582,11 +279,6 @@ public EdgeIteratorState getEdgeIteratorStateForKey(int edgeKey) { return edge; } - final void checkAdjNodeBounds(int adjNode) { - if (adjNode < 0 && adjNode != Integer.MIN_VALUE || adjNode >= nodeCount) - throw new IllegalStateException("adjNode " + adjNode + " out of bounds [0," + nf(nodeCount) + ")"); - } - @Override public EdgeExplorer createEdgeExplorer(EdgeFilter filter) { return new EdgeIteratorImpl(this, filter); @@ -614,64 +306,23 @@ public Weighting wrapWeighting(Weighting weighting) { @Override public int getOtherNode(int edge, int node) { - long edgePointer = toEdgePointer(edge); + long edgePointer = store.toEdgePointer(edge); return getOtherNode(node, edgePointer); } @Override public boolean isAdjacentToNode(int edge, int node) { - long edgePointer = toEdgePointer(edge); + long edgePointer = store.toEdgePointer(edge); return isAdjacentToNode(node, edgePointer); } - private void readFlags(long edgePointer, IntsRef edgeFlags) { - int size = edgeFlags.ints.length; - for (int i = 0; i < size; i++) { - edgeFlags.ints[i] = edges.getInt(edgePointer + E_FLAGS + i * 4); - } - } - - private void writeFlags(long edgePointer, IntsRef edgeFlags) { - int size = edgeFlags.ints.length; - for (int i = 0; i < size; i++) { - edges.setInt(edgePointer + E_FLAGS + i * 4, edgeFlags.ints[i]); - } - } - - private void setDist(long edgePointer, double distance) { - edges.setInt(edgePointer + E_DIST, distToInt(distance)); - } - - /** - * Translates double distance to integer in order to save it in a DataAccess object - */ - private int distToInt(double distance) { - if (distance < 0) - throw new IllegalArgumentException("Distance cannot be negative: " + distance); - if (distance > MAX_DIST) { - distance = MAX_DIST; - } - int integ = (int) Math.round(distance * INT_DIST_FACTOR); - assert integ >= 0 : "distance out of range"; - return integ; - } - - /** - * returns distance (already translated from integer to double) - */ - private double getDist(long pointer) { - int val = edges.getInt(pointer + E_DIST); - // do never return infinity even if INT MAX, see #435 - return val / INT_DIST_FACTOR; - } - private void setWayGeometry_(PointList pillarNodes, long edgePointer, boolean reverse) { if (pillarNodes != null && !pillarNodes.isEmpty()) { if (pillarNodes.getDimension() != nodeAccess.getDimension()) throw new IllegalArgumentException("Cannot use pointlist which is " + pillarNodes.getDimension() + "D for graph which is " + nodeAccess.getDimension() + "D"); - long existingGeoRef = Helper.toUnsignedLong(edges.getInt(edgePointer + E_GEO)); + long existingGeoRef = Helper.toUnsignedLong(store.getGeoRef(edgePointer)); int len = pillarNodes.size(); int dim = nodeAccess.getDimension(); @@ -686,7 +337,7 @@ private void setWayGeometry_(PointList pillarNodes, long edgePointer, boolean re long nextGeoRef = nextGeoRef(len * dim); setWayGeometryAtGeoRef(pillarNodes, edgePointer, reverse, nextGeoRef); } else { - edges.setInt(edgePointer + E_GEO, 0); + store.setGeoRef(edgePointer, 0); } } @@ -698,7 +349,7 @@ private void setWayGeometryAtGeoRef(PointList pillarNodes, long edgePointer, boo ensureGeometry(geoRefPosition, totalLen); byte[] wayGeometryBytes = createWayGeometryBytes(pillarNodes, reverse); wayGeometry.setBytes(geoRefPosition, wayGeometryBytes, wayGeometryBytes.length); - edges.setInt(edgePointer + E_GEO, Helper.toSignedInt(geoRef)); + store.setGeoRef(edgePointer, Helper.toSignedInt(geoRef)); } private byte[] createWayGeometryBytes(PointList pillarNodes, boolean reverse) { @@ -735,7 +386,7 @@ private PointList fetchWayGeometry_(long edgePointer, boolean reverse, FetchMode pillarNodes.add(nodeAccess, adjNode); return pillarNodes; } - long geoRef = Helper.toUnsignedLong(edges.getInt(edgePointer + E_GEO)); + long geoRef = Helper.toUnsignedLong(store.getGeoRef(edgePointer)); int count = 0; byte[] bytes = null; if (geoRef > 0) { @@ -799,8 +450,7 @@ private void setName(long edgePointer, String name) { int stringIndexRef = (int) stringIndex.add(Collections.singletonMap(STRING_IDX_NAME_KEY, name)); if (stringIndexRef < 0) throw new IllegalStateException("Too many names are stored, currently limited to int pointer"); - - edges.setInt(edgePointer + E_NAME, stringIndexRef); + store.setNameRef(edgePointer, stringIndexRef); } private void ensureGeometry(long bytePos, int byteLength) { @@ -816,6 +466,10 @@ private long nextGeoRef(int arrayLength) { return tmp; } + public boolean isClosed() { + return store.isClosed(); + } + protected static class EdgeIteratorImpl extends EdgeIteratorStateImpl implements EdgeExplorer, EdgeIterator { final EdgeFilter filter; int nextEdgeId; @@ -830,7 +484,7 @@ public EdgeIteratorImpl(BaseGraph baseGraph, EdgeFilter filter) { @Override public EdgeIterator setBaseNode(int baseNode) { - nextEdgeId = edgeId = baseGraph.getEdgeRef(baseNode); + nextEdgeId = edgeId = store.getEdgeRef(store.toNodePointer(baseNode)); this.baseNode = baseNode; return this; } @@ -846,16 +500,16 @@ public final boolean next() { } void goToNext() { - edgePointer = baseGraph.toEdgePointer(nextEdgeId); + edgePointer = store.toEdgePointer(nextEdgeId); edgeId = nextEdgeId; - int nodeA = baseGraph.getNodeA(edgePointer); + int nodeA = store.getNodeA(edgePointer); boolean baseNodeIsNodeA = baseNode == nodeA; - adjNode = baseNodeIsNodeA ? baseGraph.getNodeB(edgePointer) : nodeA; + adjNode = baseNodeIsNodeA ? store.getNodeB(edgePointer) : nodeA; reverse = !baseNodeIsNodeA; freshFlags = false; // position to next edge - nextEdgeId = baseNodeIsNodeA ? baseGraph.getLinkA(edgePointer) : baseGraph.getLinkB(edgePointer); + nextEdgeId = baseNodeIsNodeA ? store.getLinkA(edgePointer) : store.getLinkB(edgePointer); assert nextEdgeId != edgeId : ("endless loop detected for base node: " + baseNode + ", adj node: " + adjNode + ", edge pointer: " + edgePointer + ", edge: " + edgeId); } @@ -878,17 +532,17 @@ public AllEdgeIterator(BaseGraph baseGraph) { @Override public int length() { - return baseGraph.edgeCount; + return store.getEdges(); } @Override public boolean next() { edgeId++; - if (edgeId >= baseGraph.edgeCount) + if (edgeId >= store.getEdges()) return false; - edgePointer = baseGraph.toEdgePointer(edgeId); - baseNode = baseGraph.getNodeA(edgePointer); - adjNode = baseGraph.getNodeB(edgePointer); + edgePointer = store.toEdgePointer(edgeId); + baseNode = store.getNodeA(edgePointer); + adjNode = store.getNodeB(edgePointer); freshFlags = false; reverse = false; return true; @@ -917,6 +571,7 @@ public final EdgeIteratorState detach(boolean reverseArg) { static class EdgeIteratorStateImpl implements EdgeIteratorState { final BaseGraph baseGraph; + final BaseGraphNodesAndEdges store; long edgePointer = -1; int baseNode; int adjNode; @@ -928,19 +583,20 @@ static class EdgeIteratorStateImpl implements EdgeIteratorState { public EdgeIteratorStateImpl(BaseGraph baseGraph) { this.baseGraph = baseGraph; - this.edgeFlags = new IntsRef(baseGraph.intsForFlags); + this.edgeFlags = new IntsRef(baseGraph.store.getIntsForFlags()); + store = baseGraph.store; } /** * @return false if the edge has not a node equal to expectedAdjNode */ final boolean init(int edgeId, int expectedAdjNode) { - if (edgeId < 0 || edgeId >= baseGraph.edgeCount) - throw new IllegalArgumentException("edge: " + edgeId + " out of bounds: [0," + baseGraph.edgeCount + "["); + if (edgeId < 0 || edgeId >= store.getEdges()) + throw new IllegalArgumentException("edge: " + edgeId + " out of bounds: [0," + store.getEdges() + "["); this.edgeId = edgeId; - edgePointer = baseGraph.toEdgePointer(edgeId); - baseNode = baseGraph.getNodeA(edgePointer); - adjNode = baseGraph.getNodeB(edgePointer); + edgePointer = store.toEdgePointer(edgeId); + baseNode = store.getNodeA(edgePointer); + adjNode = store.getNodeB(edgePointer); freshFlags = false; if (expectedAdjNode == adjNode || expectedAdjNode == Integer.MIN_VALUE) { @@ -963,9 +619,9 @@ final void init(int edgeKey) { if (edgeKey < 0) throw new IllegalArgumentException("edge keys must not be negative, given: " + edgeKey); this.edgeId = GHUtility.getEdgeFromEdgeKey(edgeKey); - edgePointer = baseGraph.toEdgePointer(edgeId); - baseNode = baseGraph.getNodeA(edgePointer); - adjNode = baseGraph.getNodeB(edgePointer); + edgePointer = store.toEdgePointer(edgeId); + baseNode = store.getNodeA(edgePointer); + adjNode = store.getNodeB(edgePointer); freshFlags = false; if (edgeKey % 2 == 0 || baseNode == adjNode) { @@ -990,19 +646,19 @@ public final int getAdjNode() { @Override public double getDistance() { - return baseGraph.getDist(edgePointer); + return store.getDist(edgePointer); } @Override public EdgeIteratorState setDistance(double dist) { - baseGraph.setDist(edgePointer, dist); + store.setDist(edgePointer, dist); return this; } @Override public IntsRef getFlags() { if (!freshFlags) { - baseGraph.readFlags(edgePointer, edgeFlags); + store.readFlags(edgePointer, edgeFlags); freshFlags = true; } return edgeFlags; @@ -1010,8 +666,8 @@ public IntsRef getFlags() { @Override public final EdgeIteratorState setFlags(IntsRef edgeFlags) { - assert edgeId < baseGraph.edgeCount : "must be edge but was shortcut: " + edgeId + " >= " + baseGraph.edgeCount + ". Use setFlagsAndWeight"; - baseGraph.writeFlags(edgePointer, edgeFlags); + assert edgeId < store.getEdges() : "must be edge but was shortcut: " + edgeId + " >= " + store.getEdges() + ". Use setFlagsAndWeight"; + store.writeFlags(edgePointer, edgeFlags); for (int i = 0; i < edgeFlags.ints.length; i++) { this.edgeFlags.ints[i] = edgeFlags.ints[i]; } @@ -1027,7 +683,7 @@ public boolean get(BooleanEncodedValue property) { @Override public EdgeIteratorState set(BooleanEncodedValue property, boolean value) { property.setBool(reverse, getFlags(), value); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1039,7 +695,7 @@ public boolean getReverse(BooleanEncodedValue property) { @Override public EdgeIteratorState setReverse(BooleanEncodedValue property, boolean value) { property.setBool(!reverse, getFlags(), value); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1049,7 +705,7 @@ public EdgeIteratorState set(BooleanEncodedValue property, boolean fwd, boolean throw new IllegalArgumentException("EncodedValue " + property.getName() + " supports only one direction"); property.setBool(reverse, getFlags(), fwd); property.setBool(!reverse, getFlags(), bwd); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1061,7 +717,7 @@ public int get(IntEncodedValue property) { @Override public EdgeIteratorState set(IntEncodedValue property, int value) { property.setInt(reverse, getFlags(), value); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1073,7 +729,7 @@ public int getReverse(IntEncodedValue property) { @Override public EdgeIteratorState setReverse(IntEncodedValue property, int value) { property.setInt(!reverse, getFlags(), value); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1083,7 +739,7 @@ public EdgeIteratorState set(IntEncodedValue property, int fwd, int bwd) { throw new IllegalArgumentException("EncodedValue " + property.getName() + " supports only one direction"); property.setInt(reverse, getFlags(), fwd); property.setInt(!reverse, getFlags(), bwd); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1095,7 +751,7 @@ public double get(DecimalEncodedValue property) { @Override public EdgeIteratorState set(DecimalEncodedValue property, double value) { property.setDecimal(reverse, getFlags(), value); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1107,7 +763,7 @@ public double getReverse(DecimalEncodedValue property) { @Override public EdgeIteratorState setReverse(DecimalEncodedValue property, double value) { property.setDecimal(!reverse, getFlags(), value); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1117,7 +773,7 @@ public EdgeIteratorState set(DecimalEncodedValue property, double fwd, double bw throw new IllegalArgumentException("EncodedValue " + property.getName() + " supports only one direction"); property.setDecimal(reverse, getFlags(), fwd); property.setDecimal(!reverse, getFlags(), bwd); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1129,7 +785,7 @@ public > T get(EnumEncodedValue property) { @Override public > EdgeIteratorState set(EnumEncodedValue property, T value) { property.setEnum(reverse, getFlags(), value); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1141,7 +797,7 @@ public > T getReverse(EnumEncodedValue property) { @Override public > EdgeIteratorState setReverse(EnumEncodedValue property, T value) { property.setEnum(!reverse, getFlags(), value); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1151,7 +807,7 @@ public > EdgeIteratorState set(EnumEncodedValue property, T throw new IllegalArgumentException("EncodedValue " + property.getName() + " supports only one direction"); property.setEnum(reverse, getFlags(), fwd); property.setEnum(!reverse, getFlags(), bwd); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1163,7 +819,7 @@ public String get(StringEncodedValue property) { @Override public EdgeIteratorState set(StringEncodedValue property, String value) { property.setString(reverse, getFlags(), value); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1175,7 +831,7 @@ public String getReverse(StringEncodedValue property) { @Override public EdgeIteratorState setReverse(StringEncodedValue property, String value) { property.setString(!reverse, getFlags(), value); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1185,7 +841,7 @@ public EdgeIteratorState set(StringEncodedValue property, String fwd, String bwd throw new IllegalArgumentException("EncodedValue " + property.getName() + " supports only one direction"); property.setString(reverse, getFlags(), fwd); property.setString(!reverse, getFlags(), bwd); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1227,7 +883,7 @@ public int getOrigEdgeLast() { @Override public String getName() { - int stringIndexRef = baseGraph.edges.getInt(edgePointer + baseGraph.E_NAME); + int stringIndexRef = store.getNameRef(edgePointer); String name = baseGraph.stringIndex.get(stringIndexRef, STRING_IDX_NAME_KEY); // preserve backward compatibility (returns null if not explicitly set) return name == null ? "" : name; diff --git a/core/src/main/java/com/graphhopper/storage/BaseGraphNodesAndEdges.java b/core/src/main/java/com/graphhopper/storage/BaseGraphNodesAndEdges.java new file mode 100644 index 00000000000..77f7170a600 --- /dev/null +++ b/core/src/main/java/com/graphhopper/storage/BaseGraphNodesAndEdges.java @@ -0,0 +1,395 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.graphhopper.storage; + +import com.graphhopper.util.EdgeIterator; +import com.graphhopper.util.Helper; +import com.graphhopper.util.shapes.BBox; + +import java.util.Locale; + +import static com.graphhopper.util.EdgeIterator.NO_EDGE; +import static com.graphhopper.util.Helper.nf; + +/** + * Underlying storage for nodes and edges of {@link BaseGraph}. Nodes and edges are stored using two {@link DataAccess} + * instances. Nodes and edges are simply stored sequentially, see the memory layout in the constructor. + */ +class BaseGraphNodesAndEdges { + // Currently distances are stored as 4 byte integers. using a conversion factor of 1000 the minimum distance + // that is not considered zero is 0.0005m (=0.5mm) and the maximum distance per edge is about 2.147.483m=2147km. + // See OSMReader.addEdge and #1871. + private static final double INT_DIST_FACTOR = 1000d; + static double MAX_DIST = Integer.MAX_VALUE / INT_DIST_FACTOR; + + // nodes + private final DataAccess nodes; + private final int N_EDGE_REF, N_LAT, N_LON, N_ELE, N_TC; + private int nodeEntryBytes; + private int nodeCount; + + // edges + private final DataAccess edges; + private final int E_NODEA, E_NODEB, E_LINKA, E_LINKB, E_FLAGS, E_DIST, E_GEO, E_NAME; + private final int intsForFlags; + private int edgeEntryBytes; + private int edgeCount; + + private final boolean withTurnCosts; + private final boolean withElevation; + + // we do not write the bounding box directly to storage, but rather to this bbox object. we only write to storage + // when flushing. why? just because we did it like this in the past, and otherwise we run into rounding errors, + // because of: #2393 + public final BBox bounds; + private boolean frozen; + + public BaseGraphNodesAndEdges(Directory dir, int intsForFlags, boolean withElevation, boolean withTurnCosts, int segmentSize) { + this.nodes = dir.create("nodes", dir.getDefaultType("nodes", true), segmentSize); + this.edges = dir.create("edges", dir.getDefaultType("edges", true), segmentSize); + this.intsForFlags = intsForFlags; + this.withTurnCosts = withTurnCosts; + this.withElevation = withElevation; + bounds = BBox.createInverse(withElevation); + + // memory layout for nodes + N_EDGE_REF = 0; + N_LAT = 4; + N_LON = 8; + N_ELE = N_LON + (withElevation ? 4 : 0); + N_TC = N_ELE + (withTurnCosts ? 4 : 0); + nodeEntryBytes = N_TC + 4; + + // memory layout for edges + E_NODEA = 0; + E_NODEB = 4; + E_LINKA = 8; + E_LINKB = 12; + E_FLAGS = 16; + E_DIST = E_FLAGS + intsForFlags * 4; + E_GEO = E_DIST + 4; + E_NAME = E_GEO + 4; + edgeEntryBytes = E_NAME + 4; + } + + public void create(long initSize) { + nodes.create(initSize); + edges.create(initSize); + } + + public boolean loadExisting() { + if (!nodes.loadExisting() || !edges.loadExisting()) + return false; + + // now load some properties from stored data + nodeEntryBytes = nodes.getHeader(0 * 4); + nodeCount = nodes.getHeader(1 * 4); + bounds.minLon = Helper.intToDegree(nodes.getHeader(2 * 4)); + bounds.maxLon = Helper.intToDegree(nodes.getHeader(3 * 4)); + bounds.minLat = Helper.intToDegree(nodes.getHeader(4 * 4)); + bounds.maxLat = Helper.intToDegree(nodes.getHeader(5 * 4)); + if (withElevation) { + bounds.minEle = Helper.intToEle(nodes.getHeader(6 * 4)); + bounds.maxEle = Helper.intToEle(nodes.getHeader(7 * 4)); + } + frozen = nodes.getHeader(8 * 4) == 1; + + edgeEntryBytes = edges.getHeader(0 * 4); + edgeCount = edges.getHeader(1 * 4); + return true; + } + + public void flush() { + nodes.setHeader(0 * 4, nodeEntryBytes); + nodes.setHeader(1 * 4, nodeCount); + nodes.setHeader(2 * 4, Helper.degreeToInt(bounds.minLon)); + nodes.setHeader(3 * 4, Helper.degreeToInt(bounds.maxLon)); + nodes.setHeader(4 * 4, Helper.degreeToInt(bounds.minLat)); + nodes.setHeader(5 * 4, Helper.degreeToInt(bounds.maxLat)); + if (withElevation) { + nodes.setHeader(6 * 4, Helper.eleToInt(bounds.minEle)); + nodes.setHeader(7 * 4, Helper.eleToInt(bounds.maxEle)); + } + nodes.setHeader(8 * 4, frozen ? 1 : 0); + + edges.setHeader(0, edgeEntryBytes); + edges.setHeader(1 * 4, edgeCount); + + edges.flush(); + nodes.flush(); + } + + public void close() { + edges.close(); + nodes.close(); + } + + public int getNodes() { + return nodeCount; + } + + public int getEdges() { + return edgeCount; + } + + public int getIntsForFlags() { + return intsForFlags; + } + + public boolean withElevation() { + return withElevation; + } + + public boolean withTurnCosts() { + return withTurnCosts; + } + + public BBox getBounds() { + return bounds; + } + + public long getCapacity() { + return nodes.getCapacity() + edges.getCapacity(); + } + + public boolean isClosed() { + assert nodes.isClosed() == edges.isClosed(); + return nodes.isClosed(); + } + + public int edge(int nodeA, int nodeB) { + if (edgeCount == Integer.MAX_VALUE) + throw new IllegalStateException("Maximum edge count exceeded: " + edgeCount); + ensureNodeCapacity(Math.max(nodeA, nodeB)); + final int edge = edgeCount; + final long edgePointer = (long) edgeCount * edgeEntryBytes; + edgeCount++; + edges.ensureCapacity((long) edgeCount * edgeEntryBytes); + + setNodeA(edgePointer, nodeA); + setNodeB(edgePointer, nodeB); + // we keep a linked list of edges at each node. here we prepend the new edge at the already existing linked + // list of edges. + long nodePointerA = toNodePointer(nodeA); + int edgeRefA = getEdgeRef(nodePointerA); + setLinkA(edgePointer, EdgeIterator.Edge.isValid(edgeRefA) ? edgeRefA : NO_EDGE); + setEdgeRef(nodePointerA, edge); + + if (nodeA != nodeB) { + long nodePointerB = toNodePointer(nodeB); + int edgeRefB = getEdgeRef(nodePointerB); + setLinkB(edgePointer, EdgeIterator.Edge.isValid(edgeRefB) ? edgeRefB : NO_EDGE); + setEdgeRef(nodePointerB, edge); + } + return edge; + } + + public void ensureNodeCapacity(int node) { + if (node < nodeCount) + return; + + int oldNodes = nodeCount; + nodeCount = node + 1; + nodes.ensureCapacity((long) nodeCount * nodeEntryBytes); + for (int n = oldNodes; n < nodeCount; ++n) { + setEdgeRef(toNodePointer(n), NO_EDGE); + if (withTurnCosts) + setTurnCostRef(toNodePointer(n), TurnCostStorage.NO_TURN_ENTRY); + } + } + + public long toNodePointer(int node) { + if (node < 0 || node >= nodeCount) + throw new IllegalArgumentException("node: " + node + " out of bounds [0," + nodeCount + "["); + return (long) node * nodeEntryBytes; + } + + public long toEdgePointer(int edge) { + if (edge < 0 || edge >= edgeCount) + throw new IllegalArgumentException("edge: " + edge + " out of bounds [0," + edgeCount + "["); + return (long) edge * edgeEntryBytes; + } + + public void readFlags(long edgePointer, IntsRef edgeFlags) { + int size = edgeFlags.ints.length; + for (int i = 0; i < size; ++i) + edgeFlags.ints[i] = edges.getInt(edgePointer + E_FLAGS + i * 4); + } + + public void writeFlags(long edgePointer, IntsRef edgeFlags) { + int size = edgeFlags.ints.length; + for (int i = 0; i < size; ++i) + edges.setInt(edgePointer + E_FLAGS + i * 4, edgeFlags.ints[i]); + } + + public void setNodeA(long edgePointer, int nodeA) { + edges.setInt(edgePointer + E_NODEA, nodeA); + } + + public void setNodeB(long edgePointer, int nodeB) { + edges.setInt(edgePointer + E_NODEB, nodeB); + } + + public void setLinkA(long edgePointer, int linkA) { + edges.setInt(edgePointer + E_LINKA, linkA); + } + + public void setLinkB(long edgePointer, int linkB) { + edges.setInt(edgePointer + E_LINKB, linkB); + } + + public void setDist(long edgePointer, double distance) { + edges.setInt(edgePointer + E_DIST, distToInt(distance)); + } + + public void setGeoRef(long edgePointer, int geoRef) { + edges.setInt(edgePointer + E_GEO, geoRef); + } + + public void setNameRef(long edgePointer, int nameRef) { + edges.setInt(edgePointer + E_NAME, nameRef); + } + + public int getNodeA(long edgePointer) { + return edges.getInt(edgePointer + E_NODEA); + } + + public int getNodeB(long edgePointer) { + return edges.getInt(edgePointer + E_NODEB); + } + + public int getLinkA(long edgePointer) { + return edges.getInt(edgePointer + E_LINKA); + } + + public int getLinkB(long edgePointer) { + return edges.getInt(edgePointer + E_LINKB); + } + + public double getDist(long pointer) { + int val = edges.getInt(pointer + E_DIST); + // do never return infinity even if INT MAX, see #435 + return val / INT_DIST_FACTOR; + } + + public int getGeoRef(long edgePointer) { + return edges.getInt(edgePointer + E_GEO); + } + + public int getNameRef(long edgePointer) { + return edges.getInt(edgePointer + E_NAME); + } + + public void setEdgeRef(long nodePointer, int edgeRef) { + nodes.setInt(nodePointer + N_EDGE_REF, edgeRef); + } + + public void setLat(long nodePointer, double lat) { + nodes.setInt(nodePointer + N_LAT, Helper.degreeToInt(lat)); + } + + public void setLon(long nodePointer, double lon) { + nodes.setInt(nodePointer + N_LON, Helper.degreeToInt(lon)); + } + + public void setEle(long elePointer, double ele) { + nodes.setInt(elePointer + N_ELE, Helper.eleToInt(ele)); + } + + public void setTurnCostRef(long nodePointer, int tcRef) { + nodes.setInt(nodePointer + N_TC, tcRef); + } + + public int getEdgeRef(long nodePointer) { + return nodes.getInt(nodePointer + N_EDGE_REF); + } + + public double getLat(long nodePointer) { + return Helper.intToDegree(nodes.getInt(nodePointer + N_LAT)); + } + + public double getLon(long nodePointer) { + return Helper.intToDegree(nodes.getInt(nodePointer + N_LON)); + } + + public double getEle(long nodePointer) { + return Helper.intToEle(nodes.getInt(nodePointer + N_ELE)); + } + + public int getTurnCostRef(long nodePointer) { + return nodes.getInt(nodePointer + N_TC); + } + + public void setFrozen(boolean frozen) { + this.frozen = frozen; + } + + public boolean getFrozen() { + return frozen; + } + + public void debugPrint() { + final int printMax = 100; + System.out.println("nodes:"); + String formatNodes = "%12s | %12s | %12s | %12s \n"; + System.out.format(Locale.ROOT, formatNodes, "#", "N_EDGE_REF", "N_LAT", "N_LON"); + for (int i = 0; i < Math.min(nodeCount, printMax); ++i) { + long nodePointer = toNodePointer(i); + System.out.format(Locale.ROOT, formatNodes, i, getEdgeRef(nodePointer), getLat(nodePointer), getLon(nodePointer)); + } + if (nodeCount > printMax) { + System.out.format(Locale.ROOT, " ... %d more nodes\n", nodeCount - printMax); + } + System.out.println("edges:"); + String formatEdges = "%12s | %12s | %12s | %12s | %12s | %12s | %12s \n"; + System.out.format(Locale.ROOT, formatEdges, "#", "E_NODEA", "E_NODEB", "E_LINKA", "E_LINKB", "E_FLAGS", "E_DIST"); + IntsRef intsRef = new IntsRef(intsForFlags); + for (int i = 0; i < Math.min(edgeCount, printMax); ++i) { + long edgePointer = toEdgePointer(i); + readFlags(edgePointer, intsRef); + System.out.format(Locale.ROOT, formatEdges, i, + getNodeA(edgePointer), + getNodeB(edgePointer), + getLinkA(edgePointer), + getLinkB(edgePointer), + intsRef, + getDist(edgePointer)); + } + if (edgeCount > printMax) { + System.out.printf(Locale.ROOT, " ... %d more edges", edgeCount - printMax); + } + } + + private int distToInt(double distance) { + if (distance < 0) + throw new IllegalArgumentException("Distance cannot be negative: " + distance); + if (distance > MAX_DIST) { + distance = MAX_DIST; + } + int intDist = (int) Math.round(distance * INT_DIST_FACTOR); + assert intDist >= 0 : "distance out of range"; + return intDist; + } + + public String toDetailsString() { + return "edges: " + nf(edgeCount) + "(" + edges.getCapacity() / Helper.MB + "MB), " + + "nodes: " + nf(nodeCount) + "(" + nodes.getCapacity() / Helper.MB + "MB), " + + "bounds: " + bounds; + } +} diff --git a/core/src/main/java/com/graphhopper/storage/CHStorage.java b/core/src/main/java/com/graphhopper/storage/CHStorage.java index 90d97205fc6..577e11e0988 100644 --- a/core/src/main/java/com/graphhopper/storage/CHStorage.java +++ b/core/src/main/java/com/graphhopper/storage/CHStorage.java @@ -31,7 +31,7 @@ * DataAccess-based storage for CH shortcuts. Stores shortcuts and CH levels sequentially using two DataAccess objects * and gives read/write access to the different shortcut and node fields. *

- * This can be seen as an extension to a base graph: We assign a CH level to each nodes and add additional edges to + * This can be seen as an extension to a base graph: We assign a CH level to each node and add additional edges to * the graph ('shortcuts'). The shortcuts need to be ordered in a certain way, but this is not enforced here. * * @see CHStorageBuilder to build a valid storage that can be used for routing @@ -58,7 +58,7 @@ public class CHStorage { private int nodeCount = -1; private boolean edgeBased; - // some shortcuts exceed the maximum storable weight and we count them here + // some shortcuts exceed the maximum storable weight, and we count them here private int numShortcutsExceedingWeight; // use this to report shortcuts with too small weights diff --git a/core/src/main/java/com/graphhopper/storage/GHNodeAccess.java b/core/src/main/java/com/graphhopper/storage/GHNodeAccess.java index 6ad8cdaa3e9..9d0d396e4a0 100644 --- a/core/src/main/java/com/graphhopper/storage/GHNodeAccess.java +++ b/core/src/main/java/com/graphhopper/storage/GHNodeAccess.java @@ -17,8 +17,6 @@ */ package com.graphhopper.storage; -import com.graphhopper.util.Helper; - /** * A helper class for GraphHopperStorage for its node access. *

@@ -26,59 +24,55 @@ * @author Peter Karich */ class GHNodeAccess implements NodeAccess { - private final BaseGraph baseGraph; - private final boolean elevation; + private final BaseGraphNodesAndEdges store; - public GHNodeAccess(BaseGraph baseGraph, boolean withElevation) { - this.baseGraph = baseGraph; - this.elevation = withElevation; + public GHNodeAccess(BaseGraphNodesAndEdges store) { + this.store = store; } @Override public void ensureNode(int nodeId) { - baseGraph.ensureNodeIndex(nodeId); + store.ensureNodeCapacity(nodeId); } @Override public final void setNode(int nodeId, double lat, double lon, double ele) { - baseGraph.ensureNodeIndex(nodeId); - long tmp = baseGraph.toNodePointer(nodeId); - baseGraph.nodes.setInt(tmp + baseGraph.N_LAT, Helper.degreeToInt(lat)); - baseGraph.nodes.setInt(tmp + baseGraph.N_LON, Helper.degreeToInt(lon)); + store.ensureNodeCapacity(nodeId); + store.setLat(store.toNodePointer(nodeId), lat); + store.setLon(store.toNodePointer(nodeId), lon); - if (is3D()) { + if (store.withElevation()) { // meter precision is sufficient for now - baseGraph.nodes.setInt(tmp + baseGraph.N_ELE, Helper.eleToInt(ele)); - baseGraph.bounds.update(lat, lon, ele); - + store.setEle(store.toNodePointer(nodeId), ele); + store.bounds.update(lat, lon, ele); } else { - baseGraph.bounds.update(lat, lon); + store.bounds.update(lat, lon); } } @Override public final double getLat(int nodeId) { - return Helper.intToDegree(baseGraph.nodes.getInt(baseGraph.toNodePointer(nodeId) + baseGraph.N_LAT)); + return store.getLat(store.toNodePointer(nodeId)); } @Override public final double getLon(int nodeId) { - return Helper.intToDegree(baseGraph.nodes.getInt(baseGraph.toNodePointer(nodeId) + baseGraph.N_LON)); + return store.getLon(store.toNodePointer(nodeId)); } @Override public final double getEle(int nodeId) { - if (!elevation) - throw new IllegalStateException("Cannot access elevation - 3D is not enabled"); - - return Helper.intToEle(baseGraph.nodes.getInt(baseGraph.toNodePointer(nodeId) + baseGraph.N_ELE)); + if (!store.withElevation()) + throw new IllegalStateException("elevation is disabled"); + return store.getEle(store.toNodePointer(nodeId)); } + @Override public final void setTurnCostIndex(int index, int turnCostIndex) { - if (baseGraph.supportsTurnCosts()) { - baseGraph.ensureNodeIndex(index); - long tmp = baseGraph.toNodePointer(index); - baseGraph.nodes.setInt(tmp + baseGraph.N_TC, turnCostIndex); + if (store.withTurnCosts()) { + // todo: remove ensure? + store.ensureNodeCapacity(index); + store.setTurnCostRef(store.toNodePointer(index), turnCostIndex); } else { throw new AssertionError("This graph does not support turn costs"); } @@ -86,21 +80,19 @@ public final void setTurnCostIndex(int index, int turnCostIndex) { @Override public final int getTurnCostIndex(int index) { - if (baseGraph.supportsTurnCosts()) - return baseGraph.nodes.getInt(baseGraph.toNodePointer(index) + baseGraph.N_TC); + if (store.withTurnCosts()) + return store.getTurnCostRef(store.toNodePointer(index)); else throw new AssertionError("This graph does not support turn costs"); } @Override public final boolean is3D() { - return elevation; + return store.withElevation(); } @Override public int getDimension() { - if (elevation) - return 3; - return 2; + return store.withElevation() ? 3 : 2; } } diff --git a/core/src/main/java/com/graphhopper/storage/GraphHopperStorage.java b/core/src/main/java/com/graphhopper/storage/GraphHopperStorage.java index b19452f3ae6..9171e7ff70a 100644 --- a/core/src/main/java/com/graphhopper/storage/GraphHopperStorage.java +++ b/core/src/main/java/com/graphhopper/storage/GraphHopperStorage.java @@ -441,7 +441,7 @@ public void close() { } public boolean isClosed() { - return baseGraph.nodes.isClosed(); + return baseGraph.isClosed(); } public long getCapacity() { diff --git a/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeIteratorImpl.java b/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeIteratorImpl.java index 0180f96a0b8..f0245508d97 100644 --- a/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeIteratorImpl.java +++ b/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeIteratorImpl.java @@ -54,7 +54,7 @@ public RoutingCHEdgeIterator setBaseNode(int baseNode) { assert baseGraph.isFrozen(); baseIterator.setBaseNode(baseNode); int lastShortcut = store.getLastShortcut(store.toNodePointer(baseNode)); - nextEdgeId = edgeId = lastShortcut < 0 ? baseIterator.edgeId : baseGraph.edgeCount + lastShortcut; + nextEdgeId = edgeId = lastShortcut < 0 ? baseIterator.edgeId : baseGraph.getEdges() + lastShortcut; return this; } @@ -63,13 +63,13 @@ public boolean next() { // we first traverse shortcuts (in decreasing order) and when we are done we use the base iterator to traverse // the base edges as well. shortcuts are filtered using shortcutFilter, but base edges are only filtered by // access/finite weight. - while (nextEdgeId >= baseGraph.edgeCount) { - shortcutPointer = store.toShortcutPointer(nextEdgeId - baseGraph.edgeCount); + while (nextEdgeId >= baseGraph.getEdges()) { + shortcutPointer = store.toShortcutPointer(nextEdgeId - baseGraph.getEdges()); baseNode = store.getNodeA(shortcutPointer); adjNode = store.getNodeB(shortcutPointer); edgeId = nextEdgeId; nextEdgeId--; - if (nextEdgeId < baseGraph.edgeCount || store.getNodeA(store.toShortcutPointer(nextEdgeId - baseGraph.edgeCount)) != baseNode) + if (nextEdgeId < baseGraph.getEdges() || store.getNodeA(store.toShortcutPointer(nextEdgeId - baseGraph.getEdges())) != baseNode) nextEdgeId = baseIterator.edgeId; // todo: note that it would be more efficient (but cost more memory) to separate in/out edges, // especially for edge-based where we do not use bidirectional shortcuts diff --git a/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeIteratorStateImpl.java b/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeIteratorStateImpl.java index 5d2d5801880..b89c9eae1a8 100644 --- a/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeIteratorStateImpl.java +++ b/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeIteratorStateImpl.java @@ -44,11 +44,11 @@ public RoutingCHEdgeIteratorStateImpl(CHStorage store, BaseGraph baseGraph, Base } boolean init(int edge, int expectedAdjNode) { - if (edge < 0 || edge >= baseGraph.edgeCount + store.getShortcuts()) - throw new IllegalArgumentException("edge must be in bounds: [0," + (baseGraph.edgeCount + store.getShortcuts()) + "["); + if (edge < 0 || edge >= baseGraph.getEdges() + store.getShortcuts()) + throw new IllegalArgumentException("edge must be in bounds: [0," + (baseGraph.getEdges() + store.getShortcuts()) + "["); edgeId = edge; if (isShortcut()) { - shortcutPointer = store.toShortcutPointer(edge - baseGraph.edgeCount); + shortcutPointer = store.toShortcutPointer(edge - baseGraph.getEdges()); baseNode = store.getNodeA(shortcutPointer); adjNode = store.getNodeB(shortcutPointer); @@ -102,7 +102,7 @@ public int getAdjNode() { @Override public boolean isShortcut() { - return edgeId >= baseGraph.edgeCount; + return edgeId >= baseGraph.getEdges(); } @Override diff --git a/core/src/main/java/com/graphhopper/storage/TurnCostStorage.java b/core/src/main/java/com/graphhopper/storage/TurnCostStorage.java index 44ff63b77d9..8464898290b 100644 --- a/core/src/main/java/com/graphhopper/storage/TurnCostStorage.java +++ b/core/src/main/java/com/graphhopper/storage/TurnCostStorage.java @@ -239,7 +239,7 @@ public interface TurnRelationIterator { private class Itr implements TurnRelationIterator { private int viaNode = -1; private int turnCostIndex = -1; - private IntsRef intsRef = TurnCost.createFlags(); + private final IntsRef intsRef = TurnCost.createFlags(); private long turnCostPtr() { return (long) turnCostIndex * BYTES_PER_ENTRY; diff --git a/core/src/test/java/com/graphhopper/GraphHopperTest.java b/core/src/test/java/com/graphhopper/GraphHopperTest.java index 517e763bc96..6ae47f57554 100644 --- a/core/src/test/java/com/graphhopper/GraphHopperTest.java +++ b/core/src/test/java/com/graphhopper/GraphHopperTest.java @@ -230,7 +230,7 @@ public void testUTurn() { request.addPoint(new GHPoint(43.743887, 7.431151)); request.addPoint(new GHPoint(43.744007, 7.431076)); //Force initial U-Turn - request.setHeadings(Arrays.asList(200., Double.NaN)); + request.setHeadings(Arrays.asList(200.)); request.setAlgorithm(ASTAR).setProfile(profile); GHResponse rsp = hopper.route(request); diff --git a/core/src/test/java/com/graphhopper/routing/HeadingRoutingTest.java b/core/src/test/java/com/graphhopper/routing/HeadingRoutingTest.java index 37b037a10c5..23d911f108a 100644 --- a/core/src/test/java/com/graphhopper/routing/HeadingRoutingTest.java +++ b/core/src/test/java/com/graphhopper/routing/HeadingRoutingTest.java @@ -155,7 +155,7 @@ public void headingTest5() { GHPoint via = new GHPoint(0.000, 0.0015); GHRequest req = new GHRequest(). setPoints(Arrays.asList(start, via, end)). - setHeadings(Arrays.asList(0., 3.14 / 2, Double.NaN)). + setHeadings(Arrays.asList(0., 90., Double.NaN)). setProfile("profile"). setPathDetails(Collections.singletonList("edge_key")); req.putHint(Parameters.Routing.PASS_THROUGH, true); @@ -164,6 +164,96 @@ public void headingTest5() { assertArrayEquals(new int[]{5, 4, 3, 8, 7, 6, 5, 4, 3, 2}, calcNodes(graph, response.getAll().get(0))); } + @Test + public void testHeadingWithSnapFilter() { + GraphHopperStorage graph = createSquareGraphWithTunnel(); + Router router = createRouter(graph); + // Start at 8 (slightly north to make it independent on some edge ordering and always use 8-3 or 3-8 as fallback) + GHPoint start = new GHPoint(0.0011, 0.001); + // End at middle of edge 2-3 + GHPoint end = new GHPoint(0.002, 0.0005); + + // no heading + GHRequest req = new GHRequest(). + setPoints(Arrays.asList(start, end)). + setProfile("profile"). + setPathDetails(Collections.singletonList("edge_key")); + req.putHint(Parameters.Routing.PASS_THROUGH, true); + GHResponse response = router.route(req); + assertFalse(response.hasErrors()); + assertArrayEquals(new int[]{8, 3, 2}, calcNodes(graph, response.getAll().get(0))); + + // same start + end but heading=0, parallel to 3-8-7 + req = new GHRequest(). + setPoints(Arrays.asList(start, end)). + setHeadings(Arrays.asList(0.)). + setProfile("profile"). + setPathDetails(Collections.singletonList("edge_key")); + req.putHint(Parameters.Routing.PASS_THROUGH, true); + response = router.route(req); + assertFalse(response.hasErrors()); + assertArrayEquals(new int[]{8, 3, 2}, calcNodes(graph, response.getAll().get(0))); + + // heading=90 parallel to 1->5 + req = new GHRequest(). + setPoints(Arrays.asList(start, end)). + setHeadings(Arrays.asList(90., Double.NaN)). + setProfile("profile"). + setPathDetails(Collections.singletonList("edge_key")); + req.putHint(Parameters.Routing.PASS_THROUGH, true); + response = router.route(req); + assertFalse(response.hasErrors()); + assertArrayEquals(new int[]{1, 5, 4, 3, 2}, calcNodes(graph, response.getAll().get(0))); + + for (double angle = 0; angle < 360; angle += 10) { + // Ignore angles nearly parallel to 1->5. I.e. it should fallback to results with 8-3.. or 3-8.. + if (angle >= 60 && angle <= 120) continue; + + req = new GHRequest(). + setPoints(Arrays.asList(start, end)). + setHeadings(Arrays.asList(angle, Double.NaN)). + setProfile("profile"). + setPathDetails(Collections.singletonList("edge_key")); + req.putHint(Parameters.Routing.PASS_THROUGH, true); + response = router.route(req); + assertFalse(response.hasErrors()); + + int[] expectedNodes = (angle >= 130 && angle <= 250) ? new int[]{3, 8, 7, 0, 1, 2, 3} : new int[]{8, 3, 2}; + // System.out.println(Arrays.toString(calcNodes(graph, response.getAll().get(0))) + " angle:" + angle); + assertArrayEquals(expectedNodes, calcNodes(graph, response.getAll().get(0)), "angle: " + angle); + } + } + + @Test + public void testHeadingWithSnapFilter2() { + GraphHopperStorage graph = createSquareGraphWithTunnel(); + Router router = createRouter(graph); + // Start at 8 (slightly east to snap to edge 1->5 per default) + GHPoint start = new GHPoint(0.001, 0.0011); + // End at middle of edge 2-3 + GHPoint end = new GHPoint(0.002, 0.0005); + + GHRequest req = new GHRequest(). + setPoints(Arrays.asList(start, end)). + setProfile("profile"). + setHeadings(Arrays.asList(0.)). + setPathDetails(Collections.singletonList("edge_key")); + req.putHint(Parameters.Routing.PASS_THROUGH, true); + GHResponse response = router.route(req); + assertFalse(response.hasErrors()); + assertArrayEquals(new int[]{8, 3, 2}, calcNodes(graph, response.getAll().get(0))); + + req = new GHRequest(). + setPoints(Arrays.asList(start, end)). + setProfile("profile"). + setHeadings(Arrays.asList(180.)). + setPathDetails(Collections.singletonList("edge_key")); + req.putHint(Parameters.Routing.PASS_THROUGH, true); + response = router.route(req); + assertFalse(response.hasErrors()); + assertArrayEquals(new int[]{8, 3, 2}, calcNodes(graph, response.getAll().get(0))); + } + @Test public void headingTest6() { // Test if snaps at tower nodes are ignored @@ -199,11 +289,11 @@ private GraphHopperStorage createSquareGraph() { EncodingManager encodingManager = new EncodingManager.Builder().add(carEncoder).add(Subnetwork.create("profile")).build(); GraphHopperStorage g = new GraphBuilder(encodingManager).create(); - // 2---3---4 - // / | \ - // 1----8----5 - // / | / - // 0----7---6 + // 2---3---4 + // | | | + // 1---8---5 + // | | | + // 0---7---6 NodeAccess na = g.getNodeAccess(); na.setNode(0, 0.000, 0.000); na.setNode(1, 0.001, 0.000); @@ -232,6 +322,43 @@ private GraphHopperStorage createSquareGraph() { return g; } + private GraphHopperStorage createSquareGraphWithTunnel() { + CarFlagEncoder carEncoder = new CarFlagEncoder(); + EncodingManager encodingManager = new EncodingManager.Builder().add(carEncoder).add(Subnetwork.create("profile")).build(); + GraphHopperStorage g = new GraphBuilder(encodingManager).create(); + + // 2----3---4 + // | | | + // 1->- 8 >-5 (edge 1->5 is not connected to 8) + // | | | + // 0----7---6 + NodeAccess na = g.getNodeAccess(); + na.setNode(0, 0.000, 0.000); + na.setNode(1, 0.001, 0.000); + na.setNode(2, 0.002, 0.000); + na.setNode(3, 0.002, 0.001); + na.setNode(4, 0.002, 0.002); + na.setNode(5, 0.001, 0.002); + na.setNode(6, 0.000, 0.002); + na.setNode(7, 0.000, 0.001); + na.setNode(8, 0.001, 0.001); + + GHUtility.setSpeed(60, true, true, carEncoder, g.edge(0, 1).setDistance(100)); + GHUtility.setSpeed(60, true, true, carEncoder, g.edge(1, 2).setDistance(100)); + GHUtility.setSpeed(60, true, true, carEncoder, g.edge(2, 3).setDistance(100)); + GHUtility.setSpeed(60, true, true, carEncoder, g.edge(3, 4).setDistance(100)); + GHUtility.setSpeed(60, true, true, carEncoder, g.edge(4, 5).setDistance(100)); + GHUtility.setSpeed(60, true, true, carEncoder, g.edge(5, 6).setDistance(100)); + GHUtility.setSpeed(60, true, true, carEncoder, g.edge(6, 7).setDistance(100)); + GHUtility.setSpeed(60, true, true, carEncoder, g.edge(7, 0).setDistance(100)); + + GHUtility.setSpeed(60, true, false, carEncoder, g.edge(1, 5).setDistance(110)); + GHUtility.setSpeed(60, true, true, carEncoder, g.edge(3, 8).setDistance(110)); + GHUtility.setSpeed(60, true, true, carEncoder, g.edge(7, 8).setDistance(110)); + + return g; + } + private int[] calcNodes(Graph graph, ResponsePath responsePath) { List edgeKeys = responsePath.getPathDetails().get("edge_key"); int[] result = new int[edgeKeys.size() + 1]; diff --git a/core/src/test/java/com/graphhopper/storage/AbstractGraphStorageTester.java b/core/src/test/java/com/graphhopper/storage/AbstractGraphStorageTester.java index 9e0d3b9cc46..62b779eca87 100644 --- a/core/src/test/java/com/graphhopper/storage/AbstractGraphStorageTester.java +++ b/core/src/test/java/com/graphhopper/storage/AbstractGraphStorageTester.java @@ -26,7 +26,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.io.Closeable; import java.io.File; import java.util.ArrayList; import java.util.List; @@ -117,7 +116,7 @@ public void tearDown() { public void testSetTooBigDistance_435() { graph = createGHStorage(); - double maxDist = BaseGraph.MAX_DIST; + double maxDist = BaseGraphNodesAndEdges.MAX_DIST; EdgeIteratorState edge1 = GHUtility.setSpeed(60, true, true, carEncoder, graph.edge(0, 1).setDistance(maxDist)); assertEquals(maxDist, edge1.getDistance(), 1); diff --git a/core/src/test/java/com/graphhopper/storage/GraphHopperStorageTest.java b/core/src/test/java/com/graphhopper/storage/GraphHopperStorageTest.java index 13956f7b8d1..fb2237557c1 100644 --- a/core/src/test/java/com/graphhopper/storage/GraphHopperStorageTest.java +++ b/core/src/test/java/com/graphhopper/storage/GraphHopperStorageTest.java @@ -24,6 +24,8 @@ import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.util.*; import com.graphhopper.util.shapes.BBox; +import org.junit.Ignore; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.io.File; @@ -55,16 +57,10 @@ protected GraphHopperStorage newGHStorage(Directory dir, boolean enabled3D, int return GraphBuilder.start(encodingManager).setDir(dir).set3D(enabled3D).setSegmentSize(segmentSize).build(); } + @Disabled @Test public void testNoCreateCalled() { - try (GraphHopperStorage gs = GraphBuilder.start(encodingManager).build()) { - ((BaseGraph) gs.getBaseGraph()).ensureNodeIndex(123); - fail("IllegalStateException should be raised"); - } catch (IllegalStateException err) { - // ok - } catch (Exception ex) { - fail("IllegalStateException should be raised, but was " + ex.toString()); - } + assertThrows(Throwable.class, () -> GraphBuilder.start(encodingManager).build().edge(0, 1)); } @Test @@ -160,16 +156,6 @@ protected void checkGraph(Graph g) { assertEquals(GHUtility.asSet(0), GHUtility.getNeighbors(explorer.setBaseNode(2))); } - @Test - public void testBigDataEdge() { - Directory dir = new RAMDirectory(); - GraphHopperStorage graph = new GraphHopperStorage(dir, encodingManager, false); - graph.create(defaultSize); - ((BaseGraph) graph.getBaseGraph()).setEdgeCount(Integer.MAX_VALUE / 2); - assertTrue(graph.getAllEdges().next()); - graph.close(); - } - @Test public void testDoThrowExceptionIfDimDoesNotMatch() { graph = newGHStorage(new RAMDirectory(defaultGraphLoc, true), false); diff --git a/docs/core/profiles.md b/docs/core/profiles.md index d5410409493..780f844dcb1 100644 --- a/docs/core/profiles.md +++ b/docs/core/profiles.md @@ -54,6 +54,9 @@ weightings: - curvature (prefers routes with lots of curves for enjoyable motorcycle rides) - custom (enables custom profiles, see the next section) +Another important profile setting is `turn_costs: true/false`. Use this to enable turn restrictions for each profile. +You can learn more about this setting [here](./turn-restrictions.md) + The profile name is used to select the profile when executing routing queries. To do this use the `profile` request parameter, for example `/route?point=49.5,11.1&profile=car` or `/route?point=49.5,11.1&profile=some_other_profile`. diff --git a/example/pom.xml b/example/pom.xml index 339c8d7455b..0a904b48fe8 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -5,21 +5,21 @@ 4.0.0 graphhopper-example - 4.9-SNAPSHOT + 5.0-SNAPSHOT jar GraphHopper Example com.github.GIScience.graphhopper graphhopper-parent - 4.9-SNAPSHOT + 5.0-SNAPSHOT com.github.GIScience.graphhopper graphhopper-core - 4.9-SNAPSHOT + 5.0-SNAPSHOT diff --git a/hmm-lib/pom.xml b/hmm-lib/pom.xml index 0958134e3c4..0d4b7dba87a 100644 --- a/hmm-lib/pom.xml +++ b/hmm-lib/pom.xml @@ -19,7 +19,7 @@ 4.0.0 hmm-lib-external - 4.12-SNAPSHOT + 5.0-SNAPSHOT jar hmm-lib @@ -29,7 +29,7 @@ com.github.GIScience.graphhopper graphhopper-parent - 4.12-SNAPSHOT + 5.0-SNAPSHOT diff --git a/map-matching/pom.xml b/map-matching/pom.xml index 6a503b0c7fc..45800119d61 100644 --- a/map-matching/pom.xml +++ b/map-matching/pom.xml @@ -3,20 +3,21 @@ 4.0.0 graphhopper-map-matching + 5.0-SNAPSHOT jar GraphHopper Map Matching com.github.GIScience.graphhopper graphhopper-parent - 4.12-SNAPSHOT + 5.0-SNAPSHOT com.github.GIScience.graphhopper graphhopper-core - 4.12-SNAPSHOT + 5.0-SNAPSHOT org.slf4j @@ -25,7 +26,7 @@ com.github.GIScience.graphhopper hmm-lib-external - 4.12-SNAPSHOT + 5.0-SNAPSHOT ch.qos.logback diff --git a/navigation/pom.xml b/navigation/pom.xml index 680097cfcfb..269aea47b1c 100644 --- a/navigation/pom.xml +++ b/navigation/pom.xml @@ -5,26 +5,26 @@ 4.0.0 graphhopper-nav - 4.9-SNAPSHOT + 5.0-SNAPSHOT jar GraphHopper Navigation com.github.GIScience.graphhopper graphhopper-parent - 4.9-SNAPSHOT + 5.0-SNAPSHOT com.github.GIScience.graphhopper graphhopper-web-api - 4.9-SNAPSHOT + 5.0-SNAPSHOT com.github.GIScience.graphhopper graphhopper-core - 4.9-SNAPSHOT + 5.0-SNAPSHOT io.dropwizard diff --git a/pom.xml b/pom.xml index e9a575d5671..b66380a1989 100644 --- a/pom.xml +++ b/pom.xml @@ -5,8 +5,7 @@ com.github.GIScience.graphhopper graphhopper-parent - GraphHopper Parent Project - 4.12-SNAPSHOT + GraphHopper Parent Project5.0-SNAPSHOT pom https://www.graphhopper.com 2012 diff --git a/reader-gtfs/pom.xml b/reader-gtfs/pom.xml index faf0f195a52..5b4f43873ae 100644 --- a/reader-gtfs/pom.xml +++ b/reader-gtfs/pom.xml @@ -10,14 +10,14 @@ com.github.GIScience.graphhopper graphhopper-parent - 4.12-SNAPSHOT + 5.0-SNAPSHOT com.github.GIScience.graphhopper graphhopper-core - 4.12-SNAPSHOT + 5.0-SNAPSHOT com.google.guava diff --git a/tools/pom.xml b/tools/pom.xml index 72cf4613606..ec7b44dc7a6 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -10,7 +10,7 @@ com.github.GIScience.graphhopper graphhopper-parent - 4.9-SNAPSHOT + 5.0-SNAPSHOT package @@ -20,12 +20,12 @@ com.github.GIScience.graphhopper graphhopper-core - 4.9-SNAPSHOT + 5.0-SNAPSHOT com.github.GIScience.graphhopper graphhopper-web-api - 4.9-SNAPSHOT + 5.0-SNAPSHOT diff --git a/web-api/pom.xml b/web-api/pom.xml index 4750186bb25..856e5808ee7 100644 --- a/web-api/pom.xml +++ b/web-api/pom.xml @@ -5,14 +5,14 @@ 4.0.0 graphhopper-web-api jar - 4.12-SNAPSHOT + 5.0-SNAPSHOT GraphHopper Web API JSON Representation of the API classes com.github.GIScience.graphhopper graphhopper-parent - 4.12-SNAPSHOT + 5.0-SNAPSHOT diff --git a/web-bundle/pom.xml b/web-bundle/pom.xml index 4f1a585ad56..e3fa7ff36b7 100644 --- a/web-bundle/pom.xml +++ b/web-bundle/pom.xml @@ -5,36 +5,36 @@ 4.0.0 graphhopper-web-bundle jar - 4.9-SNAPSHOT + 5.0-SNAPSHOT GraphHopper Dropwizard Bundle Use the GraphHopper routing engine as a web-service com.github.GIScience.graphhopper graphhopper-parent - 4.9-SNAPSHOT + 5.0-SNAPSHOT com.github.GIScience.graphhopper graphhopper-web-api - 4.9-SNAPSHOT + 5.0-SNAPSHOT com.github.GIScience.graphhopper graphhopper-core - 4.9-SNAPSHOT + 5.0-SNAPSHOT com.github.GIScience.graphhopper graphhopper-reader-gtfs - 4.9-SNAPSHOT + 5.0-SNAPSHOT com.github.GIScience.graphhopper graphhopper-map-matching - 4.9-SNAPSHOT + 5.0-SNAPSHOT @@ -118,7 +118,7 @@ com.github.GIScience.graphhopper directions-api-client-hc - 4.9-SNAPSHOT + 5.0-SNAPSHOT test diff --git a/web-bundle/src/main/resources/com/graphhopper/maps/index.html b/web-bundle/src/main/resources/com/graphhopper/maps/index.html index 011f6d92920..bc0519f073a 100644 --- a/web-bundle/src/main/resources/com/graphhopper/maps/index.html +++ b/web-bundle/src/main/resources/com/graphhopper/maps/index.html @@ -36,7 +36,7 @@ - + diff --git a/web/pom.xml b/web/pom.xml index 331fd367bff..cdf33042d43 100644 --- a/web/pom.xml +++ b/web/pom.xml @@ -5,14 +5,14 @@ 4.0.0 graphhopper-web jar - 4.9-SNAPSHOT + 5.0-SNAPSHOT GraphHopper Web Use the GraphHopper routing engine as a web-service com.github.GIScience.graphhopper graphhopper-parent - 4.9-SNAPSHOT + 5.0-SNAPSHOT package @@ -30,12 +30,12 @@ com.github.GIScience.graphhopper graphhopper-web-bundle - 4.9-SNAPSHOT + 5.0-SNAPSHOT com.github.GIScience.graphhopper graphhopper-nav - 4.9-SNAPSHOT + 5.0-SNAPSHOT @@ -66,7 +66,7 @@ com.github.GIScience.graphhopper directions-api-client-hc - 4.9-SNAPSHOT + 5.0-SNAPSHOT test