/* This program is free software: you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public License
as published by the Free Software Foundation, either version 3 of
the License, or (props, at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package org.opentripplanner.routing.algorithm.strategies;
import java.util.Arrays;
import org.opentripplanner.common.geometry.DistanceLibrary;
import org.opentripplanner.common.geometry.SphericalDistanceLibrary;
import org.opentripplanner.common.pqueue.BinHeap;
import org.opentripplanner.routing.core.RoutingRequest;
import org.opentripplanner.routing.core.State;
import org.opentripplanner.routing.graph.AbstractVertex;
import org.opentripplanner.routing.graph.Edge;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graph.Vertex;
import org.opentripplanner.routing.location.StreetLocation;
import org.opentripplanner.routing.services.RemainingWeightHeuristicFactory;
import org.opentripplanner.routing.vertextype.StreetVertex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A heuristic that performs a single-source / all destinations shortest path search backward from the target of the main search, using lower bounds
* on the weight of each edge.
*
* @author andrewbyrd
*/
public class BidirectionalRemainingWeightHeuristic implements RemainingWeightHeuristic,
RemainingTimeHeuristic {
private static final long serialVersionUID = 20111002L;
private static Logger LOG = LoggerFactory.getLogger(BidirectionalRemainingWeightHeuristic.class);
Vertex target;
double cutoff;
double[] weights;
int nVertices = 0;
Graph g;
private TransitLocalStreetService localStreetService;
private DistanceLibrary distanceLibrary = SphericalDistanceLibrary.getInstance();
/**
* RemainingWeightHeuristic interface
*/
public BidirectionalRemainingWeightHeuristic(Graph graph) {
this.g = graph;
this.localStreetService = g.getService(TransitLocalStreetService.class);
}
@Override
public void initialize(State s, Vertex target) {
recalculate(s.getVertex(), target, s.getOptions(), false);
}
@Override
public double computeForwardWeight(State s, Vertex target) {
return computeReverseWeight(s, target);
}
@Override
public double computeReverseWeight(State s, Vertex target) {
final Vertex v = s.getVertex();
if (v instanceof StreetLocation)
return 0;
if (s.getWeight() < 10 * 60)
return 0;
int index = v.getIndex();
if (index < weights.length) {
double h = weights[index];
if (s.isEverBoarded()) {
if (localStreetService != null
&& v instanceof StreetVertex
&& s.getOptions().getMaxWalkDistance() - s.getWalkDistance() < distanceLibrary
.fastDistance(v.getCoordinate(), target.getCoordinate())
&& !localStreetService.transferrable(v)) {
return Double.MAX_VALUE;
}
if (s.isAlightedLocal()
|| (!s.isOnboard() && s.getNumBoardings() > s.getOptions()
.getMaxTransfers())) {
double d = distanceLibrary.fastDistance(v.getCoordinate(),
target.getCoordinate());
if (d > s.getOptions().getMaxWalkDistance() - s.getWalkDistance()) {
return Double.MAX_VALUE;
}
double walk = d / s.getOptions().getStreetSpeedUpperBound();
return Math.max(h, walk);
}
}
// System.out.printf("h=%f at %s\n", h, s.getVertex());
// return infinite heuristic values
// so transit boarding is not even attempted useless patterns
return h;
// return h == Double.POSITIVE_INFINITY ? 0 : h;
} else
return 0;
}
private void recalculate(Vertex origin, Vertex target, RoutingRequest options,
boolean timeNotWeight) {
if (target != this.target || options.maxWeight > this.cutoff) {
LOG.debug("recalc");
this.target = target;
this.cutoff = options.maxWeight;
this.nVertices = AbstractVertex.getMaxIndex();
weights = new double[nVertices];
Arrays.fill(weights, Double.POSITIVE_INFINITY);
BinHeap<Vertex> q = new BinHeap<Vertex>();
long t0 = System.currentTimeMillis();
if (target instanceof StreetLocation) {
for (Edge de : ((StreetLocation) target).getExtra()) {
Vertex gv;
if (options.isArriveBy()) {
gv = de.getToVertex();
} else {
gv = de.getFromVertex();
}
int gvi = gv.getIndex();
if (gv == target)
continue;
if (gvi >= nVertices)
continue;
weights[gvi] = 0;
q.insert(gv, 0);
}
} else {
int i = target.getIndex();
weights[i] = 0;
q.insert(target, 0);
}
while (!q.empty()) {
double uw = q.peek_min_key();
Vertex u = q.extract_min();
// cutting off at 2-3 hours seems to improve reaction time
// (this was previously not the case... #656?)
// of course in production this would be scaled according to the distance
if (uw > cutoff)
break;
int ui = u.getIndex();
if (uw > weights[ui])
continue;
Iterable<Edge> edges;
if (options.isArriveBy())
edges = u.getOutgoing();
else
edges = u.getIncoming();
for (Edge e : edges) {
Vertex v = options.isArriveBy() ? e.getToVertex() : e.getFromVertex();
double ew = timeNotWeight ? e.timeLowerBound(options) : e
.weightLowerBound(options);
if (ew < 0) {
LOG.error("negative edge weight {} qt {}", ew, e);
continue;
}
double vw = uw + ew;
int vi = v.getIndex();
if (weights[vi] > vw) {
weights[vi] = vw;
// selectively rekeying did not seem to offer any speed advantage
q.insert(v, vw);
// System.out.println("Insert " + v + " weight " + vw);
}
}
}
LOG.info("End SSSP ({} msec)", System.currentTimeMillis() - t0);
}
}
/**
* RemainingTimeHeuristic interface
*/
@Override
public void timeInitialize(State s, Vertex target) {
recalculate(s.getVertex(), target, s.getOptions(), true);
}
@Override
public double timeLowerBound(State s) {
return computeReverseWeight(s, null);
}
public static class Factory implements RemainingWeightHeuristicFactory {
@Override
public RemainingWeightHeuristic getInstanceForSearch(RoutingRequest opt) {
if (opt.getModes().isTransit()) {
LOG.debug("Transit itinerary requested.");
return new BidirectionalRemainingWeightHeuristic (opt.rctx.graph);
} else {
LOG.debug("Non-transit itinerary requested.");
return new DefaultRemainingWeightHeuristic();
}
}
}
@Override
public void reset() {}
@Override
public void abort() {}
}