/* 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.core; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.opentripplanner.common.geometry.DistanceLibrary; import org.opentripplanner.common.geometry.SphericalDistanceLibrary; import org.opentripplanner.routing.graph.Edge; import org.opentripplanner.routing.edgetype.PatternHop; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.graph.Vertex; import org.opentripplanner.routing.location.StreetLocation; import org.opentripplanner.common.pqueue.IntBinHeap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.index.strtree.STRtree; /** * decimate * @author andrewbyrd */ public class SimplifiedLowerBoundGraph { private static Logger LOG = LoggerFactory.getLogger(SimplifiedLowerBoundGraph.class); Graph originalGraph; int [][] vertex; double[][] weight; private List<List<Vertex>> vertex_by_gindex = new ArrayList<List<Vertex>>(); int max_gindex = 0; final double GROUP_RADIUS = 5.0; // meters private DistanceLibrary distanceLibrary = SphericalDistanceLibrary.getInstance(); private void groupVertices() { LOG.info("grouping vertices by location..."); max_gindex = 0; LOG.info("building spatial index..."); STRtree vertexIndex = new STRtree(); for (Vertex v : originalGraph.getVertices()) { Envelope env = new Envelope(v.getCoordinate()); vertexIndex.insert(env, v); v.setGroupIndex(-1); } vertexIndex.build(); for (Vertex v : originalGraph.getVertices()) { if (v.getGroupIndex() != -1) continue; Coordinate coordinate = v.getCoordinate(); Envelope env = new Envelope(coordinate); env.expandBy(SphericalDistanceLibrary.metersToDegrees(GROUP_RADIUS)); @SuppressWarnings("unchecked") List<Vertex> nearby = vertexIndex.query(env); ArrayList<Vertex> group = new ArrayList<Vertex>(); // group will contain at least v and possibly other vertices for (Vertex n : nearby) { if (n.getGroupIndex() == -1 && distanceLibrary .distance(n.getCoordinate(), coordinate) <= GROUP_RADIUS) { n.setGroupIndex(max_gindex); group.add(n); } } group.trimToSize(); vertex_by_gindex.add(group); max_gindex += 1; if (max_gindex % 10000 == 0) LOG.info(" group {}", max_gindex); } } @SuppressWarnings("unchecked") public SimplifiedLowerBoundGraph(Graph original) { this.originalGraph = original; groupVertices(); Map<Integer, Double>[] timeEdges = (Map<Integer, Double>[]) new Map[max_gindex]; Map<Integer, Double>[] distEdges = (Map<Integer, Double>[]) new Map[max_gindex]; for (int group = 0; group < max_gindex; group++){ timeEdges[group] = new HashMap<Integer, Double>(); distEdges[group] = new HashMap<Integer, Double>(); } LOG.info("finding border edges..."); RoutingRequest dummyOptions = new RoutingRequest(); for (int from_gindex = 0; from_gindex < max_gindex; from_gindex++) { if (from_gindex % 10000 == 0) LOG.info(" group {}", from_gindex); List<Vertex> group = vertex_by_gindex.get(from_gindex); for (Vertex u : group) { for (Edge e : u.getOutgoing()) { Vertex v = e.getToVertex(); int to_gindex = v.getGroupIndex(); if (to_gindex == from_gindex) continue; if (e instanceof PatternHop) putIfBetter(distEdges, from_gindex, to_gindex, e.timeLowerBound(dummyOptions)); else putIfBetter(distEdges, from_gindex, to_gindex, e.getDistance()); } } } LOG.info("saving outgoing edges for each group vertex..."); vertex = new int [max_gindex][]; weight = new double[max_gindex][]; for (int group = 0; group < max_gindex; group++){ if (group % 10000 == 0) LOG.info(" group {}", group); Map<Integer, Double> te = timeEdges[group]; Map<Integer, Double> de = distEdges[group]; int nOutgoing = te.size() + de.size(); vertex[group] = new int [nOutgoing]; weight[group] = new double[nOutgoing]; int ei = 0; for (Entry<Integer, Double> edge : te.entrySet()) { vertex[group][ei] = edge.getKey(); weight[group][ei] = edge.getValue(); ei += 1; } for (Entry<Integer, Double> edge : de.entrySet()) { vertex[group][ei] = edge.getKey(); weight[group][ei] = - (edge.getValue()); // sign is used to indicate speed-scalable edge ei += 1; } } } private boolean putIfBetter(Map<Integer, Double>[] mapArray, int fg, int tg, double value) { Map<Integer, Double> map = mapArray[fg]; Double old_value = map.get(tg); if (old_value == null || old_value > value) { map.put(tg, value); return true; } return false; } // single-source shortest path (weight to all reachable destinations) public double[] sssp(StreetLocation origin, RoutingRequest options) { double[] result = new double[max_gindex]; Arrays.fill(result, Double.POSITIVE_INFINITY); IntBinHeap q = new IntBinHeap(max_gindex / 2); for (Edge de : origin.getExtra()) { Vertex toVertex = de.getToVertex(); int toGroup = toVertex.getGroupIndex(); if (toVertex == origin) continue; if (toGroup >= max_gindex || toGroup < 0) continue; result[toGroup] = 0; q.insert(toGroup, 0); } double walkScale = -1 / options.getStreetSpeedUpperBound() * options.walkReluctance; LOG.info("Performing SSSP"); long t0 = System.currentTimeMillis(); while (!q.empty()) { double uw = q.peek_min_key(); int ui = q.p_extract_min(); int[] vs = vertex[ui]; double[] ws = weight[ui]; LOG.trace("extract {}", uw); if (vs == null) continue; int ne = vs.length; for (int ei = 0; ei < ne; ei++) { int vi = vs[ei]; double weight = ws[ei]; if (weight < 0) // sign is used to indicate speed-scalable edges weight = weight * walkScale; double vw = uw + weight; if (result[vi] > vw) { result[vi] = vw; q.insert(vi, vw); } } } LOG.info("End SSSP ({} msec)", System.currentTimeMillis() - t0); return result; } }