package org.rudirect.android.util; import android.util.Log; import org.jgrapht.GraphPath; import org.jgrapht.alg.DijkstraShortestPath; import org.jgrapht.graph.DirectedWeightedPseudograph; import org.rudirect.android.data.model.BusData; import org.rudirect.android.data.model.BusRoute; import org.rudirect.android.data.model.BusRouteEdge; import org.rudirect.android.data.model.BusStop; import org.rudirect.android.data.model.BusStopTime; import java.util.ArrayList; import java.util.HashMap; public class DirectionsUtil { private static final String TAG = DirectionsUtil.class.getSimpleName(); // The graph of active bus stops private static DirectedWeightedPseudograph<BusStop, BusRouteEdge> busStopsGraph; // Hash map of the bus stops private static HashMap<String, ArrayList<BusStop>> busStopsHashMap; // Initial wait private static double initialWait = -1; // Total path time private static double totalPathTime = -1; // Build the bus stop graph public static void setupBusStopsGraph() { busStopsGraph = new DirectedWeightedPseudograph<>(BusRouteEdge.class); busStopsHashMap = new HashMap<>(); // Add all the active bus stops to the graph for (BusRoute activeRoute : BusData.getActiveRoutes()) { if (activeRoute != null) { // Log.d(TAG, "Active bus tag: " + activeBusTag + "\n_"); BusStop[] busStops = activeRoute.getBusStops(); BusStopTime prevTime = null; // Add vertex if the first bus stop is active if (busStops[0].isActive()) { addVertex(activeRoute, busStops[0]); prevTime = busStops[0].getTimes().get(0); } // Iterate through all the bus stops for (int i = 1; i < busStops.length; i++) { // Add vertex if this bus stop is active if (busStops[i].isActive()) { addVertex(activeRoute, busStops[i]); // Add edge between this bus stop and the previous bus stop if they are both active if (busStops[i - 1].isActive()) { prevTime = addEdge(activeRoute, busStops[i - 1], busStops[i], prevTime); } } } // Add edge from last bus stop to first bus stop if (busStops[busStops.length - 1].isActive() && busStops[0].isActive()) { addEdge(activeRoute, busStops[busStops.length - 1], busStops[0], prevTime); } } } Log.d(TAG, "Bus stops graph has been set up."); } // Adds a weighted edge between two bus stops private static BusStopTime addEdge(BusRoute route, BusStop stop1, BusStop stop2, BusStopTime prevTime) { // Set previous time if it hasn't been set yet if (prevTime == null) { prevTime = stop2.getTimes().get(0); } // Add edge between stop1 and stop2 BusRouteEdge edge = busStopsGraph.addEdge(stop1, stop2); ArrayList<BusStopTime> busStopTimes = stop2.getTimes(); String vehicleId = prevTime.getVehicleId(); edge.setRouteName(route.getTitle()); edge.setRouteTag(route.getTag()); // Iterate through all the times for the bus stop to get the lowest one for (int j = 0; j < busStopTimes.size(); j++) { BusStopTime time = busStopTimes.get(j); // Check to see that the time for this bus stop is greater than the time for the previous bus stop if (time.getMinutes() - prevTime.getMinutes() < 0) { continue; } // Staying on the same vehicle if (vehicleId.equals(time.getVehicleId())) { edge.setVehicleId(vehicleId); busStopsGraph.setEdgeWeight(edge, time.getMinutes() - prevTime.getMinutes()); // Log.d(TAG, "Edge: " + stop1.getTitle() + " to " + stop2.getTitle()); // Log.d(TAG, "Vehicle ID: " + time.getVehicleId() + ", Time (same vehicle): " + (time.getMinutes() - prevTime.getMinutes())); return time; } // Transfer to another vehicle else { edge.setVehicleId(prevTime.getVehicleId()); busStopsGraph.setEdgeWeight(edge, time.getMinutes() - prevTime.getMinutes()); // Log.d(TAG, "Edge: " + stop1.getTitle() + " to " + stop2.getTitle()); // Log.d(TAG, "Vehicle ID: " + time.getVehicleId() + ", Time (vehicle transfer): " + (time.getMinutes() - prevTime.getMinutes())); return time; } } // Could not add edge to the graph, e.g. because the times for stop 2 were smaller than the times for stop 1 busStopsGraph.removeEdge(edge); return null; } // Adds the bus stop to the graph while also handling duplicate bus stops private static void addVertex(BusRoute route, BusStop busStop) { ArrayList<BusStop> stopsArrayList; // Reset the bus stop ID to 0 if it isn't 0 if (busStop.getId() != 0) { busStop.setId(0); } if (busStopsGraph.containsVertex(busStop)) { // If the bus stop already exists in the graph stopsArrayList = busStopsHashMap.get(busStop.getTitle()); busStop.setId(stopsArrayList.size()); busStopsGraph.addVertex(busStop); for (BusStop stop : stopsArrayList) { addEdge(route, busStop, stop, busStop.getTimes().get(0)); addEdge(route, stop, busStop, stop.getTimes().get(0)); } stopsArrayList.add(busStop); } else { // If the bus stop doesn't exist in the graph busStopsGraph.addVertex(busStop); stopsArrayList = new ArrayList<>(); stopsArrayList.add(busStop); busStopsHashMap.put(busStop.getTitle(), stopsArrayList); } } // Calculate the shortest path from the origin to the destination public static GraphPath<BusStop, BusRouteEdge> calculateShortestPath(BusStop origin, BusStop destination) throws IllegalArgumentException { GraphPath<BusStop, BusRouteEdge> shortestPath = null; double shortestPathTime = Double.MAX_VALUE; outer: for (int i = 0; i < busStopsGraph.vertexSet().size(); i++) { origin.setId(i); for (int j = 0; j < busStopsGraph.vertexSet().size(); j++) { destination.setId(j); try { GraphPath<BusStop, BusRouteEdge> newShortestPath = new DijkstraShortestPath<>(busStopsGraph, origin, destination).getPath(); if (newShortestPath == null) { break; } else { double newInitialWait = getInitialWait(newShortestPath); double newTravelTime = getTravelTime(newShortestPath); double newPathTime = newInitialWait + newTravelTime; if (shortestPath == null || newPathTime < shortestPathTime) { initialWait = newInitialWait; totalPathTime = newPathTime; shortestPath = newShortestPath; shortestPathTime = newPathTime; } } } catch (IllegalArgumentException e) { if (i == 0 && j == 0) { origin.setId(0); destination.setId(0); throw new IllegalArgumentException(); } else if (j == 0) { break outer; } else { break; } } } } origin.setId(0); destination.setId(0); return shortestPath == null ? null : shortestPath; } // Calculate and return the initial wait for a given path private static double getInitialWait(GraphPath<BusStop, BusRouteEdge> path) { ArrayList<BusStop> busStops = busStopsHashMap.get(path.getStartVertex().getTitle()); return busStops.get(path.getStartVertex().getId()).getTimes().get(0).getMinutes(); } // Calculate and return the travel time for a given path private static double getTravelTime(GraphPath<BusStop, BusRouteEdge> path) { return path.getWeight(); } // Calculate and return the total travel time for a graph path private static double getPathTime(GraphPath<BusStop, BusRouteEdge> path) { return path.getWeight() + getInitialWait(path); } public static double getInitialWait() { return initialWait; } public static double getTotalPathTime() { return totalPathTime; } // For debugging: print out the vertices of the bus stops graph and their corresponding edges public static void printBusStopsGraph() { for (BusStop stop : busStopsGraph.vertexSet()) { Log.d(stop.toString(), busStopsGraph.outgoingEdgesOf(stop).toString()); } Log.d(TAG, "Done printing out bus stops graph."); } }