/// <summary> /// Calculate the best organization of the tasks. /// It will seperate tasks based on service type, and then push the ordered task collections when complete. /// This will open the AlgorithmProgress window. /// </summary> /// <param name="unroutedRouteTasks">The task holders to order.</param> /// <param name="serviceTypes">The service types to consider.</param> /// <returns>An observable to stay asynchronous. The result is pushed once, so take the first item.</returns> public IObservable<IEnumerable<IEnumerable<RouteTask>>> OrderTasks(IEnumerable<RouteTask> unroutedRouteTasks, IEnumerable<string> serviceTypes) { var algorithmStatus = new AlgorithmStatus { AlgorithmVM = this }; var result = new Subject<IEnumerable<IEnumerable<RouteTask>>>(); //Organize the unroutedRouteTasks by ServiceTemplateName //only choose task holders that have LocationIds, a ServiceName, and a Latitude and a Longitude var RouteTasksToRoute = unroutedRouteTasks.Where(th => th.LocationId.HasValue && th.Name != null && th.Location.Latitude.HasValue && th.Location.Longitude.HasValue) .ToArray(); //Find which service types should be routed by choosing the (distinct) ServiceTypes of the unroutedRouteTasks //only choose the types that should be routes //Then only choose route types Make sure each of those service types have at least one route with that RouteType var distinctServiceTemplates = RouteTasksToRoute.Select(th => th.Name).Distinct() .Where(st => serviceTypes.Any(t => t == st)) .ToArray(); //Seperate the RouteTasks by ServiceTemplate Name to prevent routing different service types together var RouteTaskCollections = distinctServiceTemplates.Select(serviceTemplateName => RouteTasksToRoute.Where(th => th.Name == serviceTemplateName)); #region Depot no longer used ////If there is not a depot set. Default to FoundOPS, 1305 Cumberland Ave, 47906: 40.460335, -86.929840 //var depot = new GeoLocation(40.460335, -86.929840); ////Try to get the depot from the business account //var ownerBusinessAccount = Manager.Context.OwnerAccount as BusinessAccount; //if (ownerBusinessAccount != null && ownerBusinessAccount.Depots.Any()) //{ // var depotLocation = ownerBusinessAccount.Depots.First(); // if (depotLocation.Latitude.HasValue && depotLocation.Longitude.HasValue) // depot = new GeoLocation((double)depotLocation.Latitude.Value, (double)depotLocation.Longitude.Value); //} #endregion int totalTasksToRoute = RouteTaskCollections.Select(RouteTaskCollection => RouteTaskCollection.Count()).Sum(); if (totalTasksToRoute <= 0) return Observable.Empty<IEnumerable<IEnumerable<RouteTask>>>(); //Setup countdown timer //a) aggregate the total time to route //b) countdown TotalCalculationTime = TimeToCalculate(totalTasksToRoute); this.TimeRemaining = TotalCalculationTime; var dispatcherTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; dispatcherTimer.Tick += (s, e) => { TimeRemaining -= TimeSpan.FromSeconds(1); if (TimeRemaining > new TimeSpan()) return; //When there is no TimeRemaining //a) reset TimeRemaining to 0 seconds //b) stop the countdown TimeRemaining = new TimeSpan(); dispatcherTimer.Stop(); }; dispatcherTimer.Start(); //Calculate the order, one collection at a time //peform the calculations asynchronously if (totalTasksToRoute >= 0) Observable.Start(() => { PerformNextCalcuation(RouteTaskCollections, 0, new List<IEnumerable<RouteTask>>(), algorithmStatus, result); }); algorithmStatus.Show(); return result.AsObservable(); }
/// <summary> /// A recursive method that will calculate the order of the routes, one route at a time /// </summary> /// <param name="geoLocationCollections">The collections to order</param> /// <param name="index">The index of the collection to calculate</param> /// <param name="orderedRouteTaskCollections">The ordered RouteTask collections</param> /// <param name="statusWindow">The window to close when complete</param> /// <param name="resultSubject">The subject to push when complete</param> private void PerformNextCalcuation(IEnumerable<IEnumerable<IGeoLocation>> geoLocationCollections, int index, List<IEnumerable<RouteTask>> orderedRouteTaskCollections, AlgorithmStatus statusWindow, Subject<IEnumerable<IEnumerable<RouteTask>>> resultSubject) { //if all routes have been calculated, close the algorithm status window and return if (index >= geoLocationCollections.Count()) { statusWindow.Close(); resultSubject.OnNext(orderedRouteTaskCollections); return; } var collectionToCalculate = geoLocationCollections.ElementAt(index).ToList(); var timeToCalculate = TimeToCalculate(collectionToCalculate.Count); var calculator = new HiveRouteCalculator(); var foundSolution = calculator.Search(collectionToCalculate); IList<IGeoLocation> bestSolution = null; foundSolution.Subscribe(solutionMessage => { if (solutionMessage == null) return; bestSolution = solutionMessage.Solution; ////convert meters to miles //CurrentDistance = (int)(solutionMessage.Quality / 0.000621371192); }); //Stop the algorithm after timeToCalculate is up //then perform the next calculation var stopAlgorithmDisposable = Rxx3.RunDelayed(timeToCalculate, () => { calculator.StopSearch(); //add the best solution found to the collection orderedRouteTaskCollections.Add(bestSolution.Cast<RouteTask>()); index++; PerformNextCalcuation(geoLocationCollections, index, orderedRouteTaskCollections, statusWindow, resultSubject); }); //Stop the algorithm on cancel search and return CancelCommand.Subscribe(_ => { //since the algorithm was cancelled, prevent the time from stopping the algorithm stopAlgorithmDisposable.Dispose(); calculator.StopSearch(); statusWindow.Close(); }); }