package org.opentripplanner.api.thrift.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import lombok.Data;
import org.apache.thrift.TException;
import org.opentripplanner.api.thrift.OTPServerTask;
import org.opentripplanner.api.thrift.definition.BulkFindNearestEdgesRequest;
import org.opentripplanner.api.thrift.definition.BulkFindNearestEdgesResponse;
import org.opentripplanner.api.thrift.definition.BulkFindNearestVertexRequest;
import org.opentripplanner.api.thrift.definition.BulkFindNearestVertexResponse;
import org.opentripplanner.api.thrift.definition.BulkPathsRequest;
import org.opentripplanner.api.thrift.definition.BulkPathsResponse;
import org.opentripplanner.api.thrift.definition.FindNearestEdgesRequest;
import org.opentripplanner.api.thrift.definition.FindNearestEdgesResponse;
import org.opentripplanner.api.thrift.definition.FindNearestVertexRequest;
import org.opentripplanner.api.thrift.definition.FindNearestVertexResponse;
import org.opentripplanner.api.thrift.definition.FindPathsRequest;
import org.opentripplanner.api.thrift.definition.FindPathsResponse;
import org.opentripplanner.api.thrift.definition.GraphEdge;
import org.opentripplanner.api.thrift.definition.GraphEdgesRequest;
import org.opentripplanner.api.thrift.definition.GraphEdgesResponse;
import org.opentripplanner.api.thrift.definition.GraphVertex;
import org.opentripplanner.api.thrift.definition.GraphVerticesRequest;
import org.opentripplanner.api.thrift.definition.GraphVerticesResponse;
import org.opentripplanner.api.thrift.definition.Location;
import org.opentripplanner.api.thrift.definition.NearestEdgesQuery;
import org.opentripplanner.api.thrift.definition.NearestEdgesResult;
import org.opentripplanner.api.thrift.definition.OTPService;
import org.opentripplanner.api.thrift.definition.PathOptions;
import org.opentripplanner.api.thrift.definition.TripParameters;
import org.opentripplanner.api.thrift.definition.TripPaths;
import org.opentripplanner.api.thrift.definition.VertexQuery;
import org.opentripplanner.api.thrift.definition.VertexResult;
import org.opentripplanner.api.thrift.util.EdgeMatchExtension;
import org.opentripplanner.api.thrift.util.GraphEdgeExtension;
import org.opentripplanner.api.thrift.util.GraphVertexExtension;
import org.opentripplanner.api.thrift.util.LatLngExtension;
import org.opentripplanner.api.thrift.util.RoutingRequestBuilder;
import org.opentripplanner.api.thrift.util.TravelModeSet;
import org.opentripplanner.api.thrift.util.TripPathsExtension;
import org.opentripplanner.common.model.GenericLocation;
import org.opentripplanner.routing.core.RoutingRequest;
import org.opentripplanner.routing.core.TraversalRequirements;
import org.opentripplanner.routing.core.TraverseModeSet;
import org.opentripplanner.routing.edgetype.StreetEdge;
import org.opentripplanner.routing.graph.Edge;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graph.Vertex;
import org.opentripplanner.routing.impl.CandidateEdge;
import org.opentripplanner.routing.impl.CandidateEdge.CandidateEdgeScoreComparator;
import org.opentripplanner.routing.impl.CandidateEdgeBundle;
import org.opentripplanner.routing.services.GraphService;
import org.opentripplanner.routing.services.PathService;
import org.opentripplanner.routing.services.StreetVertexIndexService;
import org.opentripplanner.routing.spt.GraphPath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Concrete implementation of the Thrift interface.
*
* @author flamholz
*/
@Data
public class OTPServiceImpl implements OTPService.Iface {
private static Logger LOG = LoggerFactory.getLogger(OTPServerTask.class);
private GraphService graphService;
private PathService pathService;
private RoutingRequest prototypeRoutingRequest = new RoutingRequest();
/**
* Convenience getter for street index.
*
* @return
*/
private StreetVertexIndexService getStreetIndex() {
return graphService.getGraph().streetIndex;
}
/**
* Returns all vertices in the graph as GraphVertices.
*
* @param g
* @return
*/
private static List<GraphVertex> makeGraphVertices(Graph g) {
Collection<Vertex> verts = g.getVertices();
List<GraphVertex> l = new ArrayList<GraphVertex>(verts.size());
for (Vertex v : verts) {
l.add(new GraphVertexExtension(v));
}
return l;
}
@Override
public GraphVerticesResponse GetVertices(GraphVerticesRequest req) throws TException {
LOG.debug("GetVertices called");
long startTime = System.currentTimeMillis();
GraphVerticesResponse res = new GraphVerticesResponse();
Graph g = graphService.getGraph();
res.setVertices(makeGraphVertices(g));
res.setCompute_time_millis(System.currentTimeMillis() - startTime);
return res;
}
/**
* Returns all edges in the graph as GraphEdges.
*
* @param g
* @return
*/
private static List<GraphEdge> makeGraphEdges(Graph g, GraphEdgesRequest req) {
Collection<Edge> edges;
if (!req.isSetStreet_edges_only()) {
// Get all edges.
edges = g.getEdges();
} else {
// Get only street edges
Collection<StreetEdge> ses = g.getStreetEdges();
// Filter the StreetEdges by allowed traversal modes.
TraverseModeSet allowedModes = TraverseModeSet.allModes();
if (req.isSetCan_be_traversed_by()) {
allowedModes = new TravelModeSet(req.getCan_be_traversed_by()).toTraverseModeSet();
}
Set<Edge> traversableStreetEdges = new HashSet<Edge>();
for (StreetEdge se : ses) {
if (se.canTraverse(allowedModes)) {
traversableStreetEdges.add(se);
}
}
edges = traversableStreetEdges;
}
List<GraphEdge> l = new ArrayList<GraphEdge>(edges.size());
for (Edge e : edges) {
l.add(new GraphEdgeExtension(e));
}
return l;
}
@Override
public GraphEdgesResponse GetEdges(GraphEdgesRequest req) throws TException {
LOG.debug("GetEdges called");
long startTime = System.currentTimeMillis();
GraphEdgesResponse res = new GraphEdgesResponse();
Graph g = graphService.getGraph();
res.setEdges(makeGraphEdges(g, req));
res.setCompute_time_millis(System.currentTimeMillis() - startTime);
return res;
}
private VertexResult findNearbyVertex(VertexQuery q) {
// NOTE(flamholz): can't set the graph here because we are not
// actually doing any routing and don't have a to/from. From the
// perspective of the street indes, RoutingRequest is really just
// a container for the TraversalModes, which is a weird design
// but it's what we've got to work with.
RoutingRequestBuilder builder = new RoutingRequestBuilder(prototypeRoutingRequest);
if (q.isSetAllowed_modes()) {
builder.setTravelModes(q.getAllowed_modes());
}
RoutingRequest rr = builder.build();
// Get the nearest vertex
StreetVertexIndexService streetVertexIndex = getStreetIndex();
GenericLocation gl = new LatLngExtension(q.getLocation().getLat_lng()).toGenericLocation();
// NOTE(flamholz): We don't currently provide a name.
// I guess this would speed things up somewhat?
Vertex closest = streetVertexIndex.getVertexForLocation(gl, rr);
VertexResult result = new VertexResult();
result.setNearest_vertex(new GraphVertexExtension(closest));
rr.cleanup();
return result;
}
@Override
public FindNearestVertexResponse FindNearestVertex(FindNearestVertexRequest req)
throws TException {
LOG.debug("FindNearestVertex called");
long startTime = System.currentTimeMillis();
VertexQuery q = req.getQuery();
VertexResult result = findNearbyVertex(q);
FindNearestVertexResponse res = new FindNearestVertexResponse();
res.setResult(result);
res.setCompute_time_millis(System.currentTimeMillis() - startTime);
return res;
}
@Override
public BulkFindNearestVertexResponse BulkFindNearestVertex(BulkFindNearestVertexRequest req)
throws TException {
LOG.debug("BulkFindNearestVertex called");
long startTime = System.currentTimeMillis();
BulkFindNearestVertexResponse res = new BulkFindNearestVertexResponse();
for (VertexQuery q : req.getQueries()) {
VertexResult result = findNearbyVertex(q);
res.addToResults(result);
}
res.setCompute_time_millis(System.currentTimeMillis() - startTime);
return res;
}
private NearestEdgesResult findNearestEdges(NearestEdgesQuery q) {
// Set up the TraversalRequirements.
TraversalRequirements requirements = new TraversalRequirements();
requirements.setModes(new TravelModeSet(q.getAllowed_modes()).toTraverseModeSet());
// Set up the LocationObservation.
Location queryLoc = q.getLocation();
GenericLocation loc = new LatLngExtension(queryLoc.getLat_lng()).toGenericLocation();
if (queryLoc.isSetHeading()) loc.setHeading(queryLoc.getHeading());
// Find the candidate edges.
// NOTE(flamholz): for now this will return at smallish number of edges because of
// the internal binning that's going on. I'd rather get more edges just in case...
StreetVertexIndexService streetVertexIndex = getStreetIndex();
CandidateEdgeBundle edges = streetVertexIndex.getClosestEdges(loc, requirements);
// Sort them by score.
CandidateEdgeScoreComparator comp = new CandidateEdgeScoreComparator();
Collections.sort(edges, comp);
// Add matches to the response.
NearestEdgesResult result = new NearestEdgesResult();
int maxEdges = q.getMax_edges();
for (CandidateEdge e : edges) {
if (result.getNearest_edgesSize() >= maxEdges) break;
result.addToNearest_edges(new EdgeMatchExtension(e));
}
return result;
}
@Override
public FindNearestEdgesResponse FindNearestEdges(FindNearestEdgesRequest req) throws TException {
LOG.debug("FindNearestEdges called");
long startTime = System.currentTimeMillis();
NearestEdgesQuery q = req.getQuery();
NearestEdgesResult result = findNearestEdges(q);
FindNearestEdgesResponse res = new FindNearestEdgesResponse();
res.setResult(result);
res.setCompute_time_millis(System.currentTimeMillis() - startTime);
return res;
}
@Override
public BulkFindNearestEdgesResponse BulkFindNearestEdges(BulkFindNearestEdgesRequest req)
throws TException {
LOG.debug("BulkFindNearestEdges called");
long startTime = System.currentTimeMillis();
BulkFindNearestEdgesResponse res = new BulkFindNearestEdgesResponse();
for (NearestEdgesQuery q : req.getQueries()) {
NearestEdgesResult result = findNearestEdges(q);
res.addToResults(result);
}
res.setCompute_time_millis(System.currentTimeMillis() - startTime);
return res;
}
/**
* Computes the GraphPath for the given trip.
*
* @param trip
* @return
*/
private TripPaths computePaths(TripParameters trip, PathOptions pathOptions) {
// Build the RoutingRequest. For now, get only one itinerary.
RoutingRequest options = (new RoutingRequestBuilder(prototypeRoutingRequest))
.addTripParameters(trip)
.setGraph(graphService.getGraph())
.setNumItineraries(pathOptions.getNum_paths())
.build();
// For now, always use the default router.
options.setRouterId("");
// TODO(flamholz): respect the return_detailed_path option.
List<GraphPath> paths = pathService.getPaths(options);
if (paths == null || paths.size() == 0) {
LOG.warn("Found 0 paths for trip {}", trip);
LOG.warn("Origin {}", options.getFrom());
LOG.warn("Destination {}", options.getTo());
}
TripPathsExtension tripPaths = new TripPathsExtension(trip, paths);
// Need to call RoutingRequest.cleanup() to cleanup the temp edges.
options.cleanup();
return tripPaths;
}
@Override
public FindPathsResponse FindPaths(FindPathsRequest req) throws TException {
LOG.debug("FindPaths called");
long startTime = System.currentTimeMillis();
TripParameters trip = req.getTrip();
TripPaths outPaths = new TripPaths();
outPaths.setTrip(trip);
TripPaths tripPaths = computePaths(trip, req.getOptions());
FindPathsResponse res = new FindPathsResponse();
res.setPaths(tripPaths);
res.setCompute_time_millis(System.currentTimeMillis() - startTime);
return res;
}
@Override
public BulkPathsResponse BulkFindPaths(BulkPathsRequest req) throws TException {
LOG.debug("BulkFindPaths called");
long startTime = System.currentTimeMillis();
PathOptions pathOptions = req.getOptions();
BulkPathsResponse res = new BulkPathsResponse();
for (TripParameters trip : req.getTrips()) {
TripPaths tripPaths = computePaths(trip, pathOptions);
res.addToPaths(tripPaths);
}
res.setCompute_time_millis(System.currentTimeMillis() - startTime);
return res;
}
}