diff --git a/NOTICE-THIRD-PARTY.md b/NOTICE-THIRD-PARTY.md index 923aab731..1a927766b 100644 --- a/NOTICE-THIRD-PARTY.md +++ b/NOTICE-THIRD-PARTY.md @@ -93,22 +93,22 @@ FindBugs-jsr305 (3.0.2) * Source: https://code.google.com/p/jsr-305/ -GraphHopper API (0.13.0) - - * License: Apache License, Version 2.0 - * Maven artifact: `com.graphhopper:graphhopper-api:0.13.0` - * Project: https://www.graphhopper.com/graphhopper-api - * Source: https://github.com/graphhopper/graphhopper/graphhopper-api - - -GraphHopper Core (0.13.0) +GraphHopper Core (8.0) * License: The Apache Software License, Version 2.0 - * Maven artifact: `com.graphhopper:graphhopper-core:0.13.0` + * Maven artifact: `com.graphhopper:graphhopper-core:8.0` * Project: https://www.graphhopper.com/graphhopper-core * Source: https://github.com/graphhopper/graphhopper/graphhopper-core +GraphHopper Web API (8.0) + + * License: Apache License, Version 2.0 + * Maven artifact: `com.graphhopper:graphhopper-web-api:8.0` + * Project: https://www.graphhopper.com/graphhopper-web-api + * Source: https://github.com/graphhopper/graphhopper/graphhopper-web-api + + Gson (2.10.1) * License: Apache-2.0 @@ -133,46 +133,46 @@ HPPC Collections (0.8.1) * Source: https://github.com/carrotsearch/hppc/hppc -Jackson module: JAXB Annotations (2.9.9) +Jackson-annotations (2.15.0) * License: The Apache Software License, Version 2.0 - * Maven artifact: `com.fasterxml.jackson.module:jackson-module-jaxb-annotations:2.9.9` - * Project: https://github.com/FasterXML/jackson-modules-base - * Source: https://github.com/FasterXML/jackson-modules-base/jackson-module-jaxb-annotations - - -Jackson-annotations (2.9.0) - - * License: The Apache Software License, Version 2.0 - * Maven artifact: `com.fasterxml.jackson.core:jackson-annotations:2.9.0` - * Project: http://github.com/FasterXML/jackson + * Maven artifact: `com.fasterxml.jackson.core:jackson-annotations:2.15.0` + * Project: https://github.com/FasterXML/jackson * Source: https://github.com/FasterXML/jackson-annotations -Jackson-core (2.9.9) +Jackson-core (2.15.0) * License: The Apache Software License, Version 2.0 - * Maven artifact: `com.fasterxml.jackson.core:jackson-core:2.9.9` + * Maven artifact: `com.fasterxml.jackson.core:jackson-core:2.15.0` * Project: https://github.com/FasterXML/jackson-core * Source: https://github.com/FasterXML/jackson-core -jackson-databind (2.9.9) +jackson-databind (2.15.0) * License: The Apache Software License, Version 2.0 - * Maven artifact: `com.fasterxml.jackson.core:jackson-databind:2.9.9` - * Project: http://github.com/FasterXML/jackson + * Maven artifact: `com.fasterxml.jackson.core:jackson-databind:2.15.0` + * Project: https://github.com/FasterXML/jackson * Source: https://github.com/FasterXML/jackson-databind -Jackson-dataformat-XML (2.9.9) +Jackson-dataformat-XML (2.15.0) * License: The Apache Software License, Version 2.0 - * Maven artifact: `com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.9` - * Project: http://wiki.fasterxml.com/JacksonExtensionXmlDataBinding + * Maven artifact: `com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.15.0` + * Project: https://github.com/FasterXML/jackson-dataformat-xml * Source: https://github.com/FasterXML/jackson-dataformat-xml +Jackson-datatype-jts (2.14) + + * License: The Apache Software License, Version 2.0 + * Maven artifact: `com.graphhopper.external:jackson-datatype-jts:2.14` + * Project: https://github.com/bedatadriven/jackson-datatype-jts/ + * Source: scm:git:https://github.com/bedatadriven/jackson-datatype-jts + + Janino (2.7.5) * License: New BSD License @@ -237,6 +237,14 @@ org.leadpony.justify (1.1.0) * Source: https://github.com/leadpony/justify +org.locationtech.jts:jts-core (1.19.0) + + * License: Eclipse Distribution License - v 1.0, Eclipse Public License, Version 2.0 + * Maven artifact: `org.locationtech.jts:jts-core:1.19.0` + * Project: https://www.locationtech.org/projects/technology.jts/jts-modules/jts-core + * Source: https://github.com/locationtech/jts/jts-modules/jts-core + + Protocol Buffers [Core] (3.8.0) * License: 3-Clause BSD License @@ -269,10 +277,10 @@ Stax2 API (4.2.1) * Source: https://github.com/FasterXML/stax2-api -Woodstox (5.1.0) +Woodstox (6.5.1) * License: The Apache License, Version 2.0 - * Maven artifact: `com.fasterxml.woodstox:woodstox-core:5.1.0` + * Maven artifact: `com.fasterxml.woodstox:woodstox-core:6.5.1` * Project: https://github.com/FasterXML/woodstox * Source: https://github.com/FasterXML/woodstox diff --git a/bundle/src/assembly/mosaic-bundle.xml b/bundle/src/assembly/mosaic-bundle.xml index 7dd9da292..6fcba6a40 100644 --- a/bundle/src/assembly/mosaic-bundle.xml +++ b/bundle/src/assembly/mosaic-bundle.xml @@ -154,9 +154,11 @@ - com.graphhopper:graphhopper-api + com.graphhopper:graphhopper-web-api com.graphhopper:graphhopper-core + com.graphhopper.external:jackson-datatype-jts com.carrotsearch:hppc + org.locationtech.jts:jts-core com.jcraft:jsch diff --git a/lib/mosaic-routing/pom.xml b/lib/mosaic-routing/pom.xml index a53dccb1e..67b165199 100644 --- a/lib/mosaic-routing/pom.xml +++ b/lib/mosaic-routing/pom.xml @@ -32,16 +32,6 @@ com.graphhopper graphhopper-core - - - org.locationtech.jts - jts-core - - - org.apache.xmlgraphics - xmlgraphics-commons - - com.google.guava diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/CandidateRoute.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/CandidateRoute.java index f8e2a6e7e..58c13d383 100644 --- a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/CandidateRoute.java +++ b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/CandidateRoute.java @@ -23,25 +23,94 @@ */ public class CandidateRoute { + /** + * The list of connections forming this route. + */ private final List connectionIds; + + /** + * The length in meters of this route. + */ private final double length; + + /** + * The approximated driving time in seconds on this route. + */ private final double time; + /** + * The distance in meters from the start node of the first connection in the connectionIds list, to + * the point the source query was issued. + */ + private final double offsetFromSource; + + /** + * The distance in meters from the point the target query was issued, until the end node of the + * final connection in the connectionIds list. + */ + private final double offsetToTarget; + + /** + * @param connectionIds the list of connection id this route persists of. + * @param length the length in m of this route. + * @param time the approximate travel time in seconds of this route. + */ public CandidateRoute(List connectionIds, double length, double time) { + this(connectionIds, length, time, 0, 0); + } + + /** + * + * @param connectionIds the list of connection id this route persists of. + * @param length the length in m of this route. + * @param time the approximate travel time in seconds of this route. + * @param offsetFromSource the distance in meters from the start node of the first connection in the connectionIds list, to + * the point the source query was issued. + * @param offsetToTarget the distance in meters from the point the target query was issued, until the end node of the + * final connection in the connectionIds list. + */ + public CandidateRoute(List connectionIds, double length, double time, double offsetFromSource, double offsetToTarget) { this.connectionIds = connectionIds; this.length = length; this.time = time; + this.offsetFromSource = offsetFromSource; + this.offsetToTarget = offsetToTarget; } + /** + * @return the list of connection ids forming this route. + */ public List getConnectionIds() { return connectionIds; } + /** + * @return the length in meters of this route. + */ public double getLength() { return length; } + /** + * @return the approximated driving time in seconds on this route. + */ public double getTime() { return time; } + + /** + * @return the distance in meters from the start node of the first connection in the connectionIds list, to + * the point the source query was issued. + */ + public double getOffsetFromSource() { + return offsetFromSource; + } + + /** + * @return the distance in meters from the point the target query was issued, until + * the end node of the final connection in the connectionIds list. + */ + public double getOffsetToTarget() { + return offsetToTarget; + } } diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/database/DatabaseRouting.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/database/DatabaseRouting.java index e9d91d634..4b9e8409f 100644 --- a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/database/DatabaseRouting.java +++ b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/database/DatabaseRouting.java @@ -86,8 +86,7 @@ public void initialize(final CRouting configuration, final File baseDirectory) t } //creates an implementation of IRoutingGraph according to the configuration - this.routing = new GraphHopperRouting() - .loadGraphFromDatabase(scenarioDatabase); + this.routing = new GraphHopperRouting(scenarioDatabase); this.routeManager = new RouteManager(this.scenarioDatabase); } diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/GraphHopperEdgeProperties.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/GraphHopperEdgeProperties.java index a735837d5..048030d1e 100644 --- a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/GraphHopperEdgeProperties.java +++ b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/GraphHopperEdgeProperties.java @@ -20,10 +20,12 @@ import org.eclipse.mosaic.lib.routing.EdgeProperties; import org.eclipse.mosaic.lib.routing.RoutingCostFunction; import org.eclipse.mosaic.lib.routing.graphhopper.util.GraphhopperToDatabaseMapper; +import org.eclipse.mosaic.lib.routing.graphhopper.util.VehicleEncoding; +import org.eclipse.mosaic.lib.routing.graphhopper.util.WayTypeEncoder; import com.google.common.collect.Iterables; -import com.graphhopper.routing.util.FlagEncoder; import com.graphhopper.util.EdgeIteratorState; +import com.graphhopper.util.FetchMode; import org.apache.commons.lang3.Validate; import java.util.Optional; @@ -32,16 +34,18 @@ * Provides properties from the current {@link EdgeIteratorState} or * its belonging {@link Connection} to be used by an {@link RoutingCostFunction}. */ -class GraphHopperEdgeProperties implements EdgeProperties { +public class GraphHopperEdgeProperties implements EdgeProperties { - private final FlagEncoder flagEncoder; + private final VehicleEncoding encoding; + private final WayTypeEncoder wayTypeEncoder; private final GraphhopperToDatabaseMapper graphMapper; private EdgeIteratorState currentEdgeIterator; private boolean reverseRequests; - GraphHopperEdgeProperties(FlagEncoder encoder, GraphhopperToDatabaseMapper graphMapper) { - this.flagEncoder = encoder; + GraphHopperEdgeProperties(VehicleEncoding encoding, WayTypeEncoder wayTypeEncoder, GraphhopperToDatabaseMapper graphMapper) { + this.encoding = encoding; + this.wayTypeEncoder = wayTypeEncoder; this.graphMapper = graphMapper; } @@ -54,9 +58,9 @@ void setCurrentEdgeIterator(EdgeIteratorState currentEdgeIterator, boolean rever @Override public double getSpeed() { Validate.notNull(currentEdgeIterator, "Edge iterator is null"); - return (reverseRequests - ? currentEdgeIterator.getReverse(flagEncoder.getAverageSpeedEnc()) - : currentEdgeIterator.get(flagEncoder.getAverageSpeedEnc())) / 3.6d; + return reverseRequests + ? currentEdgeIterator.getReverse(encoding.speed()) / 3.6 + : currentEdgeIterator.get(encoding.speed()) / 3.6; } @Override @@ -69,8 +73,8 @@ public double getLength() { public Iterable getGeometry() { Validate.notNull(currentEdgeIterator, "Edge iterator is null"); return Iterables.transform( - currentEdgeIterator.fetchWayGeometry(3), // 3 = fetch all pillar nodes inclusive the base and adjacent tower node - ghPoint3D -> GeoPoint.latLon(ghPoint3D.getLat(), ghPoint3D.getLon(), ghPoint3D.getElevation()) + currentEdgeIterator.fetchWayGeometry(FetchMode.ALL), // fetches all pillar nodes inclusive the base and adjacent tower node + ghPoint3D -> GeoPoint.latLon(ghPoint3D.getLat(), ghPoint3D.getLon(), ghPoint3D.getEle()) ); } @@ -81,7 +85,11 @@ public String getConnectionId() { @Override public String getWayType() { - return getConnection().map(con -> con.getWay().getType()).orElse(null); + return WayTypeEncoder.decode(getWayTypeEncoded()); + } + + public int getWayTypeEncoded() { + return currentEdgeIterator.get(wayTypeEncoder); } private Optional getConnection() { diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/GraphHopperRouting.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/GraphHopperRouting.java index be567a702..bbc81632e 100644 --- a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/GraphHopperRouting.java +++ b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/GraphHopperRouting.java @@ -28,42 +28,92 @@ import org.eclipse.mosaic.lib.routing.RoutingCostFunction; import org.eclipse.mosaic.lib.routing.RoutingPosition; import org.eclipse.mosaic.lib.routing.RoutingRequest; -import org.eclipse.mosaic.lib.routing.graphhopper.algorithm.AlternativeRoutesRoutingAlgorithm; -import org.eclipse.mosaic.lib.routing.graphhopper.algorithm.DijkstraCamvitChoiceRouting; -import org.eclipse.mosaic.lib.routing.graphhopper.extended.ExtendedGraphHopper; +import org.eclipse.mosaic.lib.routing.graphhopper.algorithm.RoutingAlgorithmFactory; +import org.eclipse.mosaic.lib.routing.graphhopper.util.DatabaseGraphLoader; import org.eclipse.mosaic.lib.routing.graphhopper.util.GraphhopperToDatabaseMapper; +import org.eclipse.mosaic.lib.routing.graphhopper.util.OptionalTurnCostProvider; +import org.eclipse.mosaic.lib.routing.graphhopper.util.VehicleEncoding; +import org.eclipse.mosaic.lib.routing.graphhopper.util.VehicleEncodingManager; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; -import com.graphhopper.GraphHopper; +import com.graphhopper.config.Profile; import com.graphhopper.routing.Path; -import com.graphhopper.routing.QueryGraph; -import com.graphhopper.routing.util.BikeFlagEncoder; -import com.graphhopper.routing.util.CarFlagEncoder; -import com.graphhopper.routing.util.DefaultEdgeFilter; +import com.graphhopper.routing.RoutingAlgorithm; +import com.graphhopper.routing.ev.BooleanEncodedValue; +import com.graphhopper.routing.querygraph.QueryGraph; +import com.graphhopper.routing.querygraph.VirtualEdgeIteratorState; +import com.graphhopper.routing.subnetwork.PrepareRoutingSubnetworks; +import com.graphhopper.routing.util.AccessFilter; import com.graphhopper.routing.util.EdgeFilter; -import com.graphhopper.routing.util.EncodingManager; -import com.graphhopper.routing.util.FlagEncoder; -import com.graphhopper.routing.weighting.TurnWeighting; -import com.graphhopper.storage.TurnCostExtension; -import com.graphhopper.storage.index.QueryResult; +import com.graphhopper.routing.weighting.Weighting; +import com.graphhopper.storage.BaseGraph; +import com.graphhopper.storage.Graph; +import com.graphhopper.storage.NodeAccess; +import com.graphhopper.storage.RAMDirectory; +import com.graphhopper.storage.index.LocationIndex; +import com.graphhopper.storage.index.LocationIndexTree; +import com.graphhopper.storage.index.Snap; import com.graphhopper.util.DistanceCalc; import com.graphhopper.util.DistancePlaneProjection; import com.graphhopper.util.EdgeIteratorState; +import com.graphhopper.util.PMap; +import com.graphhopper.util.Parameters; import com.graphhopper.util.PointList; import com.graphhopper.util.shapes.GHPoint; +import edu.umd.cs.findbugs.annotations.SuppressWarnings; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; +@SuppressWarnings(value = "MS_SHOULD_BE_FINAL", justification = "Static fields kept public and adjustable for user customization") public class GraphHopperRouting { + private static final Logger LOG = LoggerFactory.getLogger(GraphHopperRouting.class); + + public static final Profile PROFILE_CAR = new Profile("car").setVehicle("car").setTurnCosts(true); + + public static final Profile PROFILE_BIKE = new Profile("bike").setVehicle("bike").setTurnCosts(false); + + public static final List PROFILES = Collections.unmodifiableList(Lists.newArrayList( + PROFILE_CAR, PROFILE_BIKE + )); + + /** + * The minimum number of alternatives to calculate when alternative routes have been requested. + * GraphHopper often returns equal routes, and by calculating more than required we can + * reduce the result to match the requested number of alternatives. + */ + private static final int NUM_ALTERNATIVE_PATHS = 5; + + /** + * Alternative routes may share a maximum of 70% of roads of the best route. + */ + public static double ALTERNATIVE_ROUTES_MAX_SHARE = 0.7; + + /** + * Alternative routes may cost a maximum of 40% more than the best route. + */ + public static double ALTERNATIVE_ROUTES_MAX_WEIGHT = 1.4; + + /** + * Increases the changes to find more alternatives. + */ + public static double ALTERNATIVE_ROUTES_EXPLORATION_FACTOR = 1.3; + + /** + * Specifies the minimum plateau portion of every alternative path that is required. + */ + public static double ALTERNATIVE_ROUTES_PLATEAU_FACTOR = 0.1; + /** * If the distance of the query position to the closest node is lower than this * value, then the closest node is used definitely as the source or target of the route. @@ -76,7 +126,7 @@ public class GraphHopperRouting { * the found route, another connection is added on which the target * point is matched on. */ - public static double TARGET_REQUEST_CONNECTION_THRESHOLD = 5d; + public static final double TARGET_REQUEST_CONNECTION_THRESHOLD = 5d; /** * Sometimes edges are dead ends. In these cases routing fails and invalid @@ -85,151 +135,189 @@ public class GraphHopperRouting { */ private static final double MAX_DISTANCE_TO_TARGET = 500d; - private final Logger log = LoggerFactory.getLogger(getClass()); - - private GraphHopper ghApi; private final DistanceCalc distanceCalculation = new DistancePlaneProjection(); - private GraphhopperToDatabaseMapper graphMapper; - private Database db; + private final GraphhopperToDatabaseMapper graphMapper; + private final Database db; + private final VehicleEncodingManager encoding; + private final BaseGraph graph; + private final LocationIndex locationIndex; - public GraphHopperRouting loadGraphFromDatabase(Database db) { + public GraphHopperRouting(Database db) { this.db = db; - //initialize reader and mapper for database import into graphhopper - GraphLoader reader = new DatabaseGraphLoader(db); graphMapper = new GraphhopperToDatabaseMapper(); + encoding = new VehicleEncodingManager(PROFILES); - ghApi = new ExtendedGraphHopper(reader, graphMapper).forDesktop(); - //we only need a encoder for speed,flag and turn costs for cars - ghApi.setEncodingManager(EncodingManager.create( - new CarFlagEncoder(5, 5, 127), - new BikeFlagEncoder(4, 2, 0) - )); + graph = createGraphFromDatabase(db); + locationIndex = createLocationIndex(); + cleanUpGraph(); - //load graph from database - ghApi.importOrLoad(); + graph.flush(); + } + + private BaseGraph createGraphFromDatabase(Database db) { + final BaseGraph graph = new BaseGraph + .Builder(encoding.getEncodingManager()) + .setDir(new RAMDirectory()) + .set3D(true) + .withTurnCosts(encoding.getEncodingManager().needsTurnCostsSupport()) + .setSegmentSize(-1) + .build(); + + final DatabaseGraphLoader reader = new DatabaseGraphLoader(db); + reader.initialize(graph, encoding, graphMapper); + reader.loadGraph(); + LOG.info("nodes: {}, edges: {}", graph.getNodes(), graph.getEdges()); + return graph; + } + + private LocationIndex createLocationIndex() { + return new LocationIndexTree(graph, graph.getDirectory()) + .setMinResolutionInMeter(300) + .setMaxRegionSearch(4) + .prepareIndex(); + } + + protected void cleanUpGraph() { + new PrepareRoutingSubnetworks(graph, buildSubnetworkRemovalJobs()) + .setMinNetworkSize(200) + .setThreads(1) + .doWork(); + } - return this; + private List buildSubnetworkRemovalJobs() { + List jobs = new ArrayList<>(); + for (Profile profile : encoding.getAllProfiles()) { + Weighting weighting = createWeighting(profile, RoutingCostFunction.Fastest, false); + jobs.add(new PrepareRoutingSubnetworks.PrepareJob(encoding.getVehicleEncoding(profile.getVehicle()).subnetwork(), weighting)); + } + return jobs; } public List findRoutes(RoutingRequest routingRequest) { - if (ghApi == null) { + if (graph == null) { throw new IllegalStateException("Load database at first"); } - final FlagEncoder flagEncoder; + final Profile profile; if (routingRequest.getRoutingParameters().getVehicleClass() == VehicleClass.Bicycle) { - flagEncoder = ghApi.getEncodingManager().getEncoder("bike"); - } else { - flagEncoder = ghApi.getEncodingManager().getEncoder("car"); - } - - final GraphHopperWeighting graphhopperWeighting = new GraphHopperWeighting(flagEncoder, graphMapper); - - // if there is no cost function given (initial routes), use the default - if (routingRequest.getRoutingParameters().getRoutingCostFunction() == null) { - graphhopperWeighting.setRoutingCostFunction(RoutingCostFunction.Default); + profile = PROFILE_BIKE; } else { - graphhopperWeighting.setRoutingCostFunction(routingRequest.getRoutingParameters().getRoutingCostFunction()); + profile = PROFILE_CAR; } + final VehicleEncoding vehicleEncoding = encoding.getVehicleEncoding(profile.getVehicle()); final RoutingPosition source = routingRequest.getSource(); final RoutingPosition target = routingRequest.getTarget(); - final QueryResult querySource = createQueryForSource(source, flagEncoder); - final QueryResult queryTarget = createQueryForTarget(target, flagEncoder); + final Snap snapSource = createQueryForSource(source, vehicleEncoding.access()); + final Snap snapTarget = createQueryForTarget(target, vehicleEncoding.access()); - if (querySource.getClosestEdge() == null || queryTarget.getClosestEdge() == null) { - log.warn("Could not find a route from {} to {}", routingRequest.getSource(), routingRequest.getTarget()); + if (snapSource.getClosestEdge() == null || snapTarget.getClosestEdge() == null) { + LOG.warn("Could not find a route from {} to {}", routingRequest.getSource(), routingRequest.getTarget()); return Lists.newArrayList(); } - final QueryGraph queryGraph = new QueryGraph(ghApi.getGraphHopperStorage()); - queryGraph.lookup(querySource, queryTarget); + final QueryGraph queryGraph = QueryGraph.create(graph, snapSource, snapTarget); - final TurnWeighting weighting = new TurnWeightingOptional( - graphhopperWeighting, (TurnCostExtension) queryGraph.getExtension(), - routingRequest.getRoutingParameters().isConsiderTurnCosts(), - routingRequest.getRoutingParameters().getRestrictionCosts() - ); - - // create algorithm - final AlternativeRoutesRoutingAlgorithm algo = new DijkstraCamvitChoiceRouting(queryGraph, weighting); - algo.setRequestAlternatives(routingRequest.getRoutingParameters().getNumAlternativeRoutes()); + final int numberOfAlternatives = routingRequest.getRoutingParameters().getNumAlternativeRoutes(); + final PMap algoHints = new PMap(); + if (numberOfAlternatives > 0) { + // We calculate more alternative routes than required, since GraphHopper often seem to return equal alternatives + algoHints.putObject(Parameters.Algorithms.AltRoute.MAX_PATHS, Math.max(numberOfAlternatives, NUM_ALTERNATIVE_PATHS) + 1); + } - final List paths = new ArrayList<>(); + final Weighting weighting = createWeighting(profile, + routingRequest.getRoutingParameters().getRoutingCostFunction(), + routingRequest.getRoutingParameters().isConsiderTurnCosts() + ); - // Calculates all paths and returns the best one - paths.add(algo.calcPath(querySource.getClosestNode(), queryTarget.getClosestNode())); + final RoutingAlgorithm algo = RoutingAlgorithmFactory.DEFAULT.createAlgorithm( + queryGraph, queryGraph.wrapWeighting(weighting), algoHints + ); - //add alternative paths to path set - paths.addAll(algo.getAlternativePaths()); + final List paths = algo.calcPaths(snapSource.getClosestNode(), snapTarget.getClosestNode()); final Set duplicateSet = new HashSet<>(); final List result = new ArrayList<>(); // convert paths to routes for (final Path path : paths) { - final CandidateRoute route = convertPath(path, queryGraph, querySource, queryTarget, source, target); + if (result.size() > numberOfAlternatives) { + break; + } + final CandidateRoute route = convertPath(queryGraph, path, source, target); if (route != null && !route.getConnectionIds().isEmpty() && checkRouteOnRequiredSourceConnection(route, source) - && checkForDuplicate(route, duplicateSet)) { + && checkForDuplicate(route, duplicateSet) + ) { result.add(route); - } else if (route != null && log.isDebugEnabled()) { - log.debug("Path is invalid and will be ignored [" + StringUtils.join(route.getConnectionIds(), ",") + "]"); + } else if (route != null && LOG.isDebugEnabled()) { + LOG.debug("Path is invalid and will be ignored [" + StringUtils.join(route.getConnectionIds(), ",") + "]"); } } return result; } - private QueryResult createQueryForTarget(RoutingPosition target, FlagEncoder flagEncoder) { - final EdgeFilter toEdgeFilter = createEdgeFilterForRoutingPosition(target, flagEncoder); - QueryResult queryTarget = ghApi.getLocationIndex().findClosest(target.getPosition().getLatitude(), target.getPosition().getLongitude(), toEdgeFilter); + private Weighting createWeighting(Profile profile, RoutingCostFunction costFunction, boolean withTurnCosts) { + final VehicleEncoding vehicleEncoding = encoding.getVehicleEncoding(profile.getVehicle()); + final OptionalTurnCostProvider turnCostProvider = new OptionalTurnCostProvider(vehicleEncoding, graph.getTurnCostStorage()); + if (!withTurnCosts) { + turnCostProvider.disableTurnCosts(); + } + return new GraphHopperWeighting(vehicleEncoding, encoding.wayType(), turnCostProvider, graphMapper) + .setRoutingCostFunction(ObjectUtils.defaultIfNull(costFunction, RoutingCostFunction.Default)); + } + + private Snap createQueryForTarget(RoutingPosition target, BooleanEncodedValue accessEnc) { + final EdgeFilter toEdgeFilter = createEdgeFilterForRoutingPosition(target, accessEnc); + Snap queryTarget = locationIndex.findClosest(target.getPosition().getLatitude(), target.getPosition().getLongitude(), toEdgeFilter); if (target.getConnectionId() != null) { - return fixQueryResultIfNoClosestEdgeFound(queryTarget, target, flagEncoder); + return fixQueryResultIfNoClosestEdgeFound(queryTarget, target, accessEnc); } else { return fixQueryResultIfSnappedPointIsCloseToTowerNode(queryTarget, target); } } - private QueryResult createQueryForSource(RoutingPosition source, FlagEncoder flagEncoder) { - final EdgeFilter fromEdgeFilter = createEdgeFilterForRoutingPosition(source, flagEncoder); - QueryResult querySource = ghApi.getLocationIndex().findClosest(source.getPosition().getLatitude(), source.getPosition().getLongitude(), fromEdgeFilter); + private Snap createQueryForSource(RoutingPosition source, BooleanEncodedValue accessEnc) { + final EdgeFilter fromEdgeFilter = createEdgeFilterForRoutingPosition(source, accessEnc); + Snap querySource = locationIndex.findClosest(source.getPosition().getLatitude(), source.getPosition().getLongitude(), fromEdgeFilter); if (source.getConnectionId() != null) { querySource = fixQueryResultIfSnappedPointIsTowerNode(querySource, source, fromEdgeFilter); - return fixQueryResultIfNoClosestEdgeFound(querySource, source, flagEncoder); + return fixQueryResultIfNoClosestEdgeFound(querySource, source, accessEnc); } else { return fixQueryResultIfSnappedPointIsCloseToTowerNode(querySource, source); } } - private QueryResult fixQueryResultIfSnappedPointIsCloseToTowerNode(QueryResult queryResult, RoutingPosition target) { - if (queryResult.getSnappedPosition() == QueryResult.Position.TOWER || target.getConnectionId() != null) { + private Snap fixQueryResultIfSnappedPointIsCloseToTowerNode(Snap queryResult, RoutingPosition target) { + if (queryResult.getSnappedPosition() == Snap.Position.TOWER || target.getConnectionId() != null) { return queryResult; } Node closestNode = graphMapper.toNode(queryResult.getClosestNode()); /* If the query result is snapped to an edge, but the matched node is very close to the query, than we use the actual closest node * as the target of the route.*/ if (closestNode != null && target.getPosition().distanceTo(closestNode.getPosition()) < TARGET_NODE_QUERY_DISTANCE) { - queryResult.setSnappedPosition(QueryResult.Position.TOWER); + queryResult.setSnappedPosition(Snap.Position.TOWER); } return queryResult; } - private QueryResult fixQueryResultIfNoClosestEdgeFound(QueryResult queryResult, RoutingPosition routingPosition, FlagEncoder flagEncoder) { + private Snap fixQueryResultIfNoClosestEdgeFound(Snap queryResult, RoutingPosition routingPosition, BooleanEncodedValue accessEnc) { if (queryResult.getClosestEdge() == null) { - log.warn("Wrong routing request: The from-connection {} does not fit with the given position {}", routingPosition.getConnectionId(), routingPosition.getPosition()); - return ghApi.getLocationIndex().findClosest( - routingPosition.getPosition().getLatitude(), routingPosition.getPosition().getLongitude(), DefaultEdgeFilter.allEdges(flagEncoder) + LOG.warn("Wrong routing request: The from-connection {} does not fit with the given position {}", routingPosition.getConnectionId(), routingPosition.getPosition()); + + return locationIndex.findClosest( + routingPosition.getPosition().getLatitude(), routingPosition.getPosition().getLongitude(), AccessFilter.allEdges(accessEnc) ); } return queryResult; } - private QueryResult fixQueryResultIfSnappedPointIsTowerNode(QueryResult queryResult, RoutingPosition routingPosition, EdgeFilter fromEdgeFilter) { - if (queryResult.getSnappedPosition() != QueryResult.Position.TOWER) { + private Snap fixQueryResultIfSnappedPointIsTowerNode(Snap queryResult, RoutingPosition routingPosition, EdgeFilter fromEdgeFilter) { + if (queryResult.getSnappedPosition() != Snap.Position.TOWER) { return queryResult; } /* If the requested position is in front or behind the edge it is mapped either on the start or end of the edge (one of the tower nodes). @@ -244,7 +332,7 @@ private QueryResult fixQueryResultIfSnappedPointIsTowerNode(QueryResult queryRes queryConnection.getFrom().getPosition(), queryConnection.getTo().getPosition() ); } - return ghApi.getLocationIndex().findClosest( + return locationIndex.findClosest( alternativeQueryPosition.getLatitude(), alternativeQueryPosition.getLongitude(), fromEdgeFilter ); } @@ -261,18 +349,25 @@ private boolean checkForDuplicate(CandidateRoute route, Set duplicateSet return duplicateSet.add(nodeIdList); } - private EdgeFilter createEdgeFilterForRoutingPosition(final RoutingPosition position, final FlagEncoder flagEncoder) { + private boolean checkRouteOnRequiredSourceConnection(CandidateRoute route, RoutingPosition source) { + if (source.getConnectionId() != null) { + return source.getConnectionId().equals(route.getConnectionIds().get(0)); + } + return true; + } + + private EdgeFilter createEdgeFilterForRoutingPosition(final RoutingPosition position, final BooleanEncodedValue accessEnc) { if (position.getConnectionId() == null) { - return DefaultEdgeFilter.allEdges(flagEncoder); + return AccessFilter.allEdges(accessEnc); } final int forcedEdge = graphMapper.fromConnection(db.getConnection(position.getConnectionId())); if (forcedEdge < 0) { - return DefaultEdgeFilter.allEdges(flagEncoder); + return AccessFilter.allEdges(accessEnc); } return edgeState -> edgeState.getEdge() == forcedEdge; } - private CandidateRoute convertPath(Path newPath, QueryGraph queryGraph, QueryResult source, QueryResult target, RoutingPosition sourcePosition, RoutingPosition targetPosition) { + private CandidateRoute convertPath(Graph graph, Path newPath, RoutingPosition sourcePosition, RoutingPosition targetPosition) { PointList pointList = newPath.calcPoints(); if (pointList.isEmpty()) { return null; @@ -290,26 +385,25 @@ private CandidateRoute convertPath(Path newPath, QueryGraph queryGraph, QueryRes List pathConnections = new ArrayList<>(); + double offsetFromSource = 0; + int lastNode = -1; + Connection lastConnection = null; + NodeAccess nodes = graph.getNodeAccess(); + while (edgesIt.hasNext()) { - EdgeIteratorState ghEdge = edgesIt.next(); + EdgeIteratorState origEdge = edgesIt.next(); + EdgeIteratorState currEdge = origEdge; /* * If the requested source or target point is in the middle of the road, an artificial node * (and artificial edges) is created in the QueryGraph. As a consequence, the first - * and/or last edge of the route might be such virtual edge. We use the queried source - * and target to extract the original edge where the requested points have been matched on. + * and/or last edge of the route might be such virtual edge, which must be converted to its original edge. */ - if (queryGraph.isVirtualEdge(ghEdge.getEdge())) { - if (pathConnections.isEmpty() && queryGraph.isVirtualNode(source.getClosestNode())) { - ghEdge = queryGraph.getOriginalEdgeFromVirtNode(source.getClosestNode()); - } else if (!edgesIt.hasNext() && queryGraph.isVirtualNode(target.getClosestNode())) { - ghEdge = queryGraph.getOriginalEdgeFromVirtNode(target.getClosestNode()); - } else { - continue; - } + if (currEdge instanceof VirtualEdgeIteratorState) { + currEdge = graph.getEdgeIteratorStateForKey(((VirtualEdgeIteratorState) origEdge).getOriginalEdgeKey()); } - Connection con = graphMapper.toConnection(ghEdge.getEdge()); + Connection con = graphMapper.toConnection(currEdge.getEdge()); if (con != null) { /* * In some cases, virtual edges are created at the target even though they are only some @@ -323,20 +417,56 @@ private CandidateRoute convertPath(Path newPath, QueryGraph queryGraph, QueryRes continue; } + if (pathConnections.isEmpty()) { + offsetFromSource = calcOffset(con, origEdge.getBaseNode(), nodes); + } + pathConnections.add(con.getId()); + lastNode = origEdge.getAdjNode(); + lastConnection = con; if (Double.isInfinite(newPath.getWeight())) { - log.warn( + LOG.warn( "Something went wrong during path search: The found route has infinite weight. Maybe there's a turn restriction or unconnected " + "sub-graphs in the network. Route will be ignored."); return null; } } else { - log.debug(String.format("A connection could be resolved by internal ID %d.", ghEdge.getEdge())); + LOG.debug(String.format("A connection could be resolved by internal ID %d.", origEdge.getEdge())); } } + + double offsetToTarget = 0; + if (lastConnection != null) { + offsetToTarget = lastConnection.getLength() - calcOffset(lastConnection, lastNode, nodes); + } + fixFirstConnectionOfPathIfNotAsQueried(sourcePosition, pathConnections); - return new CandidateRoute(pathConnections, newPath.getDistance(), newPath.getTime() / (double) 1000); + return new CandidateRoute( + pathConnections, + newPath.getDistance(), + newPath.getTime() / 1000.0, + offsetFromSource, + offsetToTarget + ); + } + + private double calcOffset(Connection con, int node, NodeAccess nodes) { + GeoPoint onConnection = GeoPoint.latLon(nodes.getLat(node), nodes.getLon(node)); + double offset = 0; + Node prev = null; + for (Node curr : con.getNodes()) { + if (prev != null) { + double distancePrevToCurr = prev.getPosition().distanceTo(curr.getPosition()); + double distancePrevToNode = prev.getPosition().distanceTo(onConnection); + if (distancePrevToNode < distancePrevToCurr) { + return Math.max(con.getLength(), offset + distancePrevToNode); + } + offset += distancePrevToCurr; + } + prev = curr; + } + return con.getLength(); } /** @@ -356,12 +486,4 @@ private void fixFirstConnectionOfPathIfNotAsQueried(RoutingPosition sourcePositi } } } - - private boolean checkRouteOnRequiredSourceConnection(CandidateRoute route, RoutingPosition source) { - if (source.getConnectionId() != null) { - return source.getConnectionId().equals(route.getConnectionIds().get(0)); - } - return true; - } - } diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/GraphHopperWeighting.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/GraphHopperWeighting.java index 2aae49a79..778ae9753 100644 --- a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/GraphHopperWeighting.java +++ b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/GraphHopperWeighting.java @@ -17,9 +17,11 @@ import org.eclipse.mosaic.lib.routing.RoutingCostFunction; import org.eclipse.mosaic.lib.routing.graphhopper.util.GraphhopperToDatabaseMapper; +import org.eclipse.mosaic.lib.routing.graphhopper.util.VehicleEncoding; +import org.eclipse.mosaic.lib.routing.graphhopper.util.WayTypeEncoder; -import com.graphhopper.routing.util.FlagEncoder; import com.graphhopper.routing.weighting.AbstractWeighting; +import com.graphhopper.routing.weighting.TurnCostProvider; import com.graphhopper.util.EdgeIteratorState; /** @@ -35,10 +37,15 @@ public class GraphHopperWeighting extends AbstractWeighting { private RoutingCostFunction routingCostFunction; - public GraphHopperWeighting(FlagEncoder encoder, GraphhopperToDatabaseMapper graphMapper) { - super(encoder); - this.edgePropertiesState = new GraphHopperEdgeProperties(encoder, graphMapper); - this.maxSpeed = encoder.getMaxSpeed() / 3.6; // getMaxSpeed returns the speed in km/h + public GraphHopperWeighting(VehicleEncoding vehicleEncoding, WayTypeEncoder wayTypeEncoder, TurnCostProvider turnCostProvider, GraphhopperToDatabaseMapper graphMapper) { + super(vehicleEncoding.access(), vehicleEncoding.speed(), turnCostProvider); + this.edgePropertiesState = new GraphHopperEdgeProperties(vehicleEncoding, wayTypeEncoder, graphMapper); + this.maxSpeed = speedEnc.getMaxOrMaxStorableDecimal() / 3.6; // getMaxOrMaxStorableDecimal returns the speed in km/h + } + + public GraphHopperWeighting setRoutingCostFunction(RoutingCostFunction routingCostFunction) { + this.routingCostFunction = routingCostFunction; + return this; } @Override @@ -47,7 +54,15 @@ public double getMinWeight(double distance) { } @Override - public double calcWeight(EdgeIteratorState edge, boolean reverse, int prevOrNextEdgeId) { + public double calcTurnWeight(int inEdge, int viaNode, int outEdge) { + return super.calcTurnWeight(inEdge, viaNode, outEdge); + } + + @Override + public double calcEdgeWeight(EdgeIteratorState edge, boolean reverse) { + if (reverse ? !edge.getReverse(accessEnc) : !edge.get(accessEnc)) { + return Double.POSITIVE_INFINITY; + } synchronized (edgePropertiesState) { edgePropertiesState.setCurrentEdgeIterator(edge, reverse); if (routingCostFunction == null) { @@ -60,14 +75,10 @@ public double calcWeight(EdgeIteratorState edge, boolean reverse, int prevOrNext public String getName() { if (routingCostFunction == null) { - return "null|" + flagEncoder; + return "fastest"; } else { - return routingCostFunction.getCostFunctionName().toLowerCase() + "|" + flagEncoder; + return routingCostFunction.getCostFunctionName().toLowerCase(); } } - public void setRoutingCostFunction(RoutingCostFunction routingCostFunction) { - this.routingCostFunction = routingCostFunction; - } - } diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/GraphLoader.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/GraphLoader.java deleted file mode 100644 index 90dc388c5..000000000 --- a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/GraphLoader.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2020 Fraunhofer FOKUS and others. All rights reserved. - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contact: mosaic@fokus.fraunhofer.de - */ - -package org.eclipse.mosaic.lib.routing.graphhopper; - -import org.eclipse.mosaic.lib.routing.graphhopper.util.GraphhopperToDatabaseMapper; - -import com.graphhopper.routing.util.EncodingManager; -import com.graphhopper.storage.GraphHopperStorage; - -/** - * Encapsulates the import procedure of a MOSAIC scenario database into a GraphHopper readable GraphStorage. - */ -public interface GraphLoader { - - /** - * Initializes the import process. - * - */ - void initialize(GraphHopperStorage graph, EncodingManager encodingManager, GraphhopperToDatabaseMapper mapper); - - /** - * Creates a graph. - */ - void loadGraph(); - -} diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/TurnWeightingOptional.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/TurnWeightingOptional.java deleted file mode 100644 index 939037a67..000000000 --- a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/TurnWeightingOptional.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2020 Fraunhofer FOKUS and others. All rights reserved. - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contact: mosaic@fokus.fraunhofer.de - */ - -package org.eclipse.mosaic.lib.routing.graphhopper; - -import com.graphhopper.routing.weighting.TurnWeighting; -import com.graphhopper.routing.weighting.Weighting; -import com.graphhopper.storage.TurnCostExtension; - -/** - * Extension of TurnWeighting which allows to disable - * the consideration of turn costs while still considering turn restrictions. - */ -class TurnWeightingOptional extends TurnWeighting { - - private final boolean enableTurnCosts; - private final TurnCostExtension tcExt; - private final double restrictedCosts; - - /** - * Creates a {@link TurnWeighting} extension which disables or enables the consideration of turn costs, but always - * considers turn restrictions. - * - * @param superWeighting the basic weighting implementation for road weighting - * @param turnCostExt the turn cost extension of the graph - * @param enableTurnCosts if true, turn costs and restrictions are considered, - * if false, only turn restrictions are considered - * @param restrictedCosts the costs to apply if a turn is restricted (use Double.POSITIVE_INFINITY to forbid turn) - */ - public TurnWeightingOptional(Weighting superWeighting, TurnCostExtension turnCostExt, boolean enableTurnCosts, double restrictedCosts) { - super(superWeighting, turnCostExt); - this.tcExt = turnCostExt; - this.restrictedCosts = restrictedCosts; - this.enableTurnCosts = enableTurnCosts; - } - - @Override - public double calcTurnWeight(int edgeFrom, int nodeVia, int edgeTo) { - long turnFlags = tcExt.getTurnCostFlags(edgeFrom, nodeVia, edgeTo); - if (getFlagEncoder().isTurnRestricted(turnFlags)) { - return restrictedCosts; - } - - if (enableTurnCosts) { - return getFlagEncoder().getTurnCost(turnFlags); - } - return 0; - } - -} \ No newline at end of file diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/AStarCamvitChoiceRouting.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/AStarCamvitChoiceRouting.java deleted file mode 100644 index 72d44a6b9..000000000 --- a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/AStarCamvitChoiceRouting.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright (c) 2020 Fraunhofer FOKUS and others. All rights reserved. - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contact: mosaic@fokus.fraunhofer.de - */ - -package org.eclipse.mosaic.lib.routing.graphhopper.algorithm; - -import com.carrotsearch.hppc.IntObjectMap; -import com.graphhopper.routing.weighting.BeelineWeightApproximator; -import com.graphhopper.routing.weighting.ConsistentWeightApproximator; -import com.graphhopper.routing.weighting.Weighting; -import com.graphhopper.storage.Graph; -import com.graphhopper.storage.SPTEntry; -import com.graphhopper.util.EdgeExplorer; -import com.graphhopper.util.EdgeIterator; -import com.graphhopper.util.Helper; -import edu.umd.cs.findbugs.annotations.SuppressWarnings; - -import java.util.PriorityQueue; - -/** - * Calculates alternative routes next to the best route, by using bidirectional AStar as routing - * engine and 'camvit choice routing' afterwards to determine alternative routes. - * - * @see AbstractCamvitChoiceRouting - * @see - */ -public class AStarCamvitChoiceRouting extends AbstractCamvitChoiceRouting { - - private final ConsistentWeightApproximator weightApprox; - - private int visitedToCount; - private int visitedFromCount; - - /** - * Creates a new {@link AStarCamvitChoiceRouting} object with a graph and the weighting of it. - * - * @param g The graph to check for suitability as alternative route. - * @param weighting The weighting of the given graph. - */ - public AStarCamvitChoiceRouting(Graph g, Weighting weighting) { - super(g, weighting); - BeelineWeightApproximator defaultApprox = new BeelineWeightApproximator(nodeAccess, weighting); - defaultApprox.setDistanceCalc(Helper.DIST_PLANE); - weightApprox = new ConsistentWeightApproximator(defaultApprox); - } - - @Override - public AbstractCamvitChoiceRouting initFrom(int from) { - - super.initFrom(from); - - weightApprox.setFrom(from); - - if (currTo != null) { - currFrom.weight += weightApprox.approximate(currFrom.adjNode, false); - currTo.weight += weightApprox.approximate(currTo.adjNode, true); - } - return this; - } - - @Override - public AbstractCamvitChoiceRouting initTo(int to) { - - super.initTo(to); - - weightApprox.setTo(to); - - if (currFrom != null) { - currFrom.weight += weightApprox.approximate(currFrom.adjNode, false); - currTo.weight += weightApprox.approximate(currTo.adjNode, true); - } - - return this; - } - - private void fillEdges(SPTEntry curr, PriorityQueue prioQueueOpenSet, - IntObjectMap shortestWeightMap, EdgeExplorer explorer, boolean reverse) { - - int currNode = curr.adjNode; - EdgeIterator iter = explorer.setBaseNode(currNode); - while (iter.next()) { - if (!accept(iter, curr)) { - continue; - } - - int traversalId = traversalMode.createTraversalId(iter, reverse); - double alreadyVisitedWeight = weighting.calcWeight(iter, reverse, curr.edge) + curr.weight; - - if (Double.isInfinite(alreadyVisitedWeight)) { - continue; - } - - AStarSPTEntry de = (AStarSPTEntry) shortestWeightMap.get(traversalId); - if (de == null || de.weight > alreadyVisitedWeight) { - - double currWeightToGoal = weightApprox.approximate(iter.getAdjNode(), reverse); - double estimationFullDist = alreadyVisitedWeight + currWeightToGoal; - if (de == null) { - de = new AStarSPTEntry(iter.getEdge(), iter.getAdjNode(), alreadyVisitedWeight, estimationFullDist); - shortestWeightMap.put(traversalId, de); - } else { - assert (de.weight > estimationFullDist) : "Inconsistent distance estimate"; - prioQueueOpenSet.remove(de); - de.edge = iter.getEdge(); - de.weight = alreadyVisitedWeight; - de.distanceEstimation = estimationFullDist; - } - - de.parent = curr; - prioQueueOpenSet.add(de); - } - } - } - - /** - * Creates the shortest path tree starting at the origin node. - * - * @return true if creating the source tree has been finished, otherwise false. - */ - protected boolean fillEdgesFrom(IntObjectMap weights, PriorityQueue heap) { - if (currFrom != null) { - if (finished(currFrom, to)) { - return true; - } - - fillEdges(currFrom, heap, weights, outEdgeExplorer, false); - visitedFromCount++; - if (heap.isEmpty()) { - return true; - } - currFrom = heap.poll(); - - } else if (currTo == null) { - //creating the source tree is finished when target tree has been finished as well - return true; - } - return false; - } - - protected boolean finished(SPTEntry currEdge, int to) { - return currEdge.adjNode == to; - } - - /** - * Creates the shortest path tree in reverse direction starting at the target node. - * - * @return true if creating the target tree has been finished, otherwise - * false - */ - protected boolean fillEdgesTo(IntObjectMap weights, PriorityQueue heap) { - if (currTo != null) { - if (finished(currTo, from)) { - return true; - } - fillEdges(currTo, heap, weights, inEdgeExplorer, true); - visitedToCount++; - if (heap.isEmpty()) { - return true; - } - currTo = heap.poll(); - } else if (currFrom == null) { - //creating the target tree is finished when source tree has been finished as well - return true; - } - return false; - } - - @Override - public int getVisitedNodes() { - return visitedFromCount + visitedToCount; - } - - @Override - public String getName() { - return "choiceroutingAstar"; - } - - @Override - SPTEntry createEdge(int edgeId, int endNode, double distance) { - return new AStarSPTEntry(edgeId, endNode, distance, 0); - } - - - /** - * extension of the {@link SPTEntry} which uses - * the distanceEstimation of AStar to sort existing - * {@link SPTEntry}s instead of the actual weight. - */ - @SuppressWarnings( - value="EQ_COMPARETO_USE_OBJECT_EQUALS", - justification = "Seems to be OK here, as extended class does not implement equals/hashCode anyhow. " - ) - private static class AStarSPTEntry extends SPTEntry { - - private double distanceEstimation; - - private AStarSPTEntry(int edgeId, int node, double weightForHeap, double distanceEstimation) { - super(edgeId, node, weightForHeap); - // round makes distance smaller => heuristic should underestimate the distance! - this.distanceEstimation = (float) distanceEstimation; - } - - - public int compareTo(SPTEntry o) { - if (o instanceof AStarSPTEntry) { - return Double.compare(this.distanceEstimation, ((AStarSPTEntry) o).distanceEstimation); - } else { - return super.compareTo(o); - } - } - } -} diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/AbstractCamvitChoiceRouting.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/AbstractCamvitChoiceRouting.java deleted file mode 100644 index b2a04bbe5..000000000 --- a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/AbstractCamvitChoiceRouting.java +++ /dev/null @@ -1,747 +0,0 @@ -/* - * Copyright (c) 2020 Fraunhofer FOKUS and others. All rights reserved. - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contact: mosaic@fokus.fraunhofer.de - */ - -package org.eclipse.mosaic.lib.routing.graphhopper.algorithm; - -import com.carrotsearch.hppc.IntObjectHashMap; -import com.carrotsearch.hppc.IntObjectMap; -import com.carrotsearch.hppc.cursors.IntCursor; -import com.graphhopper.routing.AbstractRoutingAlgorithm; -import com.graphhopper.routing.Path; -import com.graphhopper.routing.util.TraversalMode; -import com.graphhopper.routing.weighting.Weighting; -import com.graphhopper.storage.Graph; -import com.graphhopper.storage.SPTEntry; -import com.graphhopper.util.EdgeIterator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.PriorityQueue; -import java.util.Set; - -/** - * Implementation of the 'Camvit Choice Routing' algorithm to calculate alternative routes next to - * the best route. After building the source tree and target tree by the router on top of this - * class, edges which are in both trees will be determined. Afterwards, all of those edges which are - * connected to each other will be collected in a set of so called 'plateaus'. Those plateaus are - * either good (many edges / highest weight) or bad (few edges / low weight). One plateau can be - * used to calculate an alternative path from A to B going trough this plateau, by traversing from - * the start of a plateau to A by using the target tree and by traversing from the end of the - * plateau to B by using the source tree. By choosing the best plateau a good alternative route next - * to the best route can be found. - * - * @see - */ -public abstract class AbstractCamvitChoiceRouting extends AbstractRoutingAlgorithm implements AlternativeRoutesRoutingAlgorithm { - - private double constraintMaxShare = 0.9d; - private double constraintMaxStretch = 0.8d; - private double constraintMaxUniformlyBoundedStretch = 0.7d; - private double constraintThresholdLocalOptimality = 0.3d; - private double constraintMinLocalOptimality = 0.1d; - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - protected int from; - protected int to; - - private IntObjectMap shortestWeightsFrom; - private IntObjectMap shortestWeightsTo; - - private PriorityQueue heapFrom; - private PriorityQueue heapTo; - - private boolean alreadyRun; - SPTEntry currFrom; - SPTEntry currTo; - - private PriorityQueue plateaus; - private PriorityQueue relevantEdgesQ; - private HashSet relevantEdgesIDs; - - private int requestAlternatives = 0; - - private List alternativePaths; - - AbstractCamvitChoiceRouting(Graph g, Weighting type) { - super(g, type, TraversalMode.EDGE_BASED); - initCollections(Math.max(20, graph.getNodes())); - } - - /** - * initializes all required collections and maps. - * - * @param nodes the number of nodes in the graph - */ - private void initCollections(int nodes) { - try { - heapFrom = new PriorityQueue<>(nodes / 10); - shortestWeightsFrom = new IntObjectHashMap<>(nodes / 10); - - heapTo = new PriorityQueue<>(nodes / 10); - shortestWeightsTo = new IntObjectHashMap<>(nodes / 10); - } catch (OutOfMemoryError e) { - logger.error("Not sufficient memory", e); - throw e; - } - } - - - - /** - * Returns the identifier of an already traversed edge entry. In edge-based algorithms it is - * usually the id of the edge, but it might be extended by a flag which signalizes the direction - * of the edge (if algorithm is direction sensitive)- - * - * @return the identifier of an already traversed edge entry. - */ - private int createTraversalIdentifier(SPTEntry iter, boolean reverse) { - if (iter.parent != null) { - return traversalMode.createTraversalId(iter.parent.adjNode, iter.adjNode, iter.edge, reverse); - } - return iter.edge; - - } - - - /** - * Determines, if the currently traversed edge should be continued with. - * - * @param edge the edge which is currently being traversed. - * @param currEdge the preceding edge. - * @return true if this edge is acceptable to continue with. - */ - protected boolean accept(EdgeIterator edge, SPTEntry currEdge) { - return (currEdge.edge == EdgeIterator.NO_EDGE || edge.getEdge() != currEdge.edge) && super.accept(edge, currEdge.edge); - } - - /** - * Initializes the routing process by announcing the source node. - * - * @param from the source node - */ - public AbstractCamvitChoiceRouting initFrom(int from) { - this.from = from; - currFrom = createEdge(EdgeIterator.NO_EDGE, from, 0); - return this; - } - - /** - * Creates the first initial edge. - * - * @return an instance of CRAStarEdge - */ - abstract SPTEntry createEdge(int edgeId, int endNode, double distance); - - /** - * Initializes the routing process by announcing the target node. - * - * @param to the target node. - */ - public AbstractCamvitChoiceRouting initTo(int to) { - this.to = to; - currTo = createEdge(EdgeIterator.NO_EDGE, to, 0); - return this; - } - - @Override - public void setRequestAlternatives(int alternatives) { - this.requestAlternatives = alternatives; - } - - /** - * Calculates the best and alternative paths between the specified nodes. - * - * @param from Start of the path. - * @param to End of the path. - * @return The calculated path. - */ - @Override - public Path calcPath(int from, int to) { - if (alreadyRun) { - throw new IllegalStateException("Create a new instance per call"); - } - alreadyRun = true; - - alternativePaths = new ArrayList(); - - logger.debug("calc " + (requestAlternatives + 1) + " paths from " + from + " to " + to); - - if (from == to) { - return new Path(graph, weighting); - } - - initFrom(from); - initTo(to); - - boolean finished = false; - while (!finished) { - finished = fillEdgesFrom(shortestWeightsFrom, heapFrom) & fillEdgesTo(shortestWeightsTo, - heapTo); - } - - PlateauPath path = extractPath(); - - if (requestAlternatives > 0) { - // determine edges which has been traversed in both source and target - // tree - determineRelevantEdges(path.getWeight()); - - // determine edges which are connected with each other - determinePlateaus(path.getWeight()); - - // extracts alternative paths - calculateAlternativePaths(path); - } - - return path; - } - - /** - * Returns a list of alternative paths (not including the optimal path). - * - * @return The alternative paths, is empty if no alternative paths have been requested. - */ - @Override - public List getAlternativePaths() { - return alternativePaths; - } - - public PlateauPath extractPath() { - PlateauPath path = (PlateauPath) new PlateauPath(graph, weighting).setSPTEntry(currFrom).extract(); - path.setWeight(currFrom.weight); - return path; - } - - /** - * Executes one step of traversal in forward direction starting at the source node. - * - * @return true if creating the source tree has been finished, otherwise - * false - */ - abstract boolean fillEdgesFrom(IntObjectMap weights, PriorityQueue heap); - - /** - * Executes one step of traversal in backward direction starting at the target node. - * - * @return true if creating the target tree has been finished, otherwise - * false - */ - abstract boolean fillEdgesTo(IntObjectMap weights, PriorityQueue heap); - - /** - * @return true, if both path searches reached their final node. - */ - abstract boolean finished(SPTEntry currEdge, int to); - - @Override - protected boolean finished() { - throw new UnsupportedOperationException("Not required"); - } - - /** - * Collects all edges which have been traversed by both forward and backward path searches. All - * edges receive a weighting depending on their distances towards the target and source node. - * Furthermore, edges which are far away from the best path are ignored to speed up the search. - * - * @param optimalWeight the weight of the optimal path. - */ - private void determineRelevantEdges(double optimalWeight) { - relevantEdgesQ = new PriorityQueue<>(Math.max(1, shortestWeightsFrom.size() / 10), - Comparator.comparingDouble(o -> o.rating) - ); - - relevantEdgesIDs = new HashSet<>(); - - double maxWeight = optimalWeight * (1.0d + constraintMaxStretch); - - for (IntCursor key : shortestWeightsFrom.keys()) { - SPTEntry edge = shortestWeightsFrom.get(key.value); - if (edge.parent != null) { - SPTEntry edgeOfTargetTree = shortestWeightsTo.get(key.value); - if (edgeOfTargetTree != null && edgeOfTargetTree.edge == edge.edge && edge.adjNode != edgeOfTargetTree.adjNode) { - //create a new relevant edge if it has been found in both search trees - //the rating is composed by the distances of the edge towards source and target - - RelEdge relEdge = new RelEdge(); - relEdge.fwd = edge; - relEdge.bwd = edgeOfTargetTree; - relEdge.rating = (edgeOfTargetTree.parent != null) - ? edge.weight + edgeOfTargetTree.parent.weight - : edgeOfTargetTree.weight + edge.parent.weight; - - if (relEdge.rating < maxWeight && relEdge.fwd.weight < Double.MAX_VALUE - && relEdge.bwd.weight < Double.MAX_VALUE) { - relevantEdgesQ.add(relEdge); - relevantEdgesIDs.add(key.value); - } - } - } - } - } - - /** - * The set of relevant edges is used to build plateaus. A plateau consists of a sequence of - * relevant edges. If a plateau has been built, all its edges are removed out of the set of - * relevant edges. The relevant edge with the lowest rating is used to build the next plateau. - * - * @param optimalWeight the weight of the optimal path. - */ - private void determinePlateaus(double optimalWeight) { - plateaus = new PriorityQueue(Math.max(1, relevantEdgesQ.size() / 10), - new Comparator() { - @Override - public int compare(Plateau o1, Plateau o2) { - // sub paths with equal rating but highest weight on top - int r = Double.compare(o1.rating(), o2.rating()); - if (r == 0) { - return Double.compare(o1.weight(), o2.weight()); - } else { - return r; - } - } - }); - - double bestRating = Double.MAX_VALUE; - - while (!relevantEdgesQ.isEmpty()) { - RelEdge relEdge = relevantEdgesQ.poll(); - - bestRating = Math.min(relEdge.rating, bestRating); - - // if edge pointers of current relevant edge is already a member of - // a plateau, reject this relevant edge - if (!relevantEdgesIDs.contains(createTraversalIdentifier(relEdge.fwd, false)) - && !relevantEdgesIDs.contains(createTraversalIdentifier(relEdge.bwd, true))) { - continue; - } - - // if rating is twice expensive than best rating, then cancel search - // for plateaus - if (relEdge.rating > bestRating * 2) { - relevantEdgesQ.clear(); - break; - } - - // create new plateau - Plateau plateau = new Plateau(); - plateau.start = relEdge.fwd; - plateau.startRev = relEdge.bwd; - plateau.targetRev = relEdge.bwd; - - // the distance of relevant edge from target - double distance = plateau.startRev.weight; - - // traverse backward until we found the start of the plateaus, that - // is as long the parent edge is relevant - SPTEntry tmp = plateau.start.parent; - while (tmp != null && tmp.parent != null - && relevantEdgesIDs.remove(createTraversalIdentifier(tmp, false)) - ) { - distance += tmp.weight - tmp.parent.weight; - // build new edge entry for the backward pointer startRev - SPTEntry entry = new SPTEntry(tmp.edge, tmp.parent.adjNode, distance); - entry.parent = plateau.startRev; - plateau.startRev = entry; - plateau.start = tmp; - tmp = tmp.parent; - } - - // traverse forward until we found the end of the plateaus - tmp = plateau.targetRev.parent; - while (tmp != null && tmp.parent != null - && relevantEdgesIDs.remove(createTraversalIdentifier(tmp, true)) - ) { - plateau.targetRev = tmp; - tmp = tmp.parent; - } - - // the local optimality = weight of plateau / weight of optimal path - plateau.localOptimality = (plateau.startRev.weight - plateau.targetRev.parent.weight) / optimalWeight; - - // if plateau consists of only one edge or is not locally optimal, - // then reject it - if (plateau.start.edge != plateau.targetRev.edge && plateau.localOptimality >= constraintMinLocalOptimality) { - plateaus.add(plateau); - } - - // mark the relevant edge as used - relevantEdgesIDs.remove(createTraversalIdentifier(relEdge.fwd, false)); - relevantEdgesIDs.remove(createTraversalIdentifier(relEdge.bwd, true)); - } - - // we don't need those anymore - relevantEdgesIDs.clear(); - } - - /** - * Out of the plateaus, path are generated. - * - * @param optimalPath the optimal path - */ - private void calculateAlternativePaths(PlateauPath optimalPath) { - alternativePaths.clear(); - - if (requestAlternatives == 0) { - return; - } - - if (plateaus != null) { - // the first subPath is also the best path we've already found, so - // we refuse it as an alternative - plateaus.poll(); - } - - // add alternative paths as long as their weight is not greater than - // weight of original path + drift - PlateauPath nextPath = createPathFromPlateau(optimalPath); - while (nextPath != null) { - - // logger.debug("Found next path: weight "+nextPath.getWeight()+""); - alternativePaths.add(nextPath); - // continue with next path, if we not yet have the desired number of - // paths (maxPath < 0 means as many as possible) - nextPath = (alternativePaths.size() < requestAlternatives || requestAlternatives < 0) ? createPathFromPlateau( - optimalPath) : null; - } - } - - private PlateauPath createPathFromPlateau(PlateauPath optimalPath) { - if (plateaus.isEmpty()) { - return null; - } - - // get plateau with highest weight (the next best sub path) - final Plateau plateau = plateaus.poll(); - - // weight of path - final double pathWeight = plateau.startRev.weight + plateau.start.weight; - - // weight of plateau - final double plateauWeight = plateau.startRev.weight - plateau.targetRev.parent.weight; - - double ubs = 0; - - // if local optimality is below a certain threshold, the uniformly - // bounded stretch will be calculated (quite expensive procedure) - if (plateau.localOptimality < constraintThresholdLocalOptimality) { - double maxWeight = plateauWeight + (optimalPath.getWeight() - plateauWeight) / 2; - - while (maxWeight > plateauWeight * 1.5d) { - ubs = Math.max(ubs, calculateUniformleyBoundedStretch(plateau, maxWeight)); - maxWeight = plateauWeight + (maxWeight - plateauWeight) / 2; - - //if UBS exceeds the max value, the path will be rejected - if (ubs > constraintMaxUniformlyBoundedStretch) { - return createPathFromPlateau(optimalPath); - } - } - } - - final String debugInfo = String.format(Locale.ENGLISH, - "%s, ubs: %.2f, lo: %.2f, weight_optimal: %.2f, weight_plateau: %.2f", - createPlateauString(plateau), ubs, plateau.localOptimality, optimalPath.getWeight(), - plateauWeight); - final PlateauPath path = new PlateauPath(graph, weighting) { - public String getDebugInfo() { - return debugInfo; - } - }; - - // target node - final int to = optimalPath.getSptEntry().adjNode; - - // create path by traversing downwards until target node - SPTEntry targetEdge = plateau.start; - SPTEntry currentRevOfPlateau = plateau.startRev.parent; - while (currentRevOfPlateau != null) { - int endNode = (currentRevOfPlateau.parent != null) ? currentRevOfPlateau.parent.adjNode : to; - SPTEntry newEntry = new SPTEntry(currentRevOfPlateau.edge, endNode, pathWeight - currentRevOfPlateau.weight); - newEntry.parent = targetEdge; - targetEdge = newEntry; - currentRevOfPlateau = currentRevOfPlateau.parent; - if (endNode == to) { - break; - } - } - path.setWeight(targetEdge.weight); - path.setSPTEntry(targetEdge).extract(); - - if (filter(optimalPath, path)) { - return createPathFromPlateau(optimalPath); - } else { - return path; - } - } - - /** - * Applies several filters on a newly generated path. If one filter returns true, the new path - * will be rejected. Following filters are applied:
  • - * Route Share
  • - * Route Stretch
  • - * Contains loop
- */ - private boolean filter(PlateauPath optimalPath, PlateauPath newPath) { - /* - * it is sufficient to check similarity against the best path only, - * because plateaus derive only from the best path. - */ - if (checkSimilarity(optimalPath, newPath) > constraintMaxShare) { - return true; - } - - // filter route, if it's more than 1+STRETCH_MAX times longer (distance) - // than the shortest (distance) route - if (newPath.getDistance() > optimalPath.getDistance() * (1 + constraintMaxStretch)) { - return true; - } - - if (containsLoop(newPath)) { - return true; - } - - return false; - } - - /** - * This method checks whether a loop between the new and the previous founded paths exist. - * - * @param newPath The new path to check for loop. - * @return True if it contains a loop. - */ - private boolean containsLoop(PlateauPath newPath) { - Set visitedEdges = new HashSet<>(); - - // first edge of already found path - SPTEntry edge = newPath.getSptEntry(); - - while (edge.parent.edge != EdgeIterator.NO_EDGE) { - if (!visitedEdges.add(createTraversalIdentifier(edge, false))) { - return true; - } - edge = edge.parent; - } - - return false; - } - - /** - * This method checks the similarity between an existing and new path. - * - * @param existingPath The existing path to check for similarity with a new path. - * @param newPath The new path to check for similarity with an existing path. - * @return A factor, which indicating the similarity. - */ - private double checkSimilarity(PlateauPath existingPath, PlateauPath newPath) { - - Set edgesOfOriginal = new HashSet(); - // first edge of already found path - SPTEntry edge1 = existingPath.getSptEntry(); - // first edge of new path - SPTEntry edge2 = newPath.getSptEntry(); - - while (edge1.parent.edge != EdgeIterator.NO_EDGE) { - edgesOfOriginal.add(edge1.edge); - edge1 = edge1.parent; - } - - double sharedWeight = 0.0d; - while (edge2.parent.edge != EdgeIterator.NO_EDGE) { - if (edgesOfOriginal.remove(edge2.edge)) { - sharedWeight += Math.abs(edge2.weight - edge2.parent.weight); - } - edge2 = edge2.parent; - } - - return sharedWeight / existingPath.getWeight(); - } - - /** - * Calculates the value for uniformly bounded stretch (UBS) for a plateau. UBS describes the - * stretch of the full detour. Since a shortest path search is required this procedure is quite - * expensive. - * - * @return the stretch of the detour (0 means no stretch) - */ - private double calculateUniformleyBoundedStretch(Plateau plateau, double maxWeight) { - - // the weight of the plateau - double weightDetour = plateau.startRev.weight - plateau.targetRev.parent.weight; - - // the start and end edge of the plateau (pointers) - SPTEntry start = plateau.start; - SPTEntry end = plateau.targetRev; - - while (start.parent.edge != EdgeIterator.NO_EDGE || end.parent.edge != EdgeIterator.NO_EDGE) { - - // move the start point towards the source and add its weight - if (start.parent.edge != EdgeIterator.NO_EDGE) { - weightDetour += (start.weight - start.parent.weight); - start = start.parent; - } - - // move the end point towards the target and add its weight - if (end.parent.edge != EdgeIterator.NO_EDGE) { - weightDetour += (end.weight - end.parent.weight); - end = end.parent; - } - - if (weightDetour >= maxWeight) { - double weightShortest = calculateWeightOfShortestSubPath(start, end); - return weightDetour / weightShortest - 1; - } - } - return 0; - } - - private double calculateWeightOfShortestSubPath(SPTEntry start, SPTEntry end) { - from = start.adjNode; - to = end.adjNode; - initCollections(Math.max(20, graph.getNodes())); - initFrom(from); - initTo(to); - boolean finished = false; - while (!finished) { - finished = fillEdgesFrom(shortestWeightsFrom, heapFrom); - } - return currFrom.weight; - } - - /** - * Creates a {@link String} for the {@link Plateau} with start and end points. - * - * @param plateau The {@link Plateau} for which to create the string. - * @return The created {@link Plateau} string. - */ - private String createPlateauString(Plateau plateau) { - StringBuilder plateauString = new StringBuilder(); - plateauString.append("plateau: "); - SPTEntry tmpEdge = plateau.startRev; - while (tmpEdge.parent.edge != EdgeIterator.NO_EDGE && tmpEdge.edge != plateau.targetRev.edge) { - plateauString.append(tmpEdge.edge); - plateauString.append("|"); - if (tmpEdge.parent.edge == plateau.targetRev.edge) { - plateauString.append(tmpEdge.parent.edge); - } - tmpEdge = tmpEdge.parent; - } - return plateauString.toString(); - } - - /** - * Represents a relevant edge. A relevant edge has been traversed by both path searches. - */ - public static class RelEdge { - - /** - * EdgeEntry whose parent points along the shortest path towards the source node. Its weight - * is the distance from this edge to the source node. - */ - SPTEntry fwd; - - /** - * EdgeEntry whose parent points along the shortest path towards the target node. Its weight - * is the distance from this edge to the target node. - */ - SPTEntry bwd; - - /** - * A rating of the relevant edge which is composed of the sum of the costs of both edge - * entries. - */ - double rating; - } - - /** - * A plateau represents a sequence of edges which have been traversed by both path searches. - */ - public static class Plateau { - - /** - * Marks the start of the plateau and points towards the source of the path. By following - * its parents the shortest path from the start of the plateau towards the source node can - * be determined. - */ - SPTEntry start; - - /** - * Marks the start of the plateau and points towards the target of the path. By following - * its parents the path along the plateau towards the target can be determined. - */ - SPTEntry startRev; - - /** - * Marks the end of the plateau and points towards the target of the path. By following its - * parents the shortest path from the end of the plateau towards the target node can be - * determined. - */ - SPTEntry targetRev; - - /** - * Stores the local optimality of the plateau which has previously been calculated. - */ - double localOptimality; - - /** - * Calculates the weight of the resulting path of the plateau. - */ - public double weight() { - if (start.parent == null) { - return startRev.weight; - } else { - return start.parent.weight + startRev.weight; - } - } - - /** - * Calculates the rating of the plateau. The farther away from source or target the higher - * the rating. - */ - public double rating() { - double costsAtStartOfPlateau = (start.parent == null) ? 0 : start.parent.weight; - double costsAtEndOfPlateau = (targetRev.parent == null) ? 0 : targetRev.parent.weight; - - return costsAtStartOfPlateau + costsAtEndOfPlateau; - - } - } - - public final void setUbsMax(double ubsMax) { - this.constraintMaxUniformlyBoundedStretch = ubsMax; - } - - public final void setRouteShareMax(double shareMax) { - this.constraintMaxShare = shareMax; - } - - public final void setRouteStretchMax(double stretchMax) { - this.constraintMaxStretch = stretchMax; - } - - public final void setLocalOptimalityMin(double loMin) { - this.constraintMinLocalOptimality = loMin; - } - - public final void setUbsThreshold(double ubsThreshold) { - this.constraintThresholdLocalOptimality = ubsThreshold; - } - - -} diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/AlternativeRoutesRoutingAlgorithm.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/AlternativeRoutesRoutingAlgorithm.java deleted file mode 100644 index 12370853c..000000000 --- a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/AlternativeRoutesRoutingAlgorithm.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2020 Fraunhofer FOKUS and others. All rights reserved. - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contact: mosaic@fokus.fraunhofer.de - */ - -package org.eclipse.mosaic.lib.routing.graphhopper.algorithm; - -import com.graphhopper.routing.Path; -import com.graphhopper.routing.RoutingAlgorithm; - -import java.util.List; - -/** - * Mark an routing algorithm which is able to generate alternative routes. - */ -public interface AlternativeRoutesRoutingAlgorithm extends RoutingAlgorithm { - - /** - * Defines the number of additional alternative routes to calculate. - * - * @param alternatives the number of alternative routes. - */ - void setRequestAlternatives(int alternatives); - - /** - * Returns all alternative paths excluding the best path. - * - * @return all alternative paths - */ - List getAlternativePaths(); - -} diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/BellmanFordRouting.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/BellmanFordRouting.java index 229b6f3af..e3f4a3577 100644 --- a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/BellmanFordRouting.java +++ b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/BellmanFordRouting.java @@ -20,14 +20,16 @@ import com.carrotsearch.hppc.cursors.IntObjectCursor; import com.graphhopper.routing.AbstractRoutingAlgorithm; import com.graphhopper.routing.Path; -import com.graphhopper.routing.util.DefaultEdgeFilter; +import com.graphhopper.routing.PathExtractor; +import com.graphhopper.routing.SPTEntry; import com.graphhopper.routing.util.TraversalMode; import com.graphhopper.routing.weighting.Weighting; import com.graphhopper.storage.Graph; -import com.graphhopper.storage.SPTEntry; import com.graphhopper.util.EdgeExplorer; import com.graphhopper.util.EdgeIterator; import com.graphhopper.util.EdgeIteratorState; +import com.graphhopper.util.GHUtility; +import com.graphhopper.util.PMap; /** * Implementation of the Bellman-Ford algorithm which supports negative edge costs. @@ -39,16 +41,13 @@ public class BellmanFordRouting extends AbstractRoutingAlgorithm { private final IntObjectMap edgeEntries = new IntObjectHashMap<>(); private final IntObjectMap nodeEntries = new IntObjectHashMap<>(); - private SPTEntry toNode = null; - private boolean finished = false; - /** * Creates a new {@link BellmanFordRouting} object based on the {@link AbstractRoutingAlgorithm}. * * @param graph specifies the graph where this algorithm will run on. * @param weighting set the used weight calculation (e.g. fastest, shortest). */ - public BellmanFordRouting(Graph graph, Weighting weighting) { + public BellmanFordRouting(Graph graph, Weighting weighting, PMap hints) { super(graph, weighting, TraversalMode.EDGE_BASED); } @@ -61,12 +60,9 @@ public BellmanFordRouting(Graph graph, Weighting weighting) { */ @Override public Path calcPath(int from, int to) { - finished = false; - edgeEntries.clear(); nodeEntries.clear(); - toNode = null; - + determineAllEdges(); createStartCondition(from); @@ -83,9 +79,11 @@ public Path calcPath(int from, int to) { throw new IllegalStateException("There's a cycle with negative weights."); } - toNode = nodeEntries.get(to); - finished = true; - return extractPath(); + SPTEntry toNode = nodeEntries.get(to); + if (toNode != null) { + return PathExtractor.extractPath(graph, weighting, toNode); + } + return null; } /** @@ -110,7 +108,7 @@ private boolean updateWeightsOfEdges() { continue; } - tmpWeight = this.weighting.calcWeight(edgeIt, false, u.edge) + u.weight; + tmpWeight = GHUtility.calcWeightWithTurnWeight(weighting, edgeIt, false, u.edge) + u.weight; if (tmpWeight < edge.weight) { edge.weight = tmpWeight; @@ -127,12 +125,12 @@ private boolean updateWeightsOfEdges() { } private void determineAllEdges() { - final EdgeExplorer edgeExplorer = graph.createEdgeExplorer(DefaultEdgeFilter.outEdges(flagEncoder)); + final EdgeExplorer edgeExplorer = graph.createEdgeExplorer(); for (int node = 0; node < graph.getNodes(); node++) { EdgeIterator edgeIt = edgeExplorer.setBaseNode(node); while (edgeIt.next()) { - SPTEntry entry = new SPTEntry(edgeIt.getEdge(), edgeIt.getAdjNode(), Double.POSITIVE_INFINITY); + SPTEntry entry = new SPTEntry(edgeIt.getEdge(), edgeIt.getAdjNode(), Double.POSITIVE_INFINITY, null); int id = traversalMode.createTraversalId(edgeIt, false); edgeEntries.put(id, entry); nodeEntries.put(edgeIt.getAdjNode(), entry); @@ -141,24 +139,11 @@ private void determineAllEdges() { } private void createStartCondition(int from) { - nodeEntries.put(from, new SPTEntry(EdgeIterator.NO_EDGE, from, 0)); + nodeEntries.put(from, new SPTEntry(from, 0)); } @Override public int getVisitedNodes() { return this.graph.getNodes(); } - - @Override - protected boolean finished() { - return finished; - } - - @Override - protected Path extractPath() { - if (toNode != null) { - return new Path(this.graph, this.weighting).setWeight(toNode.weight).setSPTEntry(toNode).extract(); - } - return null; - } } diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/DijkstraCamvitChoiceRouting.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/DijkstraCamvitChoiceRouting.java deleted file mode 100644 index fdca81214..000000000 --- a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/DijkstraCamvitChoiceRouting.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (c) 2020 Fraunhofer FOKUS and others. All rights reserved. - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contact: mosaic@fokus.fraunhofer.de - */ - -package org.eclipse.mosaic.lib.routing.graphhopper.algorithm; - -import com.carrotsearch.hppc.IntObjectMap; -import com.graphhopper.routing.weighting.Weighting; -import com.graphhopper.storage.Graph; -import com.graphhopper.storage.SPTEntry; -import com.graphhopper.util.EdgeExplorer; -import com.graphhopper.util.EdgeIterator; - -import java.util.PriorityQueue; - -/** - * Calculates alternative routes next to the best route, by using bidirectional Dijkstra as routing - * engine and 'camvit choice routing' afterwards to determine alternative routes. - * - * @see AbstractCamvitChoiceRouting - * @see
- */ -public class DijkstraCamvitChoiceRouting extends AbstractCamvitChoiceRouting { - - private int visitedToCount; - private int visitedFromCount; - - /** - * Creates a new {@link DijkstraCamvitChoiceRouting} object while using Dijkstra and camvit choice routing. - * - * @param g Specifies the graph where this algorithm will run on. - * @param type Set the used weight calculation. - */ - public DijkstraCamvitChoiceRouting(Graph g, Weighting type) { - super(g, type); - } - - void fillEdges(SPTEntry curr, PriorityQueue prioQueue, - IntObjectMap shortestWeightMap, EdgeExplorer explorer, boolean reverse) { - - EdgeIterator iter = explorer.setBaseNode(curr.adjNode); - while (iter.next()) { - if (!accept(iter, curr)) { - continue; - } - - int traversalId = traversalMode.createTraversalId(iter, reverse);//createTraversalIdentifier(iter, reverse); - double tmpWeight = weighting.calcWeight(iter, reverse, curr.edge) + curr.weight; - - if (Double.isInfinite(tmpWeight)) { - continue; - } - - SPTEntry de = shortestWeightMap.get(traversalId); - if (de == null) { - de = new SPTEntry(iter.getEdge(), iter.getAdjNode(), tmpWeight); - de.parent = curr; - shortestWeightMap.put(traversalId, de); - prioQueue.add(de); - } else if (de.weight > tmpWeight) { - prioQueue.remove(de); - de.edge = iter.getEdge(); - de.weight = tmpWeight; - de.parent = curr; - prioQueue.add(de); - } - } - } - - /** - * Creates the shortest path tree starting at the source node. - * - * @return true if creating the source tree has been finished, otherwise - * false - */ - protected boolean fillEdgesFrom(IntObjectMap weights, PriorityQueue heap) { - if (currFrom != null) { - if (finished(currFrom, to)) { - return true; - } - - fillEdges(currFrom, heap, weights, outEdgeExplorer, false); - visitedFromCount++; - if (heap.isEmpty()) { - return true; - } - currFrom = heap.poll(); - - } else if (currTo == null) { - //creating the source tree is finished when target tree has been finished as well - return true; - } - return false; - } - - protected boolean finished(SPTEntry currEdge, int to) { - return currEdge.adjNode == to; - } - - /** - * Creates the shortest path tree in reverse direction starting at the target node. - * - * @return true if creating the target tree has been finished, otherwise - * false - */ - protected boolean fillEdgesTo(IntObjectMap weights, PriorityQueue heap) { - if (currTo != null) { - if (finished(currTo, from)) { - return true; - } - fillEdges(currTo, heap, weights, inEdgeExplorer, true); - visitedToCount++; - if (heap.isEmpty()) { - return true; - } - currTo = heap.poll(); - } else if (currFrom == null) { - //creating the target tree is finished when source tree has been finished as well - return true; - } - return false; - } - - @Override - public int getVisitedNodes() { - return visitedFromCount + visitedToCount; - } - - @Override - public String getName() { - return "choicerouting"; - } - - @Override - SPTEntry createEdge(int edgeId, int endNode, double distance) { - return new SPTEntry(edgeId, endNode, distance); - } - -} diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/PlateauPath.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/PlateauPath.java deleted file mode 100644 index c72902500..000000000 --- a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/PlateauPath.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2020 Fraunhofer FOKUS and others. All rights reserved. - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contact: mosaic@fokus.fraunhofer.de - */ - -package org.eclipse.mosaic.lib.routing.graphhopper.algorithm; - -import com.graphhopper.routing.Path; -import com.graphhopper.routing.weighting.Weighting; -import com.graphhopper.storage.Graph; -import com.graphhopper.storage.SPTEntry; - -/** - * Extension of {@link Path} providing access to the shortest path tree entry ({@link SPTEntry}). - */ -class PlateauPath extends Path { - - PlateauPath(Graph graph, Weighting weighting) { - super(graph, weighting); - } - - SPTEntry getSptEntry() { - return sptEntry; - } -} diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/RoutingAlgorithmFactory.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/RoutingAlgorithmFactory.java index 9e08e9cff..e061ad6a0 100644 --- a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/RoutingAlgorithmFactory.java +++ b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/RoutingAlgorithmFactory.java @@ -15,16 +15,33 @@ package org.eclipse.mosaic.lib.routing.graphhopper.algorithm; +import org.eclipse.mosaic.lib.routing.graphhopper.GraphHopperRouting; + +import com.graphhopper.routing.AStarBidirection; +import com.graphhopper.routing.AlternativeRoute; import com.graphhopper.routing.RoutingAlgorithm; +import com.graphhopper.routing.util.TraversalMode; import com.graphhopper.routing.weighting.Weighting; import com.graphhopper.storage.Graph; +import com.graphhopper.util.PMap; +import com.graphhopper.util.Parameters; /** * Factory to instantiate the routing algorithm to be used. */ public interface RoutingAlgorithmFactory { - RoutingAlgorithmFactory CHOICE_ROUTING_DIJKSTRA = DijkstraCamvitChoiceRouting::new; + RoutingAlgorithmFactory DEFAULT = (graph, weighting, hints) -> { + if (hints.getInt(Parameters.Algorithms.AltRoute.MAX_PATHS, 1) > 1) { + hints.putObject(Parameters.Algorithms.AltRoute.MAX_SHARE, GraphHopperRouting.ALTERNATIVE_ROUTES_MAX_SHARE); + hints.putObject(Parameters.Algorithms.AltRoute.MAX_WEIGHT, GraphHopperRouting.ALTERNATIVE_ROUTES_MAX_WEIGHT); + hints.putObject("alternative_route.max_exploration_factor", GraphHopperRouting.ALTERNATIVE_ROUTES_EXPLORATION_FACTOR); + hints.putObject("alternative_route.min_plateau_factor", GraphHopperRouting.ALTERNATIVE_ROUTES_PLATEAU_FACTOR); + return new AlternativeRoute(graph, weighting, TraversalMode.EDGE_BASED, hints); + } else { + return new AStarBidirection(graph, weighting, TraversalMode.EDGE_BASED); + } + }; RoutingAlgorithmFactory BELLMAN_FORD = BellmanFordRouting::new; @@ -32,10 +49,10 @@ public interface RoutingAlgorithmFactory { * Creates a {@link RoutingAlgorithm} instance for calculating routes based * on the given {@link Graph} and {@link Weighting} function. * - * @param graph the {@link Graph} containing the network to find routes in + * @param graph the {@link Graph} containing the network to find routes in * @param weighting the cost function * @return the {@link RoutingAlgorithm} */ - RoutingAlgorithm createAlgorithm(Graph graph, Weighting weighting); + RoutingAlgorithm createAlgorithm(Graph graph, Weighting weighting, PMap hints); } diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/extended/ExtendedGHPoint.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/extended/ExtendedGHPoint.java deleted file mode 100644 index 3b3a22125..000000000 --- a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/extended/ExtendedGHPoint.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2020 Fraunhofer FOKUS and others. All rights reserved. - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contact: mosaic@fokus.fraunhofer.de - */ - -package org.eclipse.mosaic.lib.routing.graphhopper.extended; - -import com.graphhopper.util.NumHelper; -import com.graphhopper.util.shapes.GHPoint; - -public class ExtendedGHPoint extends GHPoint { - - private final int edgeId; - - public ExtendedGHPoint(double lat, double lon, int edgeId) { - super(lat, lon); - this.edgeId = edgeId; - } - - public int getEdgeId() { - return edgeId; - } - - @Override - public int hashCode() { - int hash = super.hashCode(); - hash = 83 * hash + edgeId; - return hash; - } - - @Override - public boolean equals(Object obj) { - if (!super.equals(obj) || !(obj instanceof ExtendedGHPoint)) { - return false; - } - return NumHelper.equals(edgeId, ((ExtendedGHPoint) obj).edgeId); - } - -} diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/extended/ExtendedGHRequest.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/extended/ExtendedGHRequest.java deleted file mode 100644 index e71d6e837..000000000 --- a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/extended/ExtendedGHRequest.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2020 Fraunhofer FOKUS and others. All rights reserved. - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contact: mosaic@fokus.fraunhofer.de - */ - -package org.eclipse.mosaic.lib.routing.graphhopper.extended; - -import org.eclipse.mosaic.lib.routing.graphhopper.algorithm.RoutingAlgorithmFactory; - -import com.graphhopper.GHRequest; -import com.graphhopper.routing.weighting.Weighting; -import com.graphhopper.storage.Graph; -import com.graphhopper.util.shapes.GHPoint; - -/** - * Extends GHRequest by providing instances of weighting and algorithms preparation instead of - * defining string constants. Additionally the number of alternative routes can be set in this - * request. - * - */ -public class ExtendedGHRequest extends GHRequest { - - protected WeightingFactory weightingFactory; - protected int alternatives; - protected RoutingAlgorithmFactory routingAlgorithmFactory; - - public ExtendedGHRequest(GHPoint from, GHPoint to) { - super(from, to); - this.routingAlgorithmFactory = RoutingAlgorithmFactory.CHOICE_ROUTING_DIJKSTRA; - } - - public ExtendedGHRequest setWeightingFactory(WeightingFactory w) { - this.weightingFactory = w; - return this; - } - - public WeightingFactory getWeightingFactory() { - return weightingFactory; - } - - public Weighting getWeightingInstance(Graph graph) { - return weightingFactory.create(graph); - } - - public int getAlternatives() { - return alternatives; - } - - public void setAlternatives(int alternatives) { - this.alternatives = alternatives; - - } - - public RoutingAlgorithmFactory getAlgorithmFactory() { - return routingAlgorithmFactory; - } - - public void setRoutingAlgorithmFactory(RoutingAlgorithmFactory routingAlgorithmFactory) { - this.routingAlgorithmFactory = routingAlgorithmFactory; - } - - public interface WeightingFactory { - Weighting create(Graph graph); - } -} diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/extended/ExtendedGHResponse.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/extended/ExtendedGHResponse.java deleted file mode 100644 index e73f24c2a..000000000 --- a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/extended/ExtendedGHResponse.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2020 Fraunhofer FOKUS and others. All rights reserved. - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contact: mosaic@fokus.fraunhofer.de - */ - -package org.eclipse.mosaic.lib.routing.graphhopper.extended; - -import com.graphhopper.GHResponse; -import com.graphhopper.routing.Path; - -import java.util.List; -import java.util.Vector; - -/** - * Extends the response of a routing request by providing the calculated path and several child - * response containing alternative routes. - */ -public class ExtendedGHResponse extends GHResponse { - - private Path path; - private List responses; - - public ExtendedGHResponse setPath(Path path) { - this.path = path; - this.responses = new Vector<>(); - return this; - } - - public Path getPath() { - return path; - } - - public List getAdditionalRoutes() { - return responses; - } - - public void addRouteResponse(ExtendedGHResponse rsp) { - this.responses.add(rsp); - } - -} diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/extended/ExtendedGraphHopper.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/extended/ExtendedGraphHopper.java deleted file mode 100644 index ff96b5bd0..000000000 --- a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/extended/ExtendedGraphHopper.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright (c) 2020 Fraunhofer FOKUS and others. All rights reserved. - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contact: mosaic@fokus.fraunhofer.de - */ - -package org.eclipse.mosaic.lib.routing.graphhopper.extended; - -import org.eclipse.mosaic.lib.routing.graphhopper.GraphLoader; -import org.eclipse.mosaic.lib.routing.graphhopper.algorithm.AlternativeRoutesRoutingAlgorithm; -import org.eclipse.mosaic.lib.routing.graphhopper.util.GraphhopperToDatabaseMapper; - -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; -import com.graphhopper.GHRequest; -import com.graphhopper.GHResponse; -import com.graphhopper.GraphHopper; -import com.graphhopper.routing.Path; -import com.graphhopper.routing.QueryGraph; -import com.graphhopper.routing.RoutingAlgorithm; -import com.graphhopper.routing.util.BikeFlagEncoder; -import com.graphhopper.routing.util.CarFlagEncoder; -import com.graphhopper.routing.util.DefaultEdgeFilter; -import com.graphhopper.routing.util.EdgeFilter; -import com.graphhopper.routing.util.EncodingManager; -import com.graphhopper.routing.util.FlagEncoder; -import com.graphhopper.routing.util.FootFlagEncoder; -import com.graphhopper.routing.weighting.Weighting; -import com.graphhopper.storage.GHDirectory; -import com.graphhopper.storage.GraphHopperStorage; -import com.graphhopper.storage.RAMDirectory; -import com.graphhopper.storage.TurnCostExtension; -import com.graphhopper.storage.index.QueryResult; -import com.graphhopper.util.StopWatch; -import com.graphhopper.util.shapes.GHPoint; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.List; - -/** - * An extension of GraphHopper which is able to import map data from the MOSAIC scenario database. - */ -public class ExtendedGraphHopper extends GraphHopper { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - protected GraphLoader graphLoader; - protected TurnCostExtension tcStorage; - protected GraphhopperToDatabaseMapper mapper; - - public ExtendedGraphHopper(GraphLoader graphLoader, GraphhopperToDatabaseMapper mapper) { - this.graphLoader = graphLoader; - this.mapper = mapper; - - setCHEnabled(false); - setEncodingManager(EncodingManager.create( - new CarFlagEncoder(5, 5, 127), - new BikeFlagEncoder(), - new FootFlagEncoder()) - ); - } - - @Override - public GraphHopper importOrLoad() { - GHDirectory dir = new RAMDirectory(); - - tcStorage = new TurnCostExtension() { - // is required since we need to store the way type of edges - public boolean isRequireEdgeField() { - return true; - } - - public int getDefaultEdgeFieldValue() { - return 0; - } - }; - - GraphHopperStorage graph = new GraphHopperStorage(dir, getEncodingManager(), true, tcStorage); - - setGraphHopperStorage(graph); - graph.setSegmentSize(-1); - - importDB(getGraphHopperLocation()); - - return this; - } - - private void importDB(String ghLocation) { - if (getGraphHopperStorage() == null) { - throw new IllegalStateException("Load or init graph before import database"); - } - - if (mapper == null) { - throw new IllegalStateException("A mapper is required for importing database"); - } - - logger.info("start creating graph from database"); - - graphLoader.initialize(getGraphHopperStorage(), getEncodingManager(), mapper); - graphLoader.loadGraph(); - - postProcessing(); - try { - cleanUp(); - } catch (Exception e) { - logger.warn("Could not clean up routing graph, skipping. Routing might not work as expected!"); - } - // optimize(); - flush(); - } - - @Override - public GHResponse route(GHRequest request) { - if (request instanceof ExtendedGHRequest) { - return route((ExtendedGHRequest) request); - } - throw new UnsupportedOperationException("ExtendedGHRequest is required"); - } - - private ExtendedGHResponse route(ExtendedGHRequest request) { - if (getGraphHopperStorage() == null) { - throw new IllegalStateException("no graph has yet been initialized"); - } - - ExtendedGHResponse rsp = new ExtendedGHResponse(); - - if (!getEncodingManager().hasEncoder(request.getVehicle())) { - rsp.addError( - new IllegalArgumentException("Vehicle " + request.getVehicle() + " unsupported. Supported are: " + getEncodingManager()) - ); - return rsp; - } - - FlagEncoder encoder = getEncodingManager().getEncoder(request.getVehicle()); - - GHPoint fromPoint = Iterables.getFirst(request.getPoints(), null); - GHPoint toPoint = Iterables.getLast(request.getPoints(), null); - - EdgeFilter edgeFilterFrom = createEdgeFilter(fromPoint, encoder); - EdgeFilter edgeFilterTo = createEdgeFilter(toPoint, encoder); - - if (fromPoint == null || toPoint == null) { - rsp.addError(new IllegalArgumentException("Not enough points given")); - return rsp; - } - - StopWatch sw = new StopWatch().start(); - - final QueryResult fromRes = getLocationIndex().findClosest(fromPoint.lat, fromPoint.lon, edgeFilterFrom); - final QueryResult toRes = getLocationIndex().findClosest(toPoint.lat, toPoint.lon, edgeFilterTo); - - if (fromRes.getClosestNode() < 0 || toRes.getClosestNode() < 0) { - rsp.addError(new IllegalArgumentException("Request location(s) can not be snapped to the network")); - return rsp; - } - - QueryGraph queryGraph = new QueryGraph(getGraphHopperStorage()); - queryGraph.lookup(Lists.newArrayList(fromRes, toRes)); - - String debug = "graphLookup:" + sw.stop().getSeconds() + "s"; - - sw = new StopWatch().start(); - Weighting weighting = request.getWeightingInstance(queryGraph); - - RoutingAlgorithm algorithm = request.getAlgorithmFactory().createAlgorithm(queryGraph, weighting); - - if (algorithm instanceof AlternativeRoutesRoutingAlgorithm && request.getAlternatives() > 0) { - ((AlternativeRoutesRoutingAlgorithm) algorithm).setRequestAlternatives(request.getAlternatives()); - } - - debug += ", algoInit:" + sw.stop().getSeconds() + "s"; - sw = new StopWatch().start(); - - Path bestPath = algorithm.calcPath(fromRes.getClosestNode(), toRes.getClosestNode()); - debug += ", " + algorithm.getName() + "-routing:" + sw.stop().getSeconds() + "s, " + bestPath.getDebugInfo(); - - final List paths = new ArrayList(); - paths.add(bestPath); - if (algorithm instanceof AlternativeRoutesRoutingAlgorithm) { - paths.addAll(((AlternativeRoutesRoutingAlgorithm) algorithm).getAlternativePaths()); - } - - ExtendedGHResponse currentResponse = null; - int i = 1; - for (Path path : paths) { - if (currentResponse == null) { - // the first path is always the requested shortest path - currentResponse = rsp; - } else { - // if more than one route returned, we add a new response - currentResponse = new ExtendedGHResponse(); - rsp.addRouteResponse(currentResponse); - debug = "alternative path: " + (i++) + ", " + path.getDebugInfo(); - } - currentResponse.setPath(path).addDebugInfo(debug); - } - return rsp; - } - - - - private EdgeFilter createEdgeFilter(final GHPoint point, FlagEncoder encoder) { - if (point instanceof ExtendedGHPoint) { - return edgeState -> edgeState.getEdge() == ((ExtendedGHPoint) point).getEdgeId(); - } - return DefaultEdgeFilter.allEdges(encoder); - } - -} diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/DatabaseGraphLoader.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/util/DatabaseGraphLoader.java similarity index 69% rename from lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/DatabaseGraphLoader.java rename to lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/util/DatabaseGraphLoader.java index 9272863bb..230ae975f 100644 --- a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/DatabaseGraphLoader.java +++ b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/util/DatabaseGraphLoader.java @@ -13,24 +13,25 @@ * Contact: mosaic@fokus.fraunhofer.de */ -package org.eclipse.mosaic.lib.routing.graphhopper; +package org.eclipse.mosaic.lib.routing.graphhopper.util; import org.eclipse.mosaic.lib.database.Database; import org.eclipse.mosaic.lib.database.DatabaseUtils; import org.eclipse.mosaic.lib.database.road.Connection; import org.eclipse.mosaic.lib.database.road.Node; import org.eclipse.mosaic.lib.database.road.Way; -import org.eclipse.mosaic.lib.routing.graphhopper.util.GraphhopperToDatabaseMapper; -import org.eclipse.mosaic.lib.routing.graphhopper.util.TurnCostAnalyzer; -import org.eclipse.mosaic.lib.routing.graphhopper.util.WayTypeEncoder; +import org.eclipse.mosaic.lib.routing.graphhopper.GraphHopperRouting; import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.util.EncodingManager; -import com.graphhopper.routing.util.FlagEncoder; -import com.graphhopper.storage.GraphHopperStorage; +import com.graphhopper.routing.ev.EdgeIntAccess; +import com.graphhopper.routing.util.DefaultVehicleTagParserFactory; +import com.graphhopper.routing.util.VehicleTagParsers; +import com.graphhopper.routing.util.parsers.TagParser; +import com.graphhopper.storage.BaseGraph; import com.graphhopper.storage.IntsRef; -import com.graphhopper.storage.TurnCostExtension; +import com.graphhopper.storage.TurnCostStorage; import com.graphhopper.util.EdgeIteratorState; +import com.graphhopper.util.PMap; import com.graphhopper.util.PointList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,36 +44,41 @@ /** * Loads the content of a MOSAIC scenario database into a graphhopper graph. */ -public class DatabaseGraphLoader implements GraphLoader { +public class DatabaseGraphLoader { private static final Logger LOG = LoggerFactory.getLogger(DatabaseGraphLoader.class); - private GraphHopperStorage graphStorage; - private EncodingManager encodingManager; - private FlagEncoder flagEncoder; + private final Database database; + private final List wayParsers = new ArrayList<>(); + private BaseGraph graphStorage; + private VehicleEncodingManager encodingManager; private GraphhopperToDatabaseMapper graphMapper; - private TurnCostExtension turnCostStorage; - private Database database; + private TurnCostStorage turnCostStorage; + private EdgeIntAccess edgeAccess; public DatabaseGraphLoader(Database database) { this.database = database; } - @Override - public void initialize(GraphHopperStorage graph, - EncodingManager encodingManager, GraphhopperToDatabaseMapper mapper) { + public void initialize(BaseGraph graph, + VehicleEncodingManager encodingManager, + GraphhopperToDatabaseMapper mapper + ) { this.graphStorage = graph; this.graphMapper = mapper; this.encodingManager = encodingManager; - this.flagEncoder = encodingManager.getEncoder("CAR"); - if (graph.getExtension() instanceof TurnCostExtension) { - turnCostStorage = (TurnCostExtension) (graph).getExtension(); - } else { - throw new IllegalStateException("Graph must store turn costs by using TurnCostStorage."); + this.turnCostStorage = graph.getTurnCostStorage(); + + wayParsers.clear(); + + for (String vehicle : encodingManager.getAllProfileVehicles()) { + VehicleTagParsers parsers = new DefaultVehicleTagParserFactory() + .createParsers(encodingManager.getEncodingManager(), vehicle, new PMap()); + wayParsers.add(parsers.getAccessParser()); + wayParsers.add(parsers.getSpeedParser()); } } - @Override public void loadGraph() { int nodeIndex = 0; @@ -82,6 +88,9 @@ public void loadGraph() { final Set mainGraph = searchForMainGraph(); graphStorage.create(Math.max(mainGraph.size() / 30, 100)); + edgeAccess = graphStorage.createEdgeIntAccess(); + + WayTypeEncoder wayTypeEncoder = encodingManager.wayType(); // add nodes and connections for (Connection con : database.getConnections()) { @@ -106,24 +115,19 @@ public void loadGraph() { nodeIndex++; } - IntsRef flags = createFlags(encodingManager, way); - - if (flags != null) { - EdgeIteratorState edgeIt = graphStorage.edge( - graphMapper.fromNode(from), - graphMapper.fromNode(to)).setDistance(con.getLength()).setFlags(flags); + EdgeIteratorState edgeIt = graphStorage.edge(graphMapper.fromNode(from), graphMapper.fromNode(to)) + .setDistance(con.getLength()) + .setWayGeometry(getWayGeometry(from, to, con.getNodes())); + handleWayTags(edgeIt.getEdge(), way); - graphMapper.setConnection(con, edgeIt.getEdge()); + wayTypeEncoder.setWay(way, con.getLanes(), edgeIt.getEdge(), edgeAccess); - edgeIt.setWayGeometry(getWayGeometry(from, to, con.getNodes())); - edgeIt.setAdditionalField( - WayTypeEncoder.encode(way.getType(), way.getNumberOfLanesForward(), 0)); - } else { - LOG.warn("Connection on (way={}, type={}) is not accessible by any vehicle type" - + " and will therefore be ignored during routing.", way.getId(), way.getType()); - } + graphMapper.setConnection(con, edgeIt.getEdge()); } + VehicleEncoding car = encodingManager.getVehicleEncoding(GraphHopperRouting.PROFILE_CAR.getVehicle()); + VehicleEncoding bike = encodingManager.getVehicleEncoding(GraphHopperRouting.PROFILE_BIKE.getVehicle()); + // add turn restrictions for (Connection conFrom : database.getConnections()) { @@ -154,21 +158,25 @@ public void loadGraph() { // if end node is connected with an connection, which is not // accessible from incoming connection, then add turn // restriction - turnCostStorage.addTurnInfo(ghConFrom, ghNodeVia, ghConTo, - flagEncoder.getTurnFlags(true, 0)); + turnCostStorage.set( + car.turnRestriction(), ghConFrom, ghNodeVia, ghConTo, true + ); + turnCostStorage.set( + bike.turnRestriction(), ghConFrom, ghNodeVia, ghConTo, true + ); } else if (isUTurn) { // avoid (high costs) u-turns on same road, that is if from and to node - // of first connection are the same nodes as of second - // connection - turnCostStorage.addTurnInfo(ghConFrom, ghNodeVia, ghConTo, - flagEncoder.getTurnFlags(false, endsAtJunction ? 120 : 90)); + // of first connection are the same nodes as of second connection + // note, only for cars, bikes can easily turn + turnCostStorage.set( + car.turnCost(), ghConFrom, ghNodeVia, ghConTo, endsAtJunction ? 120 : 90 + ); } } } // analyze turn costs - new TurnCostAnalyzer().createTurnCostsForCars(graphStorage, - encodingManager.getEncoder("CAR")); + new TurnCostAnalyzer(graphStorage, wayTypeEncoder).createTurnCostsForVehicle(car); if (graphStorage.getNodes() == 0) { throw new IllegalStateException( @@ -209,23 +217,17 @@ private void addNode(int nodeIndex, final Node node) { graphMapper.setNode(node, nodeIndex); } - /** - * Rebuild an OSMway to get internal edge properties as flags. - * - * @param encoding Encoder parameter. - * @param way Way for which to create flags. - */ - private IntsRef createFlags(EncodingManager encoding, Way way) { + private void handleWayTags(int edgeId, Way way) { + IntsRef relationFlags = IntsRef.EMPTY; + ReaderWay osmWay = new ReaderWay(0); osmWay.setTag("highway", way.getType()); - osmWay.setTag("maxspeed", String.valueOf((int) way.getMaxSpeedInKmh())); + osmWay.setTag("maxspeed", String.valueOf((int) Math.round(way.getMaxSpeedInKmh()))); osmWay.setTag("oneway", "yes"); - EncodingManager.AcceptWay aw = new EncodingManager.AcceptWay(); - if (encoding.acceptWay(osmWay, aw)) { - return encoding.handleWayTags(osmWay, aw, 0); + for (TagParser parser : wayParsers) { + parser.handleWayTags(edgeId, edgeAccess, osmWay, relationFlags); } - return null; } /** diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/util/OptionalTurnCostProvider.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/util/OptionalTurnCostProvider.java new file mode 100644 index 000000000..843039579 --- /dev/null +++ b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/util/OptionalTurnCostProvider.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2023 Fraunhofer FOKUS and others. All rights reserved. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contact: mosaic@fokus.fraunhofer.de + */ + +package org.eclipse.mosaic.lib.routing.graphhopper.util; + +import com.graphhopper.routing.ev.BooleanEncodedValue; +import com.graphhopper.routing.ev.DecimalEncodedValue; +import com.graphhopper.routing.weighting.TurnCostProvider; +import com.graphhopper.storage.TurnCostStorage; +import com.graphhopper.util.EdgeIterator; + +public class OptionalTurnCostProvider implements TurnCostProvider { + + private final BooleanEncodedValue turnRestrictionsEnc; + private final DecimalEncodedValue turnCostsEnc; + private final TurnCostStorage turnCostStorage; + + private boolean turnCostsEnabled = true; + + public OptionalTurnCostProvider(VehicleEncoding encoding, TurnCostStorage turnCostStorage) { + if (turnCostStorage == null) { + throw new IllegalArgumentException("No storage set to calculate turn weight"); + } + this.turnRestrictionsEnc = encoding.turnRestriction(); + this.turnCostsEnc = encoding.turnCost(); + this.turnCostStorage = turnCostStorage; + } + + /** + * Disables consideration of turn costs. Only turn restrictions are checked. + */ + public TurnCostProvider disableTurnCosts() { + this.turnCostsEnabled = false; + return this; + } + + @Override + public double calcTurnWeight(int edgeFrom, int nodeVia, int edgeTo) { + if (!EdgeIterator.Edge.isValid(edgeFrom) || !EdgeIterator.Edge.isValid(edgeTo)) { + return 0; + } + if (turnRestrictionsEnc != null) { + boolean restricted = turnCostStorage.get(turnRestrictionsEnc, edgeFrom, nodeVia, edgeTo); + if (restricted) { + return Double.POSITIVE_INFINITY; + } + } + if (turnCostsEnc != null && turnCostsEnabled) { + return turnCostStorage.get(turnCostsEnc, edgeFrom, nodeVia, edgeTo); + } + return 0; + } + + @Override + public long calcTurnMillis(int inEdge, int viaNode, int outEdge) { + return (long) (1000 * calcTurnWeight(inEdge, viaNode, outEdge)); + } + + @Override + public String toString() { + return "mosaic_tcp"; + } +} diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/util/TurnCostAnalyzer.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/util/TurnCostAnalyzer.java index 3f0255589..976ed287a 100644 --- a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/util/TurnCostAnalyzer.java +++ b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/util/TurnCostAnalyzer.java @@ -18,13 +18,13 @@ import org.eclipse.mosaic.lib.geo.GeoPoint; import org.eclipse.mosaic.lib.geo.GeoUtils; -import com.graphhopper.routing.util.DefaultEdgeFilter; -import com.graphhopper.routing.util.FlagEncoder; +import com.graphhopper.routing.util.AccessFilter; +import com.graphhopper.storage.BaseGraph; import com.graphhopper.storage.Graph; -import com.graphhopper.storage.GraphHopperStorage; -import com.graphhopper.storage.TurnCostExtension; +import com.graphhopper.storage.TurnCostStorage; import com.graphhopper.util.EdgeExplorer; import com.graphhopper.util.EdgeIterator; +import com.graphhopper.util.FetchMode; import com.graphhopper.util.PointList; import com.graphhopper.util.shapes.GHPoint; @@ -55,11 +55,15 @@ public class TurnCostAnalyzer { * TODO make depend on road type */ private double minCurveRadius; + private final WayTypeEncoder waytypeEncoder; + private final BaseGraph graph; /** * Crates a new {@link TurnCostAnalyzer} with default calculation properties. */ - public TurnCostAnalyzer() { + public TurnCostAnalyzer(BaseGraph graph, WayTypeEncoder waytypeEncoder) { + this.graph = graph; + this.waytypeEncoder = waytypeEncoder; /* * Mean acceleration and deceleration for cars * (according to [Maurya et al: "Acceleration-Deceleration Behaviour of Various Vehicle Types", 2016]). @@ -69,21 +73,14 @@ public TurnCostAnalyzer() { minCurveRadius = 20; } - public void createTurnCostsForCars(GraphHopperStorage graph, FlagEncoder flagEncoder) { - EdgeExplorer outEdgeExplorer = graph.createEdgeExplorer(DefaultEdgeFilter.outEdges(flagEncoder)); - EdgeExplorer inEdgeExplorer = graph.createEdgeExplorer(DefaultEdgeFilter.inEdges(flagEncoder)); - final TurnCostExtension tcStorage; - if (graph.getExtension() instanceof TurnCostExtension) { - tcStorage = (TurnCostExtension) graph.getExtension(); - if (!tcStorage.isRequireEdgeField()) { - throw new IllegalStateException( - "Graph does not support storing additional edge fields"); - } - } else { - throw new IllegalStateException("Graph does not support storing of turn costs"); - } + public void createTurnCostsForVehicle(VehicleEncoding encoding) { + + EdgeExplorer outEdgeExplorer = graph.createEdgeExplorer(AccessFilter.allEdges(encoding.access())); + EdgeExplorer inEdgeExplorer = graph.createEdgeExplorer(AccessFilter.allEdges(encoding.access())); + + TurnCostStorage tcStorage = graph.getTurnCostStorage(); for (int n = 0; n < graph.getNodes(); n++) { EdgeIterator inEdgeIt = inEdgeExplorer.setBaseNode(n); @@ -91,14 +88,13 @@ public void createTurnCostsForCars(GraphHopperStorage graph, FlagEncoder flagEnc EdgeIterator outEdgeIt = outEdgeExplorer.setBaseNode(n); while (outEdgeIt.next()) { if (inEdgeIt.getEdge() != outEdgeIt.getEdge()) { - long existingTurnCosts = tcStorage.getTurnCostFlags(inEdgeIt.getEdge(), n, - outEdgeIt.getEdge()); + boolean alreadyRestricted = tcStorage.get(encoding.turnRestriction(), inEdgeIt.getEdge(), n, outEdgeIt.getEdge()); + double existingCosts = tcStorage.get(encoding.turnCost(), inEdgeIt.getEdge(), n, outEdgeIt.getEdge()); //if turn is already restricted, than we do not need to consider turn costs - if (!flagEncoder.isTurnRestricted(existingTurnCosts)) { - double c = calculateTurnCosts(graph, flagEncoder, inEdgeIt, n, outEdgeIt); + if (!alreadyRestricted) { + double c = calculateTurnCosts(encoding, inEdgeIt, n, outEdgeIt) + existingCosts; if (c > 0.00001) { - long turnFlags = flagEncoder.getTurnFlags(false, Math.ceil(c)); - tcStorage.addTurnInfo(inEdgeIt.getEdge(), n, outEdgeIt.getEdge(), turnFlags); + tcStorage.set(encoding.turnCost(), inEdgeIt.getEdge(), n, outEdgeIt.getEdge(), Math.ceil(c)); } } } @@ -114,29 +110,25 @@ public void createTurnCostsForCars(GraphHopperStorage graph, FlagEncoder flagEnc * into geometric points. Further parameter for the calculating are the angle between incoming and outgoing edge, * the length and the maximum speed of the edge. * - * @param graph Graph fot which to calculate the turn costs - * @param flagEncoder Encoding the flag * @param incomingEdge Incoming edge * @param node ID of the node * @param outgoingEdge Outgoing edge * @return Calculated turn cost */ - private double calculateTurnCosts(GraphHopperStorage graph, FlagEncoder flagEncoder, EdgeIterator incomingEdge, int node, - EdgeIterator outgoingEdge) { + private double calculateTurnCosts(VehicleEncoding encoding, EdgeIterator incomingEdge, int node, EdgeIterator outgoingEdge) { //determining way types - int incomingWayType = incomingEdge.getAdditionalField(); - int outgoingWayType = outgoingEdge.getAdditionalField(); + int incomingWayType = incomingEdge.get(waytypeEncoder); + int outgoingWayType = outgoingEdge.get(waytypeEncoder); //we ignore turn costs, as soon as we are on highways if (WayTypeEncoder.isHighway(incomingWayType) && WayTypeEncoder.isHighway(outgoingWayType)) { return 0; } - //use way geometry to calculate + //use way geometry to calculate final GHPoint pointFrom = getSecondLastPointOfEdge(graph, incomingEdge); - final GHPoint pointVia = new GHPoint(graph.getNodeAccess().getLatitude(node), - graph.getNodeAccess().getLongitude(node)); + final GHPoint pointVia = new GHPoint(graph.getNodeAccess().getLat(node), graph.getNodeAccess().getLon(node)); final GHPoint pointTo = getSecondPointOfEdge(graph, outgoingEdge); //calculate angle between incoming/outgoing edge using bearing of from-via and via-to @@ -148,8 +140,8 @@ private double calculateTurnCosts(GraphHopperStorage graph, FlagEncoder flagEnco double alpha = (bearingViaTo - (bearingFromVia) + 360) % 360; //get length and max speed of edges - double v1 = incomingEdge.get(flagEncoder.getAverageSpeedEnc()) / 3.6; - double v2 = outgoingEdge.get(flagEncoder.getAverageSpeedEnc()) / 3.6; + double v1 = incomingEdge.get(encoding.speed()) / 3.6; + double v2 = outgoingEdge.get(encoding.speed()) / 3.6; double l1 = incomingEdge.getDistance(); double l2 = outgoingEdge.getDistance(); @@ -166,12 +158,12 @@ private double calculateTurnCosts(GraphHopperStorage graph, FlagEncoder flagEnco //else, the maximum turn speed depends on the angle between incoming and outgoing edge } else { if (isLeftTurn || isRightTurn) { - //when turning, the max speed on the outgoing edge also depends on its length (i.e., if the + //when turning, the max speed on the outgoing edge also depends on its length (i.e., if the //outgoing edge is very short, we don't need to accelerate till max speed) v2 = Math.min(v2, MAX_A * Math.sqrt((2 * l2) / MAX_A)); } - //adjusting angle: tangens only gives positive values in [0,180] + //adjusting angle: tangens only gives positive values in [0,180] if (alpha > 180) { alpha = 360 - alpha; } @@ -209,13 +201,13 @@ private double calculateTurnCosts(GraphHopperStorage graph, FlagEncoder flagEnco * @return the second last coordinate of an edge. */ private GHPoint getSecondLastPointOfEdge(Graph graph, EdgeIterator edge) { - PointList geom = edge.fetchWayGeometry(GEOMETRY_FETCH_MODE_PILLAR_NODES_ONLY); - if (geom.getSize() == 0) { - return new GHPoint(graph.getNodeAccess().getLatitude(edge.getAdjNode()), - graph.getNodeAccess().getLongitude(edge.getAdjNode())); + PointList geom = edge.fetchWayGeometry(FetchMode.PILLAR_ONLY); + if (geom.isEmpty()) { + return new GHPoint(graph.getNodeAccess().getLat(edge.getAdjNode()), + graph.getNodeAccess().getLon(edge.getAdjNode())); } - int index = geom.getSize() - 1; - return new GHPoint(geom.getLatitude(index), geom.getLongitude(index)); + int index = geom.size() - 1; + return new GHPoint(geom.getLat(index), geom.getLon(index)); } /** @@ -225,12 +217,12 @@ private GHPoint getSecondLastPointOfEdge(Graph graph, EdgeIterator edge) { * @return the second coordinate of an edge. */ private GHPoint getSecondPointOfEdge(Graph graph, EdgeIterator edge) { - PointList geom = edge.fetchWayGeometry(GEOMETRY_FETCH_MODE_PILLAR_NODES_ONLY); - if (geom.getSize() == 0) { - return new GHPoint(graph.getNodeAccess().getLatitude(edge.getAdjNode()), - graph.getNodeAccess().getLongitude(edge.getAdjNode())); + PointList geom = edge.fetchWayGeometry(FetchMode.PILLAR_ONLY); + if (geom.isEmpty()) { + return new GHPoint(graph.getNodeAccess().getLat(edge.getAdjNode()), + graph.getNodeAccess().getLon(edge.getAdjNode())); } - return new GHPoint(geom.getLatitude(0), geom.getLongitude(0)); + return new GHPoint(geom.getLat(0), geom.getLon(0)); } } diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/util/VehicleEncoding.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/util/VehicleEncoding.java new file mode 100644 index 000000000..1ec7c1cf5 --- /dev/null +++ b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/util/VehicleEncoding.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2023 Fraunhofer FOKUS and others. All rights reserved. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contact: mosaic@fokus.fraunhofer.de + */ + +package org.eclipse.mosaic.lib.routing.graphhopper.util; + +import com.graphhopper.config.Profile; +import com.graphhopper.routing.ev.BooleanEncodedValue; +import com.graphhopper.routing.ev.DecimalEncodedValue; +import com.graphhopper.routing.ev.Subnetwork; +import com.graphhopper.routing.ev.TurnCost; +import com.graphhopper.routing.ev.TurnRestriction; +import com.graphhopper.routing.util.DefaultVehicleEncodedValuesFactory; +import com.graphhopper.routing.util.VehicleEncodedValues; +import com.graphhopper.util.PMap; + +/** + * Collection of all {@link com.graphhopper.routing.ev.EncodedValue} implementations + * required for a vehicle to function within the GraphHopper context. This includes: + * - "access" - If a vehicle can drive on an edge.
+ * - "speed" - The speed limit for the vehicle on the edge.
+ * - "priority" - Encode how to prioritize a road type to another.
+ * - "subnetwork" - Encode if an edge belongs to a subnetwork
+ * - "turnRestriction" / "turnCost" - encoding of costs for a turn from on edge to another.
+ * These encoders take care of storing and reading properties on edges. + */ +public class VehicleEncoding { + + private final BooleanEncodedValue accessEnc; + private final DecimalEncodedValue speedEnc; + private final BooleanEncodedValue turnRestrictionEnc; + private final DecimalEncodedValue turnCostEnc; + private final DecimalEncodedValue priorityEnc; + private final BooleanEncodedValue subnetworkEnc; + + VehicleEncoding(Profile profile) { + VehicleEncodedValues vehicle = new DefaultVehicleEncodedValuesFactory() + .createVehicleEncodedValues(profile.getVehicle(), new PMap()); + this.accessEnc = vehicle.getAccessEnc(); + this.speedEnc = vehicle.getAverageSpeedEnc(); + this.priorityEnc = vehicle.getPriorityEnc(); + this.turnRestrictionEnc = TurnRestriction.create(profile.getVehicle()); + this.turnCostEnc = TurnCost.create(profile.getVehicle(), 255); + this.subnetworkEnc = Subnetwork.create(profile.getVehicle()); + } + + public BooleanEncodedValue access() { + return accessEnc; + } + + public DecimalEncodedValue speed() { + return speedEnc; + } + + public DecimalEncodedValue priority() { + return priorityEnc; + } + + public BooleanEncodedValue turnRestriction() { + return turnRestrictionEnc; + } + + public DecimalEncodedValue turnCost() { + return turnCostEnc; + } + + public BooleanEncodedValue subnetwork() { + return subnetworkEnc; + } +} diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/util/VehicleEncodingManager.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/util/VehicleEncodingManager.java new file mode 100644 index 000000000..80c05ea17 --- /dev/null +++ b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/util/VehicleEncodingManager.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2023 Fraunhofer FOKUS and others. All rights reserved. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contact: mosaic@fokus.fraunhofer.de + */ + +package org.eclipse.mosaic.lib.routing.graphhopper.util; + +import com.graphhopper.config.Profile; +import com.graphhopper.routing.util.EncodingManager; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * In GraphHopper, any data for edges, nodes, and turns, are stored with as low overhead + * as possible. To achieve this, {@link com.graphhopper.routing.ev.EncodedValue}s + * to encode and decode any data. This class, encapsulates the initialization and access to single + * {@link com.graphhopper.routing.ev.EncodedValue}s, making it easier to use them in code. + */ +public class VehicleEncodingManager { + + private final WayTypeEncoder waytypeEncoder; + private final EncodingManager encodingManager; + + private final Map vehicleEncodings = new HashMap<>(); + private final List profiles; + + public VehicleEncodingManager(List profiles) { + this.waytypeEncoder = WayTypeEncoder.create(); + this.profiles = new ArrayList<>(profiles); + + EncodingManager.Builder builder = new EncodingManager.Builder().add(waytypeEncoder); + for (Profile profile : this.profiles) { + final VehicleEncoding encoding = new VehicleEncoding(profile); + vehicleEncodings.put(profile.getVehicle(), encoding); + builder.add(encoding.access()) + .add(encoding.speed()) + .addTurnCostEncodedValue(encoding.turnRestriction()) + .addTurnCostEncodedValue(encoding.turnCost()) + .add(encoding.subnetwork()); + if (encoding.priority() != null) { + builder.add(encoding.priority()); + } + } + this.encodingManager = builder.build(); + } + + public List getAllProfiles() { + return Collections.unmodifiableList(profiles); + } + + /** + * Returns a list of all possible transportation modes (e.g. "car", "bike"). + */ + public Collection getAllProfileVehicles() { + return Collections.unmodifiableCollection(vehicleEncodings.keySet()); + } + + /** + * Returns the specific wrapper of {@link com.graphhopper.routing.ev.EncodedValue}s required + * for the given transportation mode (e.g. "car", "bike"). + */ + public VehicleEncoding getVehicleEncoding(String vehicle) { + return vehicleEncodings.get(vehicle); + } + + /** + * Returns an encoder, which is used to encode/decode precomputed flags for way types. + */ + public WayTypeEncoder wayType() { + return waytypeEncoder; + } + + /** + * Returns the actual encoding manager used in GraphHopper. + */ + public EncodingManager getEncodingManager() { + return encodingManager; + } +} diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/util/WayTypeEncoder.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/util/WayTypeEncoder.java index 09b3f4371..8b8ccd252 100644 --- a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/util/WayTypeEncoder.java +++ b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/graphhopper/util/WayTypeEncoder.java @@ -15,13 +15,24 @@ package org.eclipse.mosaic.lib.routing.graphhopper.util; +import org.eclipse.mosaic.lib.database.road.Way; + import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.google.common.collect.Sets; +import com.graphhopper.routing.ev.EdgeIntAccess; +import com.graphhopper.routing.ev.IntEncodedValueImpl; import java.util.Set; -public class WayTypeEncoder { +/** + * Stores additional properties on an edge to describe the way-type of an edge. + * It does so by encoding the way-type, and some boolean flags based on this + * way-type, within one integer field by using bit-masking. + */ +public class WayTypeEncoder extends IntEncodedValueImpl { + + public final static String KEY = "waytype"; private static final BiMap wayTypeIntMap = HashBiMap.create(); private static final Set highwayTypes = Sets.newHashSet( @@ -40,15 +51,20 @@ public class WayTypeEncoder { "cycleway" ); - private static final int HIGHWAY = 1 << 31; - private static final int RESIDENTIAL = 1 << 30; - private static final int TUNNEL = 1 << 29; - private static final int TOLL = 1 << 28; - private static final int BAD_ROAD = 1 << 27; - private static final int ONE_LANE = 1 << 26; - private static final int MAIN_ROAD = 1 << 25; - private static final int CYCLEWAY = 1 << 24; - private static final int TYPE_MASK = 0x03FFFFFF; + private static final int HIGHWAY = 1 << 15; + private static final int RESIDENTIAL = 1 << 14; + private static final int TUNNEL = 1 << 13; + private static final int TOLL = 1 << 12; + private static final int BAD_ROAD = 1 << 11; + private static final int ONE_LANE = 1 << 10; + private static final int MAIN_ROAD = 1 << 9; + private static final int CYCLEWAY = 1 << 8; + + /* + * Store the type as integer in the first 1 byte (max value = 255) + * The rest of the integer bits is used to store the property flags, e.g. HIGHWAY, RESIDENTIAL. + */ + private static final int TYPE_MASK = 0x00FF; static { // autobahn @@ -73,6 +89,16 @@ public class WayTypeEncoder { wayTypeIntMap.put("service", 27); wayTypeIntMap.put("road", 26); wayTypeIntMap.put("track", 25); + // any other roads + wayTypeIntMap.put("cycleway", 10); + } + + private WayTypeEncoder() { + super(KEY, 31, false); + } + + public static WayTypeEncoder create() { + return new WayTypeEncoder(); } public static String decode(int type) { @@ -83,26 +109,27 @@ public static String decode(int type) { return "unknown"; } - public static int encode(String wayType, int numberLanes, int additionalFlags) { + public static int encode(String wayType, int numberLanes) { + int flags = 0; if (highwayTypes.contains(wayType)) { - additionalFlags |= HIGHWAY; + flags |= HIGHWAY; } if (residentialTypes.contains(wayType)) { - additionalFlags |= RESIDENTIAL; + flags |= RESIDENTIAL; } if (numberLanes == 1 && !oneLaneIgnoreTypes.contains(wayType)) { - additionalFlags |= ONE_LANE; + flags |= ONE_LANE; } if (mainroadTypes.contains(wayType)) { - additionalFlags |= MAIN_ROAD; + flags |= MAIN_ROAD; } if (cyclewayTypes.contains(wayType)) { - additionalFlags |= CYCLEWAY; + flags |= CYCLEWAY; } Integer result = wayTypeIntMap.get(wayType); if (result != null) { - return result | additionalFlags; + return result | flags; } return 0; } @@ -145,4 +172,7 @@ public static boolean isLowerType(int a, int b) { return typeB + 4 < typeA; } + public void setWay(Way way, int lanes, int edge, EdgeIntAccess access) { + setInt(false, edge, access, encode(way.getType(), lanes)); + } } diff --git a/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/CharlottenburgRoutingTest.java b/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/CharlottenburgRoutingTest.java new file mode 100644 index 000000000..f980db71a --- /dev/null +++ b/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/CharlottenburgRoutingTest.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2024 Fraunhofer FOKUS and others. All rights reserved. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contact: mosaic@fokus.fraunhofer.de + */ + +package org.eclipse.mosaic.lib.routing; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import org.eclipse.mosaic.lib.database.Database; +import org.eclipse.mosaic.lib.database.road.Connection; +import org.eclipse.mosaic.lib.enums.VehicleClass; +import org.eclipse.mosaic.lib.geo.GeoPoint; +import org.eclipse.mosaic.lib.junit.GeoProjectionRule; +import org.eclipse.mosaic.lib.routing.graphhopper.GraphHopperRouting; + +import com.google.common.collect.Lists; +import org.apache.commons.io.FileUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +/** + * Test routing with a real world map (Charlottenburg extract from BeST scenario). + */ +public class CharlottenburgRoutingTest { + + @Rule + public GeoProjectionRule transformationRule = new GeoProjectionRule(GeoPoint.latLon(52, 13)); + + private final static String dbFile = "/charlottenburg.db"; + + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + private Database database; + private GraphHopperRouting routing; + + @Before + public void setUp() throws IOException { + final File dbFileCopy = folder.newFile("charlottenburg.db"); + + FileUtils.copyInputStreamToFile(getClass().getResourceAsStream(dbFile), dbFileCopy); + + database = Database.loadFromFile(dbFileCopy); + + routing = new GraphHopperRouting(database); + } + + @Test + public void findPaths_27537749_to_252864802() { + List result = routing + .findRoutes(new RoutingRequest( + new RoutingPosition(GeoPoint.latLon(52.504185, 13.323964), null, "-440300111"), + new RoutingPosition(database.getNode("26761203").getPosition()), + new RoutingParameters() + .vehicleClass(VehicleClass.Car) + .alternativeRoutes(1) + .costFunction(RoutingCostFunction.Fastest) + )); + assertNotNull(result); + assertEquals(2, result.size()); + assertValidRoute(result.get(0)); + assertValidRoute(result.get(1)); + assertEquals(Lists.newArrayList("-440300111", "4381160#2", "832017303", "823947542#0", "110008909#2", "547450789#0", "4402682#8", "4490390#2", "-25418285#1", "4500153"), + result.get(0).getConnectionIds()); + assertEquals(Lists.newArrayList("-440300111", "4381160#2", "832017303", "823947542#0", "110008909#2", "547450789#0", "490351849#0", "490351848#0", "4492013#2", "25418287#6", "318889281#0", "4371002#2", "-4437136#0"), + result.get(1).getConnectionIds()); + } + + private void assertValidRoute(CandidateRoute candidateRoute) { + Connection currentConnection; + Connection previousConnection = null; + for (String connectionId : candidateRoute.getConnectionIds()) { + currentConnection = database.getConnection(connectionId); + if (previousConnection != null && !previousConnection.getOutgoingConnections().contains(currentConnection)) { + throw new AssertionError("Route is not valid, no transition existing"); + } + previousConnection = currentConnection; + } + } + +} diff --git a/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/database/DatabaseRoutingTest.java b/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/database/DatabaseRoutingTest.java index da7cdfefd..3bf4bd85b 100644 --- a/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/database/DatabaseRoutingTest.java +++ b/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/database/DatabaseRoutingTest.java @@ -229,7 +229,7 @@ public void findRoutes() throws InternalFederateException { //ASSERT assertEquals(0, response.getAlternativeRoutes().size()); assertNotNull(response.getBestRoute()); - assertEquals(15.672, response.getBestRoute().getTime(), 0.1d); + assertEquals(15.237, response.getBestRoute().getTime(), 0.1d); assertEquals(304.74, response.getBestRoute().getLength(), 0.1d); assertEquals(Arrays.asList("32909782_26704482_26785753", "25185001_26785753_26704584"), response.getBestRoute().getConnectionIds()); diff --git a/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/GraphHopperMosaicTest.java b/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/GraphHopperMosaicTest.java deleted file mode 100644 index b2c00c5af..000000000 --- a/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/GraphHopperMosaicTest.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (c) 2020 Fraunhofer FOKUS and others. All rights reserved. - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contact: mosaic@fokus.fraunhofer.de - */ - -package org.eclipse.mosaic.lib.routing.graphhopper; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import org.eclipse.mosaic.lib.routing.graphhopper.algorithm.BellmanFordRouting; -import org.eclipse.mosaic.lib.routing.graphhopper.algorithm.DijkstraCamvitChoiceRouting; -import org.eclipse.mosaic.lib.routing.graphhopper.algorithm.RoutingAlgorithmFactory; -import org.eclipse.mosaic.lib.routing.graphhopper.extended.ExtendedGHRequest; -import org.eclipse.mosaic.lib.routing.graphhopper.extended.ExtendedGHRequest.WeightingFactory; -import org.eclipse.mosaic.lib.routing.graphhopper.extended.ExtendedGHResponse; -import org.eclipse.mosaic.lib.routing.graphhopper.extended.ExtendedGraphHopper; -import org.eclipse.mosaic.lib.routing.graphhopper.junit.TestGraphRule; -import org.eclipse.mosaic.lib.routing.graphhopper.util.GHListHelper; -import org.eclipse.mosaic.lib.routing.graphhopper.util.GraphhopperToDatabaseMapper; - -import com.graphhopper.GHRequest; -import com.graphhopper.GHResponse; -import com.graphhopper.routing.Path; -import com.graphhopper.routing.RoutingAlgorithm; -import com.graphhopper.routing.util.TurnCostEncoder; -import com.graphhopper.routing.weighting.FastestWeighting; -import com.graphhopper.routing.weighting.Weighting; -import com.graphhopper.storage.Graph; -import com.graphhopper.storage.GraphExtension; -import com.graphhopper.storage.GraphHopperStorage; -import com.graphhopper.storage.TurnCostExtension; -import com.graphhopper.util.shapes.GHPoint; -import org.junit.Test; - -import java.util.List; - -public class GraphHopperMosaicTest { - - @Test - public void importFromTestGraph() { - ExtendedGraphHopper gh = new ExtendedGraphHopper(new TestGraphRule(null), new GraphhopperToDatabaseMapper()); - gh.importOrLoad(); - - assertFalse("Contraction Hierarchies should be disabled", gh.isCHEnabled()); - assertTrue("Graph is not from instance GraphHopperStorage", gh.getGraphHopperStorage() instanceof GraphHopperStorage); - - assertTrue("The default CAR encoder should implement TurnCostEncoder", - gh.getEncodingManager().getEncoder("CAR") instanceof TurnCostEncoder); - - GraphExtension extStorage = gh.getGraphHopperStorage().getExtension(); - assertTrue("Extended storage of graph is not from instance TurnCostStorage", extStorage instanceof TurnCostExtension); - assertTrue("Extended storage must offer an additional edge field", extStorage.isRequireEdgeField()); - - assertEquals("Not all nodes have been imported.", 14, gh.getGraphHopperStorage().getNodes()); - - } - - @Test(expected = IllegalStateException.class) - public void extendedGHRequestRequiredForRouting() { - ExtendedGraphHopper gh = new ExtendedGraphHopper(null, null); - gh.route(new GHRequest((GHPoint) null, (GHPoint) null)); - } - - @Test - public void routeRequest_alternativeRoutesWithDijkstra() { - //setup - final ExtendedGraphHopper gh = new ExtendedGraphHopper(new TestGraphRule(null), new GraphhopperToDatabaseMapper()); - gh.importOrLoad(); - - ExtendedGHRequest request = new ExtendedGHRequest(new GHPoint(0, 0), new GHPoint(0.03, 0.03)); - request.setWeightingFactory(new WeightingFactory() { - - @Override - public Weighting create(Graph graph) { - return new FastestWeighting(gh.getEncodingManager().getEncoder("CAR")); - } - }); - request.setRoutingAlgorithmFactory(new RoutingAlgorithmFactory() { - - @Override - public RoutingAlgorithm createAlgorithm(Graph graph, Weighting weighting) { - return new DijkstraCamvitChoiceRouting(graph, weighting); - } - }); - request.setVehicle("CAR"); - request.setAlternatives(2); - - //run - GHResponse response = gh.route(request); - - //assert - assertTrue("response should be from instance ExtendedGHResponse", response instanceof ExtendedGHResponse); - assertTrue(response.getErrors().isEmpty()); - - ExtendedGHResponse extResponse = (ExtendedGHResponse) response; - Path p = extResponse.getPath(); - assertNotNull(p); - assertEquals(GHListHelper.createTList(0, 1, 5, 10), p.calcNodes()); - assertEquals(5000, p.getDistance(), 0.1); - assertEquals(300000, p.getTime()); - - List alternatives = extResponse.getAdditionalRoutes(); - assertNotNull(alternatives); - assertEquals(2, alternatives.size()); - - p = alternatives.get(0).getPath(); - assertNotNull(p); - assertEquals(GHListHelper.createTList(0, 4, 7, 9, 10), p.calcNodes()); - assertEquals(5500, p.getDistance(), 0.1); - assertEquals(330000, p.getTime()); - - p = alternatives.get(1).getPath(); - assertNotNull(p); - assertEquals(GHListHelper.createTList(0, 1, 2, 6, 10), p.calcNodes()); - assertEquals(5600, p.getDistance(), 0.1); - assertEquals(336000, p.getTime()); - - } - - @Test - public void routeRequest_fastestWithBellmanFord() { - //setup - final ExtendedGraphHopper gh = new ExtendedGraphHopper(new TestGraphRule(null), new GraphhopperToDatabaseMapper()); - gh.importOrLoad(); - - ExtendedGHRequest request = new ExtendedGHRequest(new GHPoint(0, 0), new GHPoint(0.03, 0.03)); - request.setWeightingFactory((graph) -> new FastestWeighting(gh.getEncodingManager().getEncoder("CAR"))); - request.setRoutingAlgorithmFactory(BellmanFordRouting::new); - request.setVehicle("CAR"); - - //run - GHResponse response = gh.route(request); - - //assert - assertTrue("response should be from instance ExtendedGHResponse", response instanceof ExtendedGHResponse); - assertTrue(response.getErrors().isEmpty()); - - ExtendedGHResponse extResponse = (ExtendedGHResponse) response; - Path p = extResponse.getPath(); - assertNotNull(p); - assertEquals(GHListHelper.createTList(0, 1, 5, 10), p.calcNodes()); - assertEquals(5000, p.getDistance(), 0.1); - assertEquals(300000, p.getTime()); - } - -} diff --git a/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/GraphHopperRoutingTest.java b/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/GraphHopperRoutingTest.java index 5b4ecb067..b661ca137 100644 --- a/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/GraphHopperRoutingTest.java +++ b/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/GraphHopperRoutingTest.java @@ -60,8 +60,7 @@ public void setUp() throws IOException { database = Database.loadFromFile(dbFileCopy); - routing = new GraphHopperRouting(); - routing.loadGraphFromDatabase(database); + routing = new GraphHopperRouting(database); } @Test diff --git a/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/GraphHopperWeightingTest.java b/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/GraphHopperWeightingTest.java new file mode 100644 index 000000000..374203e5f --- /dev/null +++ b/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/GraphHopperWeightingTest.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2020 Fraunhofer FOKUS and others. All rights reserved. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contact: mosaic@fokus.fraunhofer.de + */ + +package org.eclipse.mosaic.lib.routing.graphhopper; + +import static org.junit.Assert.assertEquals; + +import org.eclipse.mosaic.lib.routing.RoutingCostFunction; +import org.eclipse.mosaic.lib.routing.graphhopper.junit.TestGraphRule; +import org.eclipse.mosaic.lib.routing.graphhopper.util.OptionalTurnCostProvider; +import org.eclipse.mosaic.lib.routing.graphhopper.util.VehicleEncoding; + +import com.graphhopper.routing.weighting.Weighting; +import com.graphhopper.util.EdgeExplorer; +import com.graphhopper.util.EdgeIterator; +import org.junit.Rule; +import org.junit.Test; + +public class GraphHopperWeightingTest { + + @Rule + public TestGraphRule testGraph = new TestGraphRule(); + + @Test + public void fastest_noTurnCosts() { + VehicleEncoding enc = testGraph.getEncodingManager().getVehicleEncoding("car"); + + Weighting w = new GraphHopperWeighting(enc, testGraph.getEncodingManager().wayType(), new OptionalTurnCostProvider(enc, testGraph.getGraph().getTurnCostStorage()), null) + .setRoutingCostFunction(RoutingCostFunction.Fastest); + + EdgeExplorer expl = testGraph.getGraph().createEdgeExplorer(); + EdgeIterator it = expl.setBaseNode(0); + + double distance = it.getDistance(); + + double weight = w.calcEdgeWeight(it, false); + double turnWeight = w.calcTurnWeight(1, 0, 0); + + assertEquals(distance / enc.speed().getMaxStorableDecimal() * 3.6, weight, 0.1d); + assertEquals(0, turnWeight, 0.1d); + } + + @Test + public void shortest_noTurnCosts() { + VehicleEncoding enc = testGraph.getEncodingManager().getVehicleEncoding("car"); + + Weighting w = new GraphHopperWeighting(enc, testGraph.getEncodingManager().wayType(), new OptionalTurnCostProvider(enc, testGraph.getGraph().getTurnCostStorage()), null) + .setRoutingCostFunction(RoutingCostFunction.Shortest); + + EdgeExplorer expl = testGraph.getGraph().createEdgeExplorer(); + EdgeIterator it = expl.setBaseNode(0); + + double distance = it.getDistance(); + + double weight = w.calcEdgeWeight(it, false); + double turnWeight = w.calcTurnWeight(1, 0, 0); + + assertEquals(distance, weight, 0.1d); + assertEquals(0, turnWeight, 0.1d); + } + + + @Test + public void shortest_turnCosts() { + VehicleEncoding enc = testGraph.getEncodingManager().getVehicleEncoding("car"); + + testGraph.getGraph().getTurnCostStorage().set(enc.turnCost(), 1, 0, 0, 10.0); + + Weighting w = new GraphHopperWeighting(enc, testGraph.getEncodingManager().wayType(), new OptionalTurnCostProvider(enc, testGraph.getGraph().getTurnCostStorage()), null) + .setRoutingCostFunction(RoutingCostFunction.Shortest); + + EdgeExplorer expl = testGraph.getGraph().createEdgeExplorer(); + EdgeIterator it = expl.setBaseNode(0); + + double distance = it.getDistance(); + + double weight = w.calcEdgeWeight(it, false); + double turnWeight = w.calcTurnWeight(1, 0, 0); + + assertEquals(distance, weight, 0.1d); + assertEquals(10, turnWeight, 0.1d); + } + + @Test + public void fastest_turnCosts() { + VehicleEncoding enc = testGraph.getEncodingManager().getVehicleEncoding("car"); + + testGraph.getGraph().getTurnCostStorage().set(enc.turnCost(), 1, 0, 0, 10.0); + + Weighting w = new GraphHopperWeighting(enc, testGraph.getEncodingManager().wayType(), new OptionalTurnCostProvider(enc, testGraph.getGraph().getTurnCostStorage()), null) + .setRoutingCostFunction(RoutingCostFunction.Fastest); + + EdgeExplorer expl = testGraph.getGraph().createEdgeExplorer(); + EdgeIterator it = expl.setBaseNode(0); + + double distance = it.getDistance(); + + double weight = w.calcEdgeWeight(it, false); + double turnWeight = w.calcTurnWeight(1, 0, 0); + + assertEquals(distance / enc.speed().getMaxOrMaxStorableDecimal() * 3.6, weight, 0.1d); + assertEquals(10, turnWeight, 0.1d); + } + + @Test + public void shortest_turnRestriction() { + VehicleEncoding enc = testGraph.getEncodingManager().getVehicleEncoding("car"); + + testGraph.getGraph().getTurnCostStorage().set(enc.turnRestriction(), 1, 0, 0, true); + + Weighting w = new GraphHopperWeighting(enc, testGraph.getEncodingManager().wayType(), new OptionalTurnCostProvider(enc, testGraph.getGraph().getTurnCostStorage()), null) + .setRoutingCostFunction(RoutingCostFunction.Shortest); + + EdgeExplorer expl = testGraph.getGraph().createEdgeExplorer(); + EdgeIterator it = expl.setBaseNode(0); + + double distance = it.getDistance(); + + double weight = w.calcEdgeWeight(it, false); + double turnWeight = w.calcTurnWeight(1, 0, 0); + + assertEquals(distance, weight, 0.1d); + assertEquals(Double.POSITIVE_INFINITY, turnWeight, 0.1d); + } + + @Test + public void fastest_turnRestriction() { + VehicleEncoding enc = testGraph.getEncodingManager().getVehicleEncoding("car"); + + testGraph.getGraph().getTurnCostStorage().set(enc.turnRestriction(), 1, 0, 0, true); + + Weighting w = new GraphHopperWeighting(enc, testGraph.getEncodingManager().wayType(), new OptionalTurnCostProvider(enc, testGraph.getGraph().getTurnCostStorage()), null) + .setRoutingCostFunction(RoutingCostFunction.Fastest); + + EdgeExplorer expl = testGraph.getGraph().createEdgeExplorer(); + EdgeIterator it = expl.setBaseNode(0); + + double distance = it.getDistance(); + + double weight = w.calcEdgeWeight(it, false); + double turnWeight = w.calcTurnWeight(1, 0, 0); + + assertEquals(distance / enc.speed().getMaxOrMaxStorableDecimal() * 3.6, weight, 0.1d); + assertEquals(Double.POSITIVE_INFINITY, turnWeight, 0.1d); + } + +} diff --git a/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/AlternativeRoutesRoutingTest.java b/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/AlternativeRoutesRoutingTest.java new file mode 100644 index 000000000..b70f00613 --- /dev/null +++ b/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/AlternativeRoutesRoutingTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2020 Fraunhofer FOKUS and others. All rights reserved. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contact: mosaic@fokus.fraunhofer.de + */ + +package org.eclipse.mosaic.lib.routing.graphhopper.algorithm; + +import static org.junit.Assert.assertEquals; + +import org.eclipse.mosaic.lib.routing.graphhopper.junit.TestGraphRule; +import org.eclipse.mosaic.lib.routing.graphhopper.util.GHListHelper; +import org.eclipse.mosaic.lib.routing.graphhopper.util.OptionalTurnCostProvider; +import org.eclipse.mosaic.lib.routing.graphhopper.util.VehicleEncoding; + +import com.graphhopper.routing.Path; +import com.graphhopper.routing.RoutingAlgorithm; +import com.graphhopper.routing.weighting.FastestWeighting; +import com.graphhopper.routing.weighting.Weighting; +import com.graphhopper.storage.BaseGraph; +import com.graphhopper.util.PMap; +import com.graphhopper.util.Parameters; +import org.junit.Rule; +import org.junit.Test; + +import java.util.List; + +public class AlternativeRoutesRoutingTest { + + @Rule + public TestGraphRule testGraph = new TestGraphRule(); + + /** + * Calculates two alternatives paths using the test graph + */ + @Test + public void calculateAlternativePaths_withTurnCost() { + BaseGraph g = testGraph.getGraph(); + VehicleEncoding enc = testGraph.getEncodingManager().getVehicleEncoding("car"); + Weighting w = new FastestWeighting(enc.access(), enc.speed(), new OptionalTurnCostProvider(enc, g.getTurnCostStorage())); + + //100 seconds turn costs for turn (0-1)->(1-2) + g.getTurnCostStorage().set(enc.turnCost(), 0, 1, 2, 100); + + RoutingAlgorithm algo = RoutingAlgorithmFactory.DEFAULT.createAlgorithm(g, w, + new PMap().putObject(Parameters.Algorithms.AltRoute.MAX_PATHS, 3) + ); + + // RUN + List paths = algo.calcPaths(0, 10); + + // ASSERT + assertEquals(3, paths.size()); + + Path p = paths.get(0); + assertEquals(GHListHelper.createTList(0, 1, 5, 10), p.calcNodes()); + assertEquals(5000, p.getDistance(), 0.1); + + p = paths.get(1); + assertEquals(GHListHelper.createTList(0, 4, 7, 9, 10), p.calcNodes()); + assertEquals(5500, p.getDistance(), 0.1); + + p = paths.get(2); + assertEquals(GHListHelper.createTList(0, 1, 5, 6, 10), p.calcNodes()); + assertEquals(6000, p.getDistance(), 0.1); + } + + /** + * Calculates two alternatives paths using the test graph + * considering turn costs + */ + @Test + public void calculateAlternativePaths() { + BaseGraph g = testGraph.getGraph(); + VehicleEncoding enc = testGraph.getEncodingManager().getVehicleEncoding("car"); + Weighting w = new FastestWeighting(enc.access(), enc.speed(), new OptionalTurnCostProvider(enc, g.getTurnCostStorage())); + + RoutingAlgorithm algo = RoutingAlgorithmFactory.DEFAULT.createAlgorithm(g, w, + new PMap().putObject(Parameters.Algorithms.AltRoute.MAX_PATHS, 3) + ); + + // RUN + List paths = algo.calcPaths(0, 10); + + // ASSERT + assertEquals(3, paths.size()); + //assert shortest + Path p = paths.get(0); + assertEquals(GHListHelper.createTList(0, 1, 5, 10), p.calcNodes()); + assertEquals(5000, p.getDistance(), 0.1); + + p = paths.get(1); + assertEquals(GHListHelper.createTList(0, 4, 7, 9, 10), p.calcNodes()); + assertEquals(5500, p.getDistance(), 0.1); + + p = paths.get(2); + assertEquals(GHListHelper.createTList(0, 1, 2, 6, 10), p.calcNodes()); + assertEquals(5600, p.getDistance(), 0.1); + } + + /** + * Calculates only the best path. + */ + @Test + public void calculateBestPath() { + BaseGraph g = testGraph.getGraph(); + VehicleEncoding enc = testGraph.getEncodingManager().getVehicleEncoding("car"); + Weighting w = new FastestWeighting(enc.access(), enc.speed(), new OptionalTurnCostProvider(enc, g.getTurnCostStorage())); + + RoutingAlgorithm algo = RoutingAlgorithmFactory.DEFAULT.createAlgorithm(g, w, new PMap()); + + Path p = algo.calcPath(0, 10); + + //assert shortest + assertEquals(GHListHelper.createTList(0, 1, 5, 10), p.calcNodes()); + assertEquals(5000, p.getDistance(), 0.1); + } + +} diff --git a/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/BellmanFordRoutingTest.java b/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/BellmanFordRoutingTest.java index 0986d9160..899283e2e 100644 --- a/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/BellmanFordRoutingTest.java +++ b/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/BellmanFordRoutingTest.java @@ -19,42 +19,32 @@ import org.eclipse.mosaic.lib.routing.graphhopper.junit.TestGraphRule; import org.eclipse.mosaic.lib.routing.graphhopper.util.GHListHelper; +import org.eclipse.mosaic.lib.routing.graphhopper.util.OptionalTurnCostProvider; +import org.eclipse.mosaic.lib.routing.graphhopper.util.VehicleEncoding; import com.graphhopper.routing.Path; -import com.graphhopper.routing.util.BikeFlagEncoder; -import com.graphhopper.routing.util.CarFlagEncoder; -import com.graphhopper.routing.util.EncodingManager; -import com.graphhopper.routing.util.FlagEncoder; -import com.graphhopper.routing.util.FootFlagEncoder; import com.graphhopper.routing.weighting.FastestWeighting; import com.graphhopper.routing.weighting.ShortestWeighting; -import com.graphhopper.routing.weighting.TurnWeighting; import com.graphhopper.routing.weighting.Weighting; -import com.graphhopper.storage.GraphHopperStorage; -import com.graphhopper.storage.TurnCostExtension; +import com.graphhopper.storage.BaseGraph; +import com.graphhopper.util.PMap; import org.junit.Rule; import org.junit.Test; public class BellmanFordRoutingTest { - private EncodingManager encManager = EncodingManager.create( - new CarFlagEncoder(5, 5, 127), - new BikeFlagEncoder(), - new FootFlagEncoder() - ); @Rule - public TestGraphRule testGraph = new TestGraphRule(encManager); + public TestGraphRule testGraph = new TestGraphRule(); @Test public void calculateFastestPath() { - GraphHopperStorage g = testGraph.getGraph(); - FlagEncoder e = encManager.getEncoder("CAR"); - Weighting w = new FastestWeighting(e); - + BaseGraph g = testGraph.getGraph(); + VehicleEncoding enc = testGraph.getEncodingManager().getVehicleEncoding("car"); + Weighting w = new FastestWeighting(enc.access(), enc.speed()); //run - Path p = new BellmanFordRouting(g, w).calcPath(0, 10); + Path p = new BellmanFordRouting(g, w, new PMap()).calcPath(0, 10); //assert shortest assertEquals(GHListHelper.createTList(0, 1, 5, 10), p.calcNodes()); @@ -63,15 +53,15 @@ public void calculateFastestPath() { @Test public void calculateFastestPath_turnCosts() { - GraphHopperStorage g = testGraph.getGraph(); - FlagEncoder e = encManager.getEncoder("CAR"); - Weighting w = new TurnWeighting(new FastestWeighting(e), (TurnCostExtension) g.getExtension()); + BaseGraph g = testGraph.getGraph(); + VehicleEncoding enc = testGraph.getEncodingManager().getVehicleEncoding("car"); + Weighting w = new FastestWeighting(enc.access(), enc.speed(), new OptionalTurnCostProvider(enc, g.getTurnCostStorage())); //add expensive turn at (0-1)->(1,5) - testGraph.getTurnCostStorage().addTurnInfo(0, 1, 3, e.getTurnFlags(false, 124)); + g.getTurnCostStorage().set(enc.turnCost(), 0, 1, 3, 124); //run - Path p = new BellmanFordRouting(g, w).calcPath(0, 10); + Path p = new BellmanFordRouting(g, w, new PMap()).calcPath(0, 10); //assert shortest assertEquals(GHListHelper.createTList(0, 4, 7, 9, 10), p.calcNodes()); @@ -80,12 +70,12 @@ public void calculateFastestPath_turnCosts() { @Test public void calculateShortestPath() { - GraphHopperStorage g = testGraph.getGraph(); - FlagEncoder e = encManager.getEncoder("CAR"); - Weighting w = new ShortestWeighting(e); + BaseGraph g = testGraph.getGraph(); + VehicleEncoding enc = testGraph.getEncodingManager().getVehicleEncoding("car"); + Weighting w = new ShortestWeighting(enc.access(), enc.speed()); //run - Path p = new BellmanFordRouting(g, w).calcPath(0, 10); + Path p = new BellmanFordRouting(g, w, new PMap()).calcPath(0, 10); //assert shortest assertEquals(GHListHelper.createTList(0, 1, 5, 10), p.calcNodes()); diff --git a/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/CamvitChoiceRoutingTest.java b/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/CamvitChoiceRoutingTest.java deleted file mode 100644 index ac796353d..000000000 --- a/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/CamvitChoiceRoutingTest.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (c) 2020 Fraunhofer FOKUS and others. All rights reserved. - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contact: mosaic@fokus.fraunhofer.de - */ - -package org.eclipse.mosaic.lib.routing.graphhopper.algorithm; - -import static org.junit.Assert.assertEquals; - -import org.eclipse.mosaic.lib.routing.graphhopper.junit.TestGraphRule; -import org.eclipse.mosaic.lib.routing.graphhopper.util.GHListHelper; - -import com.graphhopper.routing.Path; -import com.graphhopper.routing.util.BikeFlagEncoder; -import com.graphhopper.routing.util.CarFlagEncoder; -import com.graphhopper.routing.util.EncodingManager; -import com.graphhopper.routing.util.FlagEncoder; -import com.graphhopper.routing.util.FootFlagEncoder; -import com.graphhopper.routing.util.TurnCostEncoder; -import com.graphhopper.routing.weighting.FastestWeighting; -import com.graphhopper.routing.weighting.TurnWeighting; -import com.graphhopper.routing.weighting.Weighting; -import com.graphhopper.storage.Graph; -import com.graphhopper.storage.GraphHopperStorage; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - -import java.util.Arrays; -import java.util.Collection; -import java.util.List; - -@RunWith(Parameterized.class) -public class CamvitChoiceRoutingTest { - /** - * Run tests with both implementations: Djikstra and A* - */ - @Parameters - public static Collection configs() { - return Arrays.asList(new Object[][]{ - {(TestAlgorithmPreparation) DijkstraCamvitChoiceRouting::new}, - {(TestAlgorithmPreparation) AStarCamvitChoiceRouting::new}} - ); - } - - private TestAlgorithmPreparation algoPreparation; - - private EncodingManager encManager = EncodingManager.create( - new CarFlagEncoder(5, 5, 127), - new BikeFlagEncoder(), - new FootFlagEncoder() - ); - - @Rule - public TestGraphRule testGraph = new TestGraphRule(encManager); - - public CamvitChoiceRoutingTest(TestAlgorithmPreparation algoPreparation) { - this.algoPreparation = algoPreparation; - } - - /** - * Calculates two alternatives paths using the test graph - */ - @Test - public void calculateAlternativePaths_withTurnCost() { - GraphHopperStorage g = testGraph.getGraph(); - FlagEncoder e = encManager.getEncoder("CAR"); - TurnWeighting w = new TurnWeighting(new FastestWeighting(e), testGraph.getTurnCostStorage()); - - //2 seconds turn costs for turn (0-1)->(1-2) - testGraph.getTurnCostStorage().addTurnInfo(0, 1, 2, ((TurnCostEncoder) e).getTurnFlags(false, 124)); - - //run - AlternativeRoutesRoutingAlgorithm algo = algoPreparation.createAlgo(g, w); - - algo.setRequestAlternatives(2); - - Path p = algo.calcPath(0, 10); - - //assert shortest - assertEquals(GHListHelper.createTList(0, 1, 5, 10), p.calcNodes()); - assertEquals(5000, p.getDistance(), 0.1); - - List paths = algo.getAlternativePaths(); - assertEquals(2, paths.size()); - - p = paths.get(0); - assertEquals(GHListHelper.createTList(0, 4, 7, 9, 10), p.calcNodes()); - assertEquals(5500, p.getDistance(), 0.1); - - p = paths.get(1); - assertEquals(GHListHelper.createTList(0, 1, 5, 6, 10), p.calcNodes()); - assertEquals(6000, p.getDistance(), 0.1); - } - - /** - * Calculates two alternatives paths using the test graph - * considering turn costs - */ - @Test - public void calculateAlternativePaths() { - Graph g = testGraph.getGraph(); - FlagEncoder e = encManager.getEncoder("CAR"); - Weighting w = new FastestWeighting(e); - - AlternativeRoutesRoutingAlgorithm algo = algoPreparation.createAlgo(g, w); - - algo.setRequestAlternatives(2); - - Path p = algo.calcPath(0, 10); - - //assert shortest - assertEquals(GHListHelper.createTList(0, 1, 5, 10), p.calcNodes()); - assertEquals(5000, p.getDistance(), 0.1); - - List paths = algo.getAlternativePaths(); - assertEquals(2, paths.size()); - - p = paths.get(0); - assertEquals(GHListHelper.createTList(0, 4, 7, 9, 10), p.calcNodes()); - assertEquals(5500, p.getDistance(), 0.1); - - p = paths.get(1); - assertEquals(GHListHelper.createTList(0, 1, 2, 6, 10), p.calcNodes()); - assertEquals(5600, p.getDistance(), 0.1); - } - - /** - * Calculates only the best path using ChoiceRouting - */ - @Test - public void calculateBestPath() { - Graph g = testGraph.getGraph(); - FlagEncoder e = encManager.getEncoder("CAR"); - Weighting w = new FastestWeighting(e); - - AlternativeRoutesRoutingAlgorithm algo = algoPreparation.createAlgo(g, w); - - algo.setRequestAlternatives(0); - - Path p = algo.calcPath(0, 10); - - //assert shortest - assertEquals(GHListHelper.createTList(0, 1, 5, 10), p.calcNodes()); - assertEquals(5000, p.getDistance(), 0.1); - - List paths = algo.getAlternativePaths(); - assertEquals(0, paths.size()); - } - - interface TestAlgorithmPreparation { - AlternativeRoutesRoutingAlgorithm createAlgo(Graph g, Weighting w); - } -} diff --git a/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/TurnWeightingImplTest.java b/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/TurnWeightingImplTest.java deleted file mode 100644 index b7bd02544..000000000 --- a/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/algorithm/TurnWeightingImplTest.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (c) 2020 Fraunhofer FOKUS and others. All rights reserved. - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contact: mosaic@fokus.fraunhofer.de - */ - -package org.eclipse.mosaic.lib.routing.graphhopper.algorithm; - -import static org.junit.Assert.assertEquals; - -import org.eclipse.mosaic.lib.routing.graphhopper.junit.TestGraphRule; - -import com.graphhopper.routing.util.CarFlagEncoder; -import com.graphhopper.routing.util.EncodingManager; -import com.graphhopper.routing.util.FlagEncoder; -import com.graphhopper.routing.util.TurnCostEncoder; -import com.graphhopper.routing.weighting.FastestWeighting; -import com.graphhopper.routing.weighting.ShortestWeighting; -import com.graphhopper.routing.weighting.TurnWeighting; -import com.graphhopper.util.EdgeExplorer; -import com.graphhopper.util.EdgeIterator; -import org.junit.Rule; -import org.junit.Test; - -public class TurnWeightingImplTest { - - private EncodingManager encManager = EncodingManager.create(new CarFlagEncoder(5, 5, 127)); - - @Rule - public TestGraphRule testGraph = new TestGraphRule(encManager); - - @Test - public void fastest_noTurnCosts() { - FlagEncoder enc = encManager.getEncoder("CAR"); - - TurnWeighting w = new TurnWeighting(new FastestWeighting(enc), testGraph.getTurnCostStorage()); - - EdgeExplorer expl = testGraph.getGraph().createEdgeExplorer(); - EdgeIterator it = expl.setBaseNode(0); - - double distance = it.getDistance(); - - double weight = w.calcWeight(it, false, 0); - double turnWeight = w.calcTurnWeight(1, 0, 0); - - assertEquals(distance / enc.getMaxSpeed(), weight, 0.1d); - assertEquals(0, turnWeight, 0.1d); - } - - @Test - public void shortest_noTurnCosts() { - FlagEncoder enc = encManager.getEncoder("CAR"); - - TurnWeighting w = new TurnWeighting(new ShortestWeighting(enc), testGraph.getTurnCostStorage()); - - EdgeExplorer expl = testGraph.getGraph().createEdgeExplorer(); - EdgeIterator it = expl.setBaseNode(0); - - double distance = it.getDistance(); - - double weight = w.calcWeight(it, false, 0); - double turnWeight = w.calcTurnWeight(1, 0, 0); - - assertEquals(distance, weight, 0.1d); - assertEquals(0, turnWeight, 0.1d); - } - - - @Test - public void shortest_turnCosts() { - FlagEncoder enc = encManager.getEncoder("CAR"); - - testGraph.getTurnCostStorage().addTurnInfo(1, 0, 0, ((TurnCostEncoder) enc).getTurnFlags(false, 10)); - - TurnWeighting w = new TurnWeighting(new ShortestWeighting(enc), testGraph.getTurnCostStorage()); - - EdgeExplorer expl = testGraph.getGraph().createEdgeExplorer(); - EdgeIterator it = expl.setBaseNode(0); - - double distance = it.getDistance(); - - double weight = w.calcWeight(it, false, 0); - double turnWeight = w.calcTurnWeight(1, 0, 0); - - assertEquals(distance, weight, 0.1d); - assertEquals(10, turnWeight, 0.1d); - } - - @Test - public void fastest_turnCosts() { - FlagEncoder enc = encManager.getEncoder("CAR"); - - testGraph.getTurnCostStorage().addTurnInfo(1, 0, 0, ((TurnCostEncoder) enc).getTurnFlags(false, 10)); - - TurnWeighting w = new TurnWeighting(new FastestWeighting(enc), testGraph.getTurnCostStorage()); - - EdgeExplorer expl = testGraph.getGraph().createEdgeExplorer(); - EdgeIterator it = expl.setBaseNode(0); - - double distance = it.getDistance(); - - double weight = w.calcWeight(it, false, 0); - double turnWeight = w.calcTurnWeight(1, 0, 0); - - assertEquals(distance / enc.getMaxSpeed(), weight, 0.1d); - assertEquals(10, turnWeight, 0.1d); - } - - @Test - public void shortest_turnRestriction() { - FlagEncoder enc = encManager.getEncoder("CAR"); - - testGraph.getTurnCostStorage().addTurnInfo(1, 0, 0, ((TurnCostEncoder) enc).getTurnFlags(true, 0)); - - TurnWeighting w = new TurnWeighting(new ShortestWeighting(enc), testGraph.getTurnCostStorage()); - - EdgeExplorer expl = testGraph.getGraph().createEdgeExplorer(); - EdgeIterator it = expl.setBaseNode(0); - - double distance = it.getDistance(); - - double weight = w.calcWeight(it, false, 0); - double turnWeight = w.calcTurnWeight(1, 0, 0); - - assertEquals(distance, weight, 0.1d); - assertEquals(Double.POSITIVE_INFINITY, turnWeight, 0.1d); - } - - @Test - public void fastest_turnRestriction() { - FlagEncoder enc = encManager.getEncoder("CAR"); - - testGraph.getTurnCostStorage().addTurnInfo(1, 0, 0, ((TurnCostEncoder) enc).getTurnFlags(true, 0)); - - TurnWeighting w = new TurnWeighting(new FastestWeighting(enc), testGraph.getTurnCostStorage()); - - EdgeExplorer expl = testGraph.getGraph().createEdgeExplorer(); - EdgeIterator it = expl.setBaseNode(0); - - double distance = it.getDistance(); - - double weight = w.calcWeight(it, false, 0); - double turnWeight = w.calcTurnWeight(1, 0, 0); - - assertEquals(distance / enc.getMaxSpeed(), weight, 0.1d); - assertEquals(Double.POSITIVE_INFINITY, turnWeight, 0.1d); - } - -} diff --git a/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/junit/TestGraphRule.java b/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/junit/TestGraphRule.java index 7f5a1e4b7..30c64e469 100644 --- a/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/junit/TestGraphRule.java +++ b/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/junit/TestGraphRule.java @@ -15,48 +15,40 @@ package org.eclipse.mosaic.lib.routing.graphhopper.junit; -import org.eclipse.mosaic.lib.routing.graphhopper.GraphLoader; -import org.eclipse.mosaic.lib.routing.graphhopper.util.GraphhopperToDatabaseMapper; +import org.eclipse.mosaic.lib.routing.graphhopper.GraphHopperRouting; +import org.eclipse.mosaic.lib.routing.graphhopper.util.VehicleEncoding; +import org.eclipse.mosaic.lib.routing.graphhopper.util.VehicleEncodingManager; -import com.graphhopper.routing.util.EncodingManager; -import com.graphhopper.storage.GraphHopperStorage; -import com.graphhopper.storage.RAMDirectory; -import com.graphhopper.storage.TurnCostExtension; +import com.graphhopper.routing.util.AllEdgesIterator; +import com.graphhopper.storage.BaseGraph; import org.junit.rules.ExternalResource; -public class TestGraphRule extends ExternalResource implements GraphLoader { +public class TestGraphRule extends ExternalResource { - private GraphHopperStorage graph; - private EncodingManager encodingManager; + private BaseGraph graph; + private final VehicleEncodingManager encodingManager; - private TurnCostExtension turnCostStorage; - private boolean emptyGraph; + private final boolean emptyGraph; - public TestGraphRule(EncodingManager encodingManager) { - this(encodingManager, false); + public TestGraphRule() { + this(false); } - public TestGraphRule(EncodingManager encodingManager, boolean emptyGraph) { - this.encodingManager = encodingManager; + public TestGraphRule(boolean emptyGraph) { + this.encodingManager = new VehicleEncodingManager(GraphHopperRouting.PROFILES); this.emptyGraph = emptyGraph; } + public VehicleEncodingManager getEncodingManager() { + return encodingManager; + } + @Override protected void before() throws Throwable { - turnCostStorage = new TurnCostExtension() { - @Override - public boolean isRequireEdgeField() { - return true; - } - @Override - public int getDefaultEdgeFieldValue() { - return 0; - } - }; - - final RAMDirectory dir = new RAMDirectory(); - graph = new GraphHopperStorage(dir, encodingManager, true, turnCostStorage); + graph = new BaseGraph.Builder(encodingManager.getEncodingManager()) + .withTurnCosts(true) + .build(); if (!emptyGraph) { graph = graph.create(100); @@ -69,30 +61,12 @@ public int getDefaultEdgeFieldValue() { protected void after() { graph.close(); graph = null; - turnCostStorage = null; } - public GraphHopperStorage getGraph() { + public BaseGraph getGraph() { return graph; } - public TurnCostExtension getTurnCostStorage() { - return turnCostStorage; - } - - @Override - public void initialize(GraphHopperStorage graph, EncodingManager encodingManager, GraphhopperToDatabaseMapper mapper) { - this.graph = graph; - this.turnCostStorage = (TurnCostExtension) graph.getExtension(); - this.encodingManager = encodingManager; - } - - @Override - public void loadGraph() { - graph.create(25); - createTestGraph(); - } - private void createTestGraph() { graph.getNodeAccess().setNode(0, 0.00, 0.00, 0); //A graph.getNodeAccess().setNode(1, 0.01, 0.00, 0); //B @@ -109,28 +83,39 @@ private void createTestGraph() { graph.getNodeAccess().setNode(12, 0.01, 0.04, 0); //M graph.getNodeAccess().setNode(13, 0.02, 0.04, 0); //N - graph.edge(0, 1, 1000, true); //0 - graph.edge(0, 4, 1000, true); //1 - graph.edge(1, 2, 1000, true); //2 - graph.edge(1, 5, 1000, true); //3 - graph.edge(1, 7, 2500, true); //4 - graph.edge(2, 3, 1000, true); //5 - graph.edge(2, 6, 1600, true); //6 - graph.edge(3, 6, 1000, true); //7 - graph.edge(4, 7, 1000, true); //8 - graph.edge(5, 6, 2000, true); //9 - graph.edge(5, 7, 1600, true); //10 - graph.edge(5, 10, 3000, true); //11 - graph.edge(6, 10, 2000, true); //12 - graph.edge(7, 8, 1000, true); //13 - graph.edge(7, 9, 1500, true); //14 - graph.edge(8, 11, 1000, true); //15 - graph.edge(9, 10, 2000, true); //16 - graph.edge(9, 11, 1500, true); //17 - graph.edge(9, 13, 1500, true); //18 - graph.edge(10, 13, 1500, true); //19 - graph.edge(11, 12, 1000, true); //20 - graph.edge(12, 13, 1000, true); //21 + + graph.edge(0, 1).setDistance(1000); //0 + graph.edge(0, 4).setDistance(1000); //1 + graph.edge(1, 2).setDistance(1000); //2 + graph.edge(1, 5).setDistance(1000); //3 + graph.edge(1, 7).setDistance(2500); //4 + graph.edge(2, 3).setDistance(1000); //5 + graph.edge(2, 6).setDistance(1600); //6 + graph.edge(3, 6).setDistance(1000); //7 + graph.edge(4, 7).setDistance(1000); //8 + graph.edge(5, 6).setDistance(2000); //9 + graph.edge(5, 7).setDistance(1600); //10 + graph.edge(5, 10).setDistance(3000); //11 + graph.edge(6, 10).setDistance(2000); //12 + graph.edge(7, 8).setDistance(1000); //13 + graph.edge(7, 9).setDistance(1500); //14 + graph.edge(8, 11).setDistance(1000); //15 + graph.edge(9, 10).setDistance(2000); //16 + graph.edge(9, 11).setDistance(1500); //17 + graph.edge(9, 13).setDistance(1500); //18 + graph.edge(10, 13).setDistance(1500); //19 + graph.edge(11, 12).setDistance(1000); //20 + graph.edge(12, 13).setDistance(1000); //21 + + + VehicleEncoding enc = encodingManager.getVehicleEncoding("car"); + AllEdgesIterator it = graph.getAllEdges(); + while (it.next()) { + it.set(enc.access(), true); + it.set(enc.speed(), 60); + it.setReverse(enc.access(), true); + it.setReverse(enc.speed(), 60); + } } } diff --git a/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/util/DatabaseGraphLoaderTest.java b/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/util/DatabaseGraphLoaderTest.java index 1b6659237..a67db7f0a 100644 --- a/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/util/DatabaseGraphLoaderTest.java +++ b/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/util/DatabaseGraphLoaderTest.java @@ -21,19 +21,13 @@ import static org.junit.Assert.assertTrue; import org.eclipse.mosaic.lib.database.Database; -import org.eclipse.mosaic.lib.routing.graphhopper.DatabaseGraphLoader; -import org.eclipse.mosaic.lib.routing.graphhopper.GraphLoader; import org.eclipse.mosaic.lib.routing.graphhopper.junit.TestGraphRule; -import com.graphhopper.routing.util.CarFlagEncoder; -import com.graphhopper.routing.util.DefaultEdgeFilter; +import com.graphhopper.routing.ev.DecimalEncodedValue; +import com.graphhopper.routing.util.AccessFilter; import com.graphhopper.routing.util.EdgeFilter; -import com.graphhopper.routing.util.EncodingManager; -import com.graphhopper.routing.util.FlagEncoder; -import com.graphhopper.routing.util.TurnCostEncoder; -import com.graphhopper.storage.GraphHopperStorage; -import com.graphhopper.storage.GraphStorage; -import com.graphhopper.storage.TurnCostExtension; +import com.graphhopper.storage.BaseGraph; +import com.graphhopper.storage.TurnCostStorage; import com.graphhopper.util.EdgeExplorer; import com.graphhopper.util.EdgeIterator; import org.apache.commons.io.IOUtils; @@ -50,13 +44,12 @@ /** * Tests, if the {@link DatabaseGraphLoader} correctly transfers the scenario database - * into {@link GraphStorage}. + * into {@link BaseGraph}. */ public class DatabaseGraphLoaderTest { - private EncodingManager encodingManager = EncodingManager.create(new CarFlagEncoder(5, 5, 127)); @Rule - public TestGraphRule testGraph = new TestGraphRule(encodingManager, true); + public TestGraphRule testGraph = new TestGraphRule(true); @Rule public TemporaryFolder folder = new TemporaryFolder(); @@ -72,10 +65,10 @@ public void correctImport() throws Exception { Database database = Database.loadFromFile(testDb); - GraphLoader reader = new DatabaseGraphLoader(database); + DatabaseGraphLoader reader = new DatabaseGraphLoader(database); GraphhopperToDatabaseMapper mapper = new GraphhopperToDatabaseMapper(); - GraphHopperStorage g = testGraph.getGraph(); - TurnCostExtension tcStorage = testGraph.getTurnCostStorage(); + BaseGraph g = testGraph.getGraph(); + TurnCostStorage tcStorage = testGraph.getGraph().getTurnCostStorage(); // pre-assert assertEquals("#nodes", 4, database.getNodes().size()); @@ -85,7 +78,7 @@ public void correctImport() throws Exception { assertEquals("#routes", 1, database.getRoutes().size()); //run - reader.initialize(g, encodingManager, mapper); + reader.initialize(g, testGraph.getEncodingManager(), mapper); reader.loadGraph(); //assert @@ -100,8 +93,10 @@ public void correctImport() throws Exception { int con3_2_3 = mapper.fromConnection(database.getConnection("3_2_3")); int con3_3_2 = mapper.fromConnection(database.getConnection("3_3_2")); - FlagEncoder enc = encodingManager.getEncoder("CAR"); - EdgeFilter outFilter = DefaultEdgeFilter.outEdges(enc); + VehicleEncoding vehicleEncoding = testGraph.getEncodingManager().getVehicleEncoding("car"); + WayTypeEncoder wayTypeEnc = testGraph.getEncodingManager().wayType(); + + EdgeFilter outFilter = AccessFilter.outEdges(vehicleEncoding.access()); assertEquals("#nodes of graph", 4, g.getNodes()); @@ -112,8 +107,8 @@ public void correctImport() throws Exception { assertEquals(con1_1_2, it.getEdge()); assertEquals(n2, it.getAdjNode()); assertEquals(100, it.getDistance(), 0d); - assertEquals(140, it.get(enc.getAverageSpeedEnc()), 10d); //graphhopper has default max speed of 140 km/h - assertFalse(WayTypeEncoder.isResidential(it.getAdditionalField())); + assertEquals(140, it.get(vehicleEncoding.speed()), 10d); //graphhopper has default max speed of 140 km/h + assertFalse(WayTypeEncoder.isResidential(it.get(wayTypeEnc))); assertFalse(it.next()); final Map assertionMap = new HashMap<>(); @@ -122,66 +117,64 @@ public void correctImport() throws Exception { it = expl.setBaseNode(n2); assertionMap.put(n1, edgeAssertion(n1, con1_2_1, 80, 140, false)); assertionMap.put(n3, edgeAssertion(n3, con3_2_3, 50, 140, false)); - assertNextEdge(enc, it, assertionMap); - assertNextEdge(enc, it, assertionMap); + assertNextEdge(vehicleEncoding.speed(), wayTypeEnc, it, assertionMap); + assertNextEdge(vehicleEncoding.speed(), wayTypeEnc, it, assertionMap); assertTrue(assertionMap.isEmpty()); assertFalse(it.next()); //assert outgoing edges of node 3 it = expl.setBaseNode(n3); assertionMap.put(n2, edgeAssertion(n2, con3_3_2, 50, 140, false)); - assertNextEdge(enc, it, assertionMap); + assertNextEdge(vehicleEncoding.speed(), wayTypeEnc, it, assertionMap); assertTrue(assertionMap.isEmpty()); assertFalse(it.next()); //assert outgoing edges of node 4 it = expl.setBaseNode(n4); assertionMap.put(n2, edgeAssertion(n2, con2_4_2, 50, 30 * 3.6 * 0.9, true)); - assertNextEdge(enc, it, assertionMap); + assertNextEdge(vehicleEncoding.speed(), wayTypeEnc, it, assertionMap); assertTrue(assertionMap.isEmpty()); assertFalse(it.next()); - //assert turn restriction - TurnCostEncoder turnEnc = (TurnCostEncoder) enc; //U-Turns have high costs after the import process (u-turn penalty + turn costs) - assertFalse(turnEnc.isTurnRestricted(tcStorage.getTurnCostFlags(con1_2_1, n1, con1_1_2))); - assertFalse(turnEnc.isTurnRestricted(tcStorage.getTurnCostFlags(con3_2_3, n3, con3_3_2))); - assertEquals(122, turnEnc.getTurnCost(tcStorage.getTurnCostFlags(con1_2_1, n1, con1_1_2)), 0.1d); - assertEquals(122, turnEnc.getTurnCost(tcStorage.getTurnCostFlags(con3_2_3, n3, con3_3_2)), 0.1d); + assertFalse(tcStorage.get(vehicleEncoding.turnRestriction(), con1_2_1, n1, con1_1_2)); + assertFalse(tcStorage.get(vehicleEncoding.turnRestriction(), con3_2_3, n3, con3_3_2)); + assertEquals(130, tcStorage.get(vehicleEncoding.turnCost(), con1_2_1, n1, con1_1_2), 0.1d); + assertEquals(130, tcStorage.get(vehicleEncoding.turnCost(), con3_2_3, n3, con3_3_2), 0.1d); //Except for U-Turs in the middle of the road. We do not allow them here - assertTrue(turnEnc.isTurnRestricted(tcStorage.getTurnCostFlags(con1_1_2, n2, con1_2_1))); - assertTrue(turnEnc.isTurnRestricted(tcStorage.getTurnCostFlags(con3_3_2, n2, con3_2_3))); + assertTrue(tcStorage.get(vehicleEncoding.turnRestriction(), con1_1_2, n2, con1_2_1)); + assertTrue(tcStorage.get(vehicleEncoding.turnRestriction(), con3_3_2, n2, con3_2_3)); //Turn restriction in database has been taken over - assertTrue(turnEnc.isTurnRestricted(tcStorage.getTurnCostFlags(con2_4_2, n2, con3_2_3))); + assertTrue(tcStorage.get(vehicleEncoding.turnRestriction(), con2_4_2, n2, con3_2_3)); //all other turns are allowed - assertFalse(turnEnc.isTurnRestricted(tcStorage.getTurnCostFlags(con1_1_2, n2, con3_2_3))); - assertFalse(turnEnc.isTurnRestricted(tcStorage.getTurnCostFlags(con2_4_2, n2, con1_2_1))); + assertFalse(tcStorage.get(vehicleEncoding.turnRestriction(), con1_1_2, n2, con3_2_3)); + assertFalse(tcStorage.get(vehicleEncoding.turnRestriction(), con2_4_2, n2, con1_2_1)); //turn costs have been calculated (might be unrealistic due to unrealistic test data) - assertEquals(40, turnEnc.getTurnCost(tcStorage.getTurnCostFlags(con1_1_2, n2, con3_2_3)), 5.d); //left turn - assertEquals(37, turnEnc.getTurnCost(tcStorage.getTurnCostFlags(con2_4_2, n2, con1_2_1)), 5.d); //left turn + assertEquals(40, tcStorage.get(vehicleEncoding.turnCost(), con1_1_2, n2, con3_2_3), 5.d); //left turn + assertEquals(37, tcStorage.get(vehicleEncoding.turnCost(), con2_4_2, n2, con1_2_1), 5.d); //left turn } - private void assertNextEdge(FlagEncoder enc, EdgeIterator it, Map assertionMap) { + private void assertNextEdge(DecimalEncodedValue speedEnc, WayTypeEncoder wayTypeEnc, EdgeIterator it, Map assertionMap) { assertTrue(it.next()); EdgeAssert edgeAssert = assertionMap.remove(it.getAdjNode()); assertNotNull(edgeAssert); - edgeAssert.assertEdge(enc, it); + edgeAssert.assertEdge(speedEnc, wayTypeEnc, it); } private EdgeAssert edgeAssertion(final int adjNode, final int edgeIt, final double distance, final double speed, final boolean isResidential) { return new EdgeAssert(adjNode) { @Override - void assertEdge(FlagEncoder enc, EdgeIterator it) { + void assertEdge(DecimalEncodedValue speedEnc, WayTypeEncoder wayTypeEnc, EdgeIterator it) { assertEquals(adjNode, it.getAdjNode()); assertEquals(edgeIt, it.getEdge()); assertEquals(distance, it.getDistance(), 0d); - assertEquals(speed, it.get(enc.getAverageSpeedEnc()), 10d); //graphhopper has default max speed of 140 km/h - assertEquals(isResidential, WayTypeEncoder.isResidential(it.getAdditionalField())); + assertEquals(speed, it.get(speedEnc), 10d); //graphhopper has default max speed of 140 km/h + assertEquals(isResidential, WayTypeEncoder.isResidential(it.get(wayTypeEnc))); } }; } @@ -194,7 +187,7 @@ public EdgeAssert(int adjNode) { this.adjNode = adjNode; } - abstract void assertEdge(FlagEncoder enc, EdgeIterator it); + abstract void assertEdge(DecimalEncodedValue speedEnc, WayTypeEncoder wayTypeEnc, EdgeIterator it); public int getAdjNode() { return adjNode; diff --git a/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/util/TurnCostAnalyzerTest.java b/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/util/TurnCostAnalyzerTest.java index cc0e286cd..908025753 100644 --- a/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/util/TurnCostAnalyzerTest.java +++ b/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/util/TurnCostAnalyzerTest.java @@ -19,29 +19,27 @@ import org.eclipse.mosaic.lib.routing.graphhopper.junit.TestGraphRule; -import com.graphhopper.routing.util.CarFlagEncoder; -import com.graphhopper.routing.util.EncodingManager; -import com.graphhopper.routing.util.TurnCostEncoder; -import com.graphhopper.storage.TurnCostExtension; +import com.graphhopper.routing.ev.BooleanEncodedValue; +import com.graphhopper.routing.ev.DecimalEncodedValue; +import com.graphhopper.storage.TurnCostStorage; import org.junit.Rule; import org.junit.Test; public class TurnCostAnalyzerTest { - private EncodingManager encManager = EncodingManager.create(new CarFlagEncoder(5, 5, 127)); - @Rule - public TestGraphRule testGraph = new TestGraphRule(encManager); + public TestGraphRule testGraph = new TestGraphRule(); @Test public void turnCostsCalculation() { //run - new TurnCostAnalyzer().createTurnCostsForCars(testGraph.getGraph(), encManager.getEncoder("CAR")); + new TurnCostAnalyzer(testGraph.getGraph(), testGraph.getEncodingManager().wayType()) + .createTurnCostsForVehicle(testGraph.getEncodingManager().getVehicleEncoding("car")); //assert assertEquals(4d, getTurnCosts(1, 0, 0), 0.4d); //4 seconds for 90deg right turn - assertEquals(2d, getTurnCosts(2, 2, 6), 0.4d); //2 seconds for a slight 45deg right turn + assertEquals(2d, getTurnCosts(2, 2, 6), 0.4d); //2 seconds for a slight 25deg right turn assertEquals(6d, getTurnCosts(0, 1, 4), 0.4d); //6 seconds for a hard 120deg right turn @@ -50,12 +48,14 @@ public void turnCostsCalculation() { } private double getTurnCosts(int fromEdge, int viaNode, int toEdge) { - TurnCostEncoder enc = (TurnCostEncoder) (encManager.getEncoder("CAR")); - TurnCostExtension tcS = testGraph.getTurnCostStorage(); - long flags = tcS.getTurnCostFlags(fromEdge, viaNode, toEdge); - if (enc.isTurnRestricted(flags)) { + TurnCostStorage tc = testGraph.getGraph().getTurnCostStorage(); + BooleanEncodedValue turnRestrictionsEnc = testGraph.getEncodingManager().getVehicleEncoding("car").turnRestriction(); + DecimalEncodedValue turnCostsEnc = testGraph.getEncodingManager().getVehicleEncoding("car").turnCost(); + + boolean isRestricted = tc.get(turnRestrictionsEnc, fromEdge, viaNode, toEdge); + if (isRestricted) { return Double.MAX_VALUE; } - return enc.getTurnCost(flags); + return tc.get(turnCostsEnc, fromEdge, viaNode, toEdge); } } diff --git a/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/util/WayTypeEncoderTest.java b/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/util/WayTypeEncoderTest.java new file mode 100644 index 000000000..3210eea95 --- /dev/null +++ b/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/graphhopper/util/WayTypeEncoderTest.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024 Fraunhofer FOKUS and others. All rights reserved. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contact: mosaic@fokus.fraunhofer.de + */ + +package org.eclipse.mosaic.lib.routing.graphhopper.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class WayTypeEncoderTest { + + @Test + public void encodeDecodeMotorway() { + int c = WayTypeEncoder.encode("motorway", 2); + assertEquals("motorway", WayTypeEncoder.decode(c)); + assertTrue(WayTypeEncoder.isHighway(c)); + assertFalse(WayTypeEncoder.isResidential(c)); + assertFalse(WayTypeEncoder.isMainRoad(c)); + assertFalse(WayTypeEncoder.isCycleway(c)); + } + + @Test + public void encodeDecodeSecondary() { + int c = WayTypeEncoder.encode("secondary", 2); + assertEquals("secondary", WayTypeEncoder.decode(c)); + assertFalse(WayTypeEncoder.isHighway(c)); + assertFalse(WayTypeEncoder.isResidential(c)); + assertTrue(WayTypeEncoder.isMainRoad(c)); + assertFalse(WayTypeEncoder.isCycleway(c)); + } + + @Test + public void encodeDecodeCycleway() { + int c = WayTypeEncoder.encode("cycleway", 1); + assertEquals("cycleway", WayTypeEncoder.decode(c)); + assertFalse(WayTypeEncoder.isHighway(c)); + assertFalse(WayTypeEncoder.isResidential(c)); + assertFalse(WayTypeEncoder.isMainRoad(c)); + assertTrue(WayTypeEncoder.isCycleway(c)); + } + +} \ No newline at end of file diff --git a/lib/mosaic-routing/src/test/resources/charlottenburg.db b/lib/mosaic-routing/src/test/resources/charlottenburg.db new file mode 100644 index 000000000..9e42b5d3b Binary files /dev/null and b/lib/mosaic-routing/src/test/resources/charlottenburg.db differ diff --git a/pom.xml b/pom.xml index bcd4f8ca6..4ad71cdcf 100644 --- a/pom.xml +++ b/pom.xml @@ -93,11 +93,12 @@ 3.6.1 1.6 1.3.9-1 - 0.13.0 + 8.0 2.10.1 32.1.1-jre 0.8.1 - 2.9.9 + 1.19.0 + 2.15.0 1.3.9 2.7.5 1.1.13 @@ -293,12 +294,23 @@ sqlite-jdbc ${version.sqlite-jdbc}
- + + + com.fasterxml.jackson.core + jackson-core + ${version.jackson} + + + + com.fasterxml.jackson.core + jackson-databind + ${version.jackson} + com.fasterxml.jackson.dataformat jackson-dataformat-xml - ${version.jackson-xml} + ${version.jackson} @@ -318,7 +330,7 @@ com.graphhopper - graphhopper-api + graphhopper-web-api ${version.graphhopper} @@ -326,6 +338,28 @@ com.graphhopper graphhopper-core ${version.graphhopper} + + + org.jetbrains.kotlin + kotlin-stdlib + + + org.openstreetmap.osmosis + osmosis-osm-binary + + + de.westnordost + osm-legal-default-speeds-jvm + + + org.codehaus.janino + janino + + + org.apache.xmlgraphics + xmlgraphics-commons + + @@ -333,6 +367,12 @@ hppc ${version.hppc} + + + org.locationtech.jts + jts-core + ${version.jts-core} + org.java-websocket diff --git a/test/mosaic-integration-tests/src/test/java/org/eclipse/mosaic/test/AbstractPerceptionModuleIT.java b/test/mosaic-integration-tests/src/test/java/org/eclipse/mosaic/test/AbstractPerceptionModuleIT.java index 7c0b1ea7f..5ca6daf4c 100644 --- a/test/mosaic-integration-tests/src/test/java/org/eclipse/mosaic/test/AbstractPerceptionModuleIT.java +++ b/test/mosaic-integration-tests/src/test/java/org/eclipse/mosaic/test/AbstractPerceptionModuleIT.java @@ -55,4 +55,5 @@ public void rightAmountOfTrafficLightPhaseSwitches() throws Exception { // perceived vehicles repeat their route 10 times resulting in 11 perceptions LogAssert.contains(simulationRule, PERCEPTION_VEHICLE_LOG, ".*Traffic Light switched 11 times\\..*"); } + }