/* 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 (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.io.Serializable; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.IdentityHashMap; import java.util.Map; import java.util.Queue; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.commons.pool.PoolableObjectFactory; import org.apache.commons.pool.impl.GenericObjectPool; import org.opentripplanner.routing.core.State; import org.opentripplanner.routing.core.StateEditor; import org.opentripplanner.routing.core.TraverseMode; import org.opentripplanner.routing.core.RoutingRequest; import org.opentripplanner.routing.edgetype.FreeEdge; import org.opentripplanner.routing.edgetype.TransitBoardAlight; import org.opentripplanner.routing.edgetype.PreBoardEdge; import org.opentripplanner.routing.graph.Edge; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.graph.Vertex; import org.opentripplanner.common.pqueue.BinHeap; import org.opentripplanner.routing.spt.BasicShortestPathTree; import org.opentripplanner.routing.vertextype.TransitStop; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /* * WeightTable stores a table of lower bounds on shortest path weight between * all pairs of transit stops in a graph */ public class WeightTable implements Serializable { private static final long serialVersionUID = 20110506L; // YYYYMMDD private static final Logger LOG = LoggerFactory .getLogger(WeightTable.class); private float[][] table; private Graph g; Map<Vertex, Integer> stopIndices; private double maxWalkSpeed; private double maxWalkDistance; private transient int count; public WeightTable(Graph g) { this.g = g; // default max walk speed is biking speed //maxWalkSpeed = new TraverseOptions(TraverseMode.BICYCLE).speed; maxWalkSpeed = new RoutingRequest().getSpeed(TraverseMode.WALK); } public double getWeight(Vertex from, Vertex to) { int fi = stopIndices.get(from); int ti = stopIndices.get(to); return table[fi][ti]; } public boolean includes(Vertex v) { return stopIndices.containsKey(v); } public synchronized void incrementCount() { count += 1; if (count % 1000 == 0) LOG.debug("TransitStop " + count + "/" + table.length); } // assignindices(Graph g) // update(origin, dest, newval) static class PoolableBinHeapFactory<T> implements PoolableObjectFactory { private int size; public PoolableBinHeapFactory(int size) { this.size = size; } @SuppressWarnings("unchecked") @Override public void activateObject(Object heap) throws Exception { ((BinHeap<T>) heap).reset(); } @Override public void destroyObject(Object arg0) throws Exception { } @Override public Object makeObject() throws Exception { return new BinHeap<T>(size); } @Override public void passivateObject(Object arg0) throws Exception { } @Override public boolean validateObject(Object arg0) { return true; } } /** * Build the weight table, parallelized according to the number of processors */ public void buildTable() { ArrayList<TransitStop> stopVertices; LOG.debug("Number of vertices: " + g.getVertices().size()); stopVertices = new ArrayList<TransitStop>(); for (Vertex gv : g.getVertices()) if (gv instanceof TransitStop) stopVertices.add((TransitStop) gv); int nStops = stopVertices.size(); stopIndices = new IdentityHashMap<Vertex, Integer>(nStops); for (int i = 0; i < nStops; i++) stopIndices.put(stopVertices.get(i), i); LOG.debug("Number of stops: " + nStops); table = new float[nStops][nStops]; for (float[] row : table) Arrays.fill(row, Float.POSITIVE_INFINITY); LOG.debug("Performing search at each transit stop."); int nThreads = Runtime.getRuntime().availableProcessors(); LOG.debug("number of threads: " + nThreads); ArrayBlockingQueue<Runnable> taskQueue = new ArrayBlockingQueue<Runnable>( nStops); ThreadPoolExecutor threadPool = new ThreadPoolExecutor(nThreads, nThreads, 10, TimeUnit.SECONDS, taskQueue); GenericObjectPool heapPool = new GenericObjectPool( new PoolableBinHeapFactory<State>(g.getVertices().size()), nThreads); // make one heap and recycle it RoutingRequest options = new RoutingRequest(); // TODO LG Check this change: options.setWalkSpeed(maxWalkSpeed); final double MAX_WEIGHT = 60 * 60 * options.walkReluctance; final double OPTIMISTIC_BOARD_COST = options.getBoardCostLowerBound(); // create a task for each transit stop in the graph ArrayList<Callable<Void>> tasks = new ArrayList<Callable<Void>>(); for (TransitStop origin : stopVertices) { SPTComputer task = new SPTComputer(heapPool, options, MAX_WEIGHT, OPTIMISTIC_BOARD_COST, origin); tasks.add(task); } try { //invoke all of tasks. threadPool.invokeAll(tasks); threadPool.shutdown(); } catch (InterruptedException e) { throw new RuntimeException(e); } floyd(); } /** * A callable that computes the shortest path tree out to MAX_WEIGHT for one vertex, updating the weight * table as it goes. * @author novalis * */ class SPTComputer implements Callable<Void> { private GenericObjectPool heapPool; private RoutingRequest options; private double OPTIMISTIC_BOARD_COST; private double MAX_WEIGHT; private TransitStop origin; SPTComputer(GenericObjectPool heapPool, RoutingRequest options, final double MAX_WEIGHT, final double OPTIMISTIC_BOARD_COST, TransitStop origin) { this.heapPool = heapPool; this.options = options; this.MAX_WEIGHT = MAX_WEIGHT; this.OPTIMISTIC_BOARD_COST = OPTIMISTIC_BOARD_COST; this.origin = origin; } @SuppressWarnings("unchecked") public Void call() throws Exception { // LOG.debug("ORIGIN " + origin); int oi = stopIndices.get(origin); // origin index // first check for walking transfers // LOG.debug(" Walk"); BinHeap<State> heap; try { heap = (BinHeap<State>) heapPool.borrowObject(); } catch (Exception e) { throw new RuntimeException(e); } BasicShortestPathTree spt = new BasicShortestPathTree(options); State s0 = new State(origin, options); spt.add(s0); heap.insert(s0, s0.getWeight()); while (!heap.empty()) { double w = heap.peek_min_key(); State u = heap.extract_min(); if (!spt.visit(u)) continue; Vertex uVertex = u.getVertex(); // LOG.debug("heap extract " + u + " weight " + w); if (w > MAX_WEIGHT) break; if (uVertex instanceof TransitStop) { int di = stopIndices.get(uVertex); // dest index table[oi][di] = (float) w; // LOG.debug(" Dest " + u + " w=" + w); } for (Edge e : uVertex.getOutgoing()) { if (!(e instanceof PreBoardEdge)) { State v = e.optimisticTraverse(u); if (v != null && spt.add(v)) heap.insert(v, v.getWeight()); } } } // then check what is accessible in one transit trip heap.reset(); // recycle heap spt = new BasicShortestPathTree(options); // first handle preboard edges Queue<Vertex> q = new ArrayDeque<Vertex>(100); q.add(origin); while (!q.isEmpty()) { Vertex u = q.poll(); for (Edge e : u.getOutgoing()) { if (e instanceof TransitBoardAlight && ((TransitBoardAlight) e).isBoarding()) { Vertex v = ((TransitBoardAlight) e).getToVertex(); // give onboard vertices same index as their // corresponding station stopIndices.put(v, oi); StateEditor se = (new State(u, options)).edit(e); se.incrementWeight(OPTIMISTIC_BOARD_COST); s0 = se.makeState(); spt.add(s0); heap.insert(s0, s0.getWeight()); // LOG.debug(" board " + tov); } else if (e instanceof FreeEdge) { // handle preboard Vertex v = ((FreeEdge) e).getToVertex(); // give onboard vertices same index as their // corresponding station stopIndices.put(v, oi); q.add(v); } } } // all boarding edges for this stop have now been traversed // LOG.debug(" Transit"); while (!heap.empty()) { // check for transit stops when pulling off of heap // and continue when one is found // this is enough to prevent reboarding // need to mark closed vertices because otherwise cycles may // appear (interlining...) double w = heap.peek_min_key(); State u = heap.extract_min(); if (!spt.visit(u)) continue; // LOG.debug(" Extract " + u + " w=" + w); Vertex uVertex = u.getVertex(); if (uVertex instanceof TransitStop) { int di = stopIndices.get(uVertex); // dest index if (table[oi][di] > w) { table[oi][di] = (float) w; // LOG.debug(" Dest " + u + "w=" + w); } continue; } for (Edge e : uVertex.getOutgoing()) { // LOG.debug(" Edge " + e); State v = e.optimisticTraverse(u); if (v != null && spt.add(v)) heap.insert(v, v.getWeight()); // else LOG.debug(" (skip)"); } } heapPool.returnObject(heap); incrementCount(); return null; } } /* Find all pairs shortest paths */ private void floyd() { LOG.debug("Floyd"); int n = table.length; for (int k = 0; k < n; k++) { for (int i = 0; i < n; i++) { double ik = table[i][k]; if (ik == Float.POSITIVE_INFINITY) continue; for (int j = 0; j < n; j++) { double kj = table[k][j]; if (kj == Float.POSITIVE_INFINITY) continue; double ikj = ik + kj; double ij = table[i][j]; if (ikj < ij) table[i][j] = (float) ikj; } } if (k % 50 == 0) LOG.debug("k=" + k + "/" + n); } } public void setMaxWalkSpeed(double maxWalkSpeed) { this.maxWalkSpeed = maxWalkSpeed; } public double getMaxWalkSpeed() { return maxWalkSpeed; } public void setMaxWalkDistance(double maxWalkDistance) { this.maxWalkDistance = maxWalkDistance; } public double getMaxWalkDistance(double maxWalkDistance) { return this.maxWalkDistance; } }