/** * This class is an helper for Edges and Vertexes to store various data about elevation profiles. */ public class ElevationProfileSegment implements Serializable { private static final long serialVersionUID = MavenVersion.VERSION.getUID(); private static final Logger LOG = LoggerFactory.getLogger(ElevationProfileSegment.class); private PackedCoordinateSequence elevationProfile; private double length; private double slopeSpeedEffectiveLength; private double bicycleSafetyEffectiveLength; private double slopeWorkCost; private double maxSlope; protected boolean slopeOverride; private boolean flattened; public ElevationProfileSegment(double length) { this.length = length; slopeSpeedEffectiveLength = length; bicycleSafetyEffectiveLength = length; slopeWorkCost = length; } public double getMaxSlope() { return maxSlope; } public void setSlopeOverride(boolean slopeOverride) { this.slopeOverride = slopeOverride; } public boolean getSlopeOverride() { return slopeOverride; } public void setLength(double length) { this.length = length; } public double getLength() { return length; } public void setSlopeSpeedEffectiveLength(double slopeSpeedEffectiveLength) { this.slopeSpeedEffectiveLength = slopeSpeedEffectiveLength; } public double getSlopeSpeedEffectiveLength() { return slopeSpeedEffectiveLength; } public void setBicycleSafetyEffectiveLength(double bicycleSafetyEffectiveLength) { this.bicycleSafetyEffectiveLength = bicycleSafetyEffectiveLength; } public double getBicycleSafetyEffectiveLength() { return bicycleSafetyEffectiveLength; } public void setSlopeWorkCost(double slopeWorkCost) { this.slopeWorkCost = slopeWorkCost; } public double getSlopeWorkCost() { return slopeWorkCost; } public PackedCoordinateSequence getElevationProfile() { return elevationProfile; } public PackedCoordinateSequence getElevationProfile(double start, double end) { return ElevationUtils.getPartialElevationProfile(elevationProfile, start, end); } public void setElevationProfile(PackedCoordinateSequence elevationProfile) { this.elevationProfile = elevationProfile; } public boolean setElevationProfile( PackedCoordinateSequence elev, boolean computed, boolean slopeLimit) { if (elev == null || elev.size() < 2) { return false; } if (slopeOverride && !computed) { return false; } elevationProfile = elev; // compute the various costs of the elevation changes double lengthMultiplier = ElevationUtils.getLengthMultiplierFromElevation(elev); if (Double.isNaN(lengthMultiplier)) { LOG.error("lengthMultiplier from elevation profile is NaN, setting to 1"); lengthMultiplier = 1; } length *= lengthMultiplier; bicycleSafetyEffectiveLength *= lengthMultiplier; SlopeCosts costs = ElevationUtils.getSlopeCosts(elev, slopeLimit); slopeSpeedEffectiveLength = costs.slopeSpeedEffectiveLength; maxSlope = costs.maxSlope; slopeWorkCost = costs.slopeWorkCost; bicycleSafetyEffectiveLength += costs.slopeSafetyCost; flattened = costs.flattened; return costs.flattened; } public String toString() { String out = ""; if (elevationProfile == null || elevationProfile.size() == 0) { return "(empty elevation profile)"; } for (int i = 0; i < elevationProfile.size(); ++i) { Coordinate coord = elevationProfile.getCoordinate(i); out += "(" + coord.x + "," + coord.y + "), "; } return out.substring(0, out.length() - 2); } public boolean isFlattened() { return flattened; } }
public class MultiShortestPathTree extends AbstractShortestPathTree { private static final long serialVersionUID = MavenVersion.VERSION.getUID(); private Map<Vertex, List<State>> stateSets; public MultiShortestPathTree(RoutingRequest options) { super(options); stateSets = new IdentityHashMap<Vertex, List<State>>(); } public Set<Vertex> getVertices() { return stateSets.keySet(); } /** ** {@link ShortestPathTree} Interface ** */ @Override public boolean add(State newState) { Vertex vertex = newState.getVertex(); List<State> states = stateSets.get(vertex); // if the vertex has no states, add one and return if (states == null) { states = new ArrayList<State>(); stateSets.put(vertex, states); states.add(newState); return true; } // if the vertex has any states that dominate the new state, don't add the state // if the new state dominates any old states, remove them Iterator<State> it = states.iterator(); while (it.hasNext()) { State oldState = it.next(); // order is important, because in the case of a tie // we want to reject the new state if (dominates(oldState, newState)) return false; if (dominates(newState, oldState)) it.remove(); } // any states remaining are codominent with the new state states.add(newState); return true; } public static boolean dominates(State thisState, State other) { if (other.weight == 0) { return false; } // Multi-state (bike rental, P+R) - no domination for different states if (thisState.isBikeRenting() != other.isBikeRenting()) return false; if (thisState.isCarParked() != other.isCarParked()) return false; if (thisState.backEdge != other.getBackEdge() && ((thisState.backEdge instanceof StreetEdge) && (!((StreetEdge) thisState.backEdge).getTurnRestrictions().isEmpty()))) return false; if (thisState.routeSequenceSubset(other)) { // TODO subset is not really the right idea return thisState.weight <= other.weight && thisState.getElapsedTimeSeconds() <= other.getElapsedTimeSeconds(); // && this.getNumBoardings() <= other.getNumBoardings(); } // If returning more than one result from GenericAStar, the search can be very slow // unless you replace the following code with: // return false; boolean walkDistanceBetter = thisState.walkDistance <= other.getWalkDistance() * 1.05; double weightRatio = thisState.weight / other.weight; boolean weightBetter = (weightRatio < 1.02 && thisState.weight - other.weight < 30); boolean timeBetter = thisState.getElapsedTimeSeconds() - other.getElapsedTimeSeconds() <= 30; return walkDistanceBetter && weightBetter && timeBetter; // return this.weight < other.weight; } @Override public State getState(Vertex dest) { Collection<State> states = stateSets.get(dest); if (states == null) return null; State ret = null; for (State s : states) { if ((ret == null || s.betterThan(ret)) && s.isFinal() && s.allPathParsersAccept()) { ret = s; } } return ret; } @Override public List<State> getStates(Vertex dest) { return stateSets.get(dest); } @Override public int getVertexCount() { return stateSets.keySet().size(); } /** * Check that a state coming out of the queue is still in the Pareto-optimal set for this vertex, * which indicates that it has not been ruled out as a state on an optimal path. Many shortest * path algorithms will decrease the key of an entry in the priority queue when it is updated, or * remove it when it is dominated. * * <p>When the Fibonacci heap was replaced with a binary heap, the decrease-key operation was * removed for the same reason: both improve theoretical run time complexity, at the cost of high * constant factors and more complex code. * * <p>So there can be dominated (useless) states in the queue. When they come out we want to * ignore them rather than spend time branching out from them. */ @Override public boolean visit(State state) { boolean ret = false; for (State s : stateSets.get(state.getVertex())) { if (s == state) { ret = true; break; } } return ret; } public String toString() { return "MultiSPT(" + this.stateSets.size() + " vertices)"; } @Override public Collection<State> getAllStates() { ArrayList<State> allStates = new ArrayList<State>(); for (List<State> stateSet : stateSets.values()) { allStates.addAll(stateSet); } return allStates; } }