/* 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.impl; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import org.opentripplanner.routing.core.RoutingRequest; import org.opentripplanner.routing.core.State; import org.opentripplanner.routing.edgetype.LegSwitchingEdge; import org.opentripplanner.routing.graph.Edge; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.graph.Vertex; import org.opentripplanner.routing.pathparser.BasicPathParser; import org.opentripplanner.routing.pathparser.NoThruTrafficPathParser; import org.opentripplanner.routing.pathparser.PathParser; import org.opentripplanner.routing.services.GraphService; import org.opentripplanner.routing.services.PathService; import org.opentripplanner.routing.services.SPTService; import org.opentripplanner.routing.spt.GraphPath; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; public class TravelingSalesmanPathService implements PathService { private static final Logger LOG = LoggerFactory.getLogger(TravelingSalesmanPathService.class); @Autowired public GraphService graphService; // @Resource("name") or @Qualifier @Autowired public SPTService tspSptService; private PathService chainedPathService; @Override public List<GraphPath> getPaths(RoutingRequest options) { if (!options.hasIntermediatePlaces()) { LOG.debug("No intermediates places given, calling underlying path service."); // no intermediate places specified, chain to main path service return chainedPathService.getPaths(options); } /* intermediate places present, intercept request */ Graph graph = graphService.getGraph(options.getRouterId()); long time = options.dateTime; options.setRoutingContext(graph); options.rctx.pathParsers = new PathParser[] { new BasicPathParser(), new NoThruTrafficPathParser() }; Vertex fromVertex = options.rctx.fromVertex; Vertex toVertex = options.rctx.toVertex; if (options.intermediatesEffectivelyOrdered()) { LOG.debug("Intermediates are ordered."); List<Vertex> vertices = options.rctx.intermediateVertices; vertices.add(toVertex); options.clearIntermediatePlaces(); // simple case: intermediate places are in order. List<GraphPath> paths = new ArrayList<GraphPath>(); Vertex previousVertex = fromVertex; for (Vertex v : vertices) { options.dateTime = time; options.setRoutingContext(graph, previousVertex, v); List<GraphPath> partialPaths = chainedPathService.getPaths(options); if (partialPaths == null || partialPaths.size() == 0) return null; GraphPath path = partialPaths.get(0); paths.add(path); previousVertex = v; time = path.getEndTime(); } return Arrays.asList(joinPaths(paths)); } // Difficult case: intermediate places can occur in any order (Traveling Salesman Problem) LOG.debug("Intermediates are not ordered: attempting to optimize ordering."); Map<Vertex, HashMap<Vertex, GraphPath>> paths = new HashMap<Vertex, HashMap<Vertex, GraphPath>>(); HashMap<Vertex, GraphPath> firstLegPaths = new HashMap<Vertex, GraphPath>(); paths.put(fromVertex, firstLegPaths); // compute shortest paths between each pair of vertices @SuppressWarnings("unchecked") List<Vertex> intermediates = (List<Vertex>) options.rctx.intermediateVertices.clone(); for (Vertex v : intermediates) { /* Find initial paths from the source vertex to the intermediate vertex */ options.dateTime = time; options.setRoutingContext(graph, fromVertex, v); List<GraphPath> partialPaths = chainedPathService.getPaths(options); if (partialPaths == null || partialPaths.size() == 0) return null; firstLegPaths.put(v, partialPaths.get(0)); /* Find paths from this intermediate vertex to each other intermediate */ HashMap<Vertex, GraphPath> outPaths = new HashMap<Vertex, GraphPath>(); paths.put(v, outPaths); for (Vertex tv : intermediates) { /* We don't need to compute paths where the source and target vertex are the same */ if (v == tv) continue; options.setRoutingContext(graph, v, tv); List<GraphPath> morePaths = chainedPathService.getPaths(options); if (!morePaths.isEmpty()) { outPaths.put(tv, morePaths.get(0)); } } /* Find paths from the intermediate vertex to the target vertex */ options.setRoutingContext(graph, v, toVertex); List<GraphPath> lastPaths = chainedPathService.getPaths(options); if (!lastPaths.isEmpty()) outPaths.put(toVertex, lastPaths.get(0)); } // compute shortest path overall HashSet<Vertex> verticesCopy = new HashSet<Vertex>(); verticesCopy.addAll(intermediates); return Arrays.asList(TSPPathFinder.findShortestPath(toVertex, fromVertex, paths, verticesCopy, time, options)); } private GraphPath joinPaths(List<GraphPath> paths) { State lastState = paths.get(0).states.getLast(); GraphPath newPath = new GraphPath(lastState, false); Vertex lastVertex = lastState.getVertex(); for (GraphPath path : paths.subList(1, paths.size())) { lastState = newPath.states.getLast(); // add a leg-switching state LegSwitchingEdge legSwitchingEdge = new LegSwitchingEdge(lastVertex, lastVertex); lastState = legSwitchingEdge.traverse(lastState); newPath.edges.add(legSwitchingEdge); newPath.states.add(lastState); // add the next subpath for (Edge e : path.edges) { lastState = e.traverse(lastState); newPath.edges.add(e); newPath.states.add(lastState); } lastVertex = path.getEndVertex(); } return newPath; } public PathService getChainedPathService() { return chainedPathService; } @Autowired public void setChainedPathService(PathService chainedPathService) { this.chainedPathService = chainedPathService; } }