public async Task <IActionResult> RouteRequest(string startNode, string endNode, string routeType, TimeSpan startingTime) { try { // Get all student routes. AllStudentRoutes = await CongestionHelper.GenerateAllStudentRoutes(DissDatabaseContext, Pathfinder, MemoryCache, AppSettings.Accommodations); RouteRequestResponse response = new RouteRequestResponse(); // If start and end are the same, return empty response. if (startNode == endNode) { return(Ok(response)); } // Get a list of possible start and end nodes that fulfil this request. List <string> starts = await RoutingHelper.GetMatchingIds(DissDatabaseContext, startNode, routeType[0].ToString()); List <string> ends = await RoutingHelper.GetMatchingIds(DissDatabaseContext, endNode, routeType.Last().ToString()); // Check we have a start location. if (starts.Count == 0) { return(BadRequest("Could not find start points.")); } // Check we have an end location. if (ends.Count == 0) { return(BadRequest("Could not find end points")); } // Calculate all possible routes between all starts and all ends. // There is more than one start or end point - probably more efficient to calculate in parallel. if (starts.Count > 1 || ends.Count > 1) { ConcurrentBag <Route> possibleRoutes = new ConcurrentBag <Route>(); ConcurrentBag <Route> possibleAdjustedRoutes = new ConcurrentBag <Route>(); Parallel.ForEach(starts, startPoint => { Parallel.ForEach(ends, destination => { // Calculate route and add to possible. possibleRoutes.Add(Pathfinder.BuildAStar(startPoint, destination)); possibleAdjustedRoutes.Add(Pathfinder.BuildAStar(startPoint, destination, AllStudentRoutes, startingTime)); }); }); // Find the shortest route from the possible routes. response.NormalRoute = RoutingHelper.FindTheBestRouteFromPossibleRoutes(possibleRoutes.ToList()); response.AdjustedRoute = RoutingHelper.FindTheBestRouteFromPossibleRoutes(possibleAdjustedRoutes.ToList()); } else { // Only one start and end point - only need to do this once, so avoid parallel overheads. response.NormalRoute = Pathfinder.BuildAStar(starts[0], ends[0]); response.AdjustedRoute = Pathfinder.BuildAStar(starts[0], ends[0], AllStudentRoutes, startingTime); } // Adjust walking times based on congestion values. response.NormalRoute.CalculateAdjustedWalkingTime(AllStudentRoutes, startingTime); response.AdjustedRoute.CalculateAdjustedWalkingTime(AllStudentRoutes, startingTime); if (response.AdjustedRoute.WalkingTimeSeconds == response.NormalRoute.WalkingTimeSeconds) { // Both routes are the same, remove the adjusted route - there was likely no congestion. response.AdjustedRoute = new Route(); } return(Ok(response)); } catch (Exception exception) { return(StatusCode(500, exception.Message)); } }
/// <summary> /// Calculate the adjusted walking time for this route, taking congestion into account. /// </summary> /// <param name="allStudentRoutes">A collection of all student routes today.</param> /// <param name="startingTime">The starting time of this route.</param> /// <exception cref="InvalidOperationException"></exception> /// <exception cref="AggregateException"></exception> public void CalculateAdjustedWalkingTime(StudentRoute[] allStudentRoutes, TimeSpan startingTime) { double distanceSoFar = 0; double totalAdjustedWalkingTime = 0; List <Dictionary <(string entryId, string exitId), int> > allCongestionValues = new List <Dictionary <(string entryId, string exitId), int> >(RouteNodes.Count); for (int i = 0; i < RouteNodes.Count - 1; i++) { // Increment the distance walked so far. if (RouteNodes[i].Type == NodeType.Corridor && RouteNodes[i + 1].Type == NodeType.Corridor) { distanceSoFar += RouteNodes[i].DistanceInMetersTo(RouteNodes[i + 1]); } else { distanceSoFar += SharedFunctions.NonCorridorDistance; } // Calculate the time spent getting to this point and get the current time of day from that. TimeSpan currentTime = startingTime.Add(TimeSpan.FromSeconds(SharedFunctions.CalculateWalkingTimeNoRounding(distanceSoFar))); // Calculate the occupancies at this point. Dictionary <(string entryId, string exitId), int> edgeOccupancies = CongestionHelper.CalculateEdgeOccupanciesAtTime(allStudentRoutes, currentTime); if (edgeOccupancies.Count > 0) { // Add to running count for heatmap congestion calculation later. allCongestionValues.Add(edgeOccupancies); } // Get the edge object. NodeEdge edge = RouteNodes[i].OutgoingEdges.Find(e => e.Node2.NodeId == RouteNodes[i + 1].NodeId); if (edge != default) { // Increment the actual walking time. totalAdjustedWalkingTime += Pathfinder.CalculateWalkingTimeWithCongestion(edge, edgeOccupancies).timeWithCongestion; } } // Congestion for heatmap display. Congestion = new Dictionary <string, float>(); if (allCongestionValues.Count > 0) { foreach (Dictionary <(string entryId, string exitId), int> entry in allCongestionValues) { foreach (KeyValuePair <(string entryId, string exitId), int> congestedEdge in entry) { string newKey = $"{congestedEdge.Key.entryId},{congestedEdge.Key.exitId}"; if (!Congestion.TryAdd(newKey, congestedEdge.Value)) { // Already exists, add to it Congestion[newKey] += congestedEdge.Value; } } } if (Congestion.Count > 0) { MaxCongestion = Congestion.Values.Max(); } } WalkingTimeSeconds = Convert.ToInt32(Math.Round(totalAdjustedWalkingTime)); }