public async Task <PlanElementCandidate> GenerateAsync(DecisionArray decisionArray, Plan plan, int whichMeal, PlanElementIteratorParams iterParams) { var test = true; //choose type based on preferences var type = (plan.PlanForm.FoodPreference == FoodPreference.OnlyRestaurant || (plan.PlanForm.FoodPreference == FoodPreference.Mixed && whichMeal == 2)) ? GooglePlaceTypeCategory.Restaurant : GooglePlaceTypeCategory.Food; var candidates = new List <PlanElementCandidate>(); var decisionRows = new List <DecisionRow>(); var googleNearbyFoodInput = _googlePlaceNearbySearchInputFactory.Create(iterParams.CurrentLocation, type); var nearbyFoodResults = await _googlePlaceNearbySearchApiClient.GetAsync(googleNearbyFoodInput); int counter = 1; foreach (var nr in nearbyFoodResults.results) { var details = await _googlePlaceDetailsApiClient.GetAsync(_googlePlaceDetailsInputFactory.CreateAllUseful(nr.place_id)); if (details.IsOk) { var candidate = new PlanElementCandidate(details.Result.name, details.Result.place_id, details.Result.formatted_address, details.Result.geometry.location, details.Result.opening_hours, details.Result.types, details.Result.rating, details.Result.price_level, details.Result.user_ratings_total); if (test) { return(candidate); } candidates.Add(candidate); } ++counter; if (counter > 10 && candidates.Count > 5) { break; } } var iter = 1; foreach (var candidate in candidates) { decisionRows.Add(_decisionRowFactory.Create(candidate, iter, iterParams.CurrentLocation)); ++iter; } var minVector = DecisionArray.GetMinVector(decisionRows); var maxVector = DecisionArray.GetMaxVector(decisionRows); foreach (var decisionRow in decisionRows) { decisionRow.NormalizedScore = _sawMethod.CalculateNormalizedScore(SawNormalizationMethod.LinearFirstType, decisionArray.WeightVector, decisionRow.DecisionValues, minVector, maxVector); } var result = decisionRows.OrderByDescending(x => x.NormalizedScore).FirstOrDefault(x => x.Candidate.IsOpen(iterParams.CurrentDateTime)); if (result == null) { throw new UserFriendlyException($"Nie udało się znaleźć żadnego miejsca, gdzie można zjeść w pobliżu o godz: {iterParams.CurrentDateTime}"); } return(result.Candidate); }
public async Task <IList <int> > Optimize(DecisionArray decisionArray, Plan plan, int?elementsCount = null) { if (!elementsCount.HasValue) { elementsCount = decisionArray.DecisionRows.Count; } var waypoints = decisionArray.DecisionRows.Take(elementsCount.Value) .Select(x => x.Candidate.Location).ToList(); var startSeconds = GooglePlaceCalculator.ConvertToUnixTimestamp(plan.PlanForm.StartDateTime); GoogleTravelMode mode; if (plan.PlanForm.PreferedTravelModes.Contains(GoogleTravelMode.Driving)) { mode = GoogleTravelMode.Driving; } else if (plan.PlanForm.PreferedTravelModes.Contains(GoogleTravelMode.Walking)) { mode = GoogleTravelMode.Walking; } else if (plan.PlanForm.PreferedTravelModes.Contains(GoogleTravelMode.Bicycling)) { mode = GoogleTravelMode.Bicycling; } else { mode = GoogleTravelMode.Transit; } var optimizeApiInput = _googleDirectionsInputFactory.CreateOptimizedWaypoints(plan.StartLocation, plan.StartLocation, mode, waypoints, startSeconds); var result = await _googleDirectionsApiClient.GetAsync(optimizeApiInput); if (result.IsOk) { var optimizedOrder = result.routes.First(); return(optimizedOrder.waypoint_order); } else { throw new UserFriendlyException($"Nie udało się zoptymalizować kolejności elementów planu (Problem komiwojażera)!"); } }
public async Task <Plan> GenerateAsync(PlanForm planForm) { // 1. Create decision array var DecisionArray = new DecisionArray(); // 2. Create plan object. Validate desitnation and accomodation. var destinationInfo = await _googlePlaceDetailsApiClient.GetAsync(_googlePlaceDetailsInputFactory.CreateAllUseful(planForm.PlaceId)); var plan = new Plan(destinationInfo.Result.name, destinationInfo.Result.geometry.location.lat, destinationInfo.Result.geometry.location.lng, (decimal?)destinationInfo.Result.rating, (decimal?)destinationInfo.Result.user_ratings_total, destinationInfo.Result.formatted_address); if (planForm.HasAccomodationBooked) { var accomodationInfo = await _googlePlaceDetailsApiClient.GetAsync(_googlePlaceDetailsInputFactory.CreateAllUseful(planForm.AccomodationId)); plan.PlanAccomodation = new PlanAccomodation(accomodationInfo.Result.geometry.location.lat, accomodationInfo.Result.geometry.location.lng, planForm.AccomodationId, accomodationInfo.Result.name, accomodationInfo.Result.formatted_address, (decimal?)accomodationInfo.Result.rating, (decimal?)accomodationInfo.Result.user_ratings_total); var distance = CalculateDistance(plan.Latitude, plan.Longitude, plan.PlanAccomodation.Lat, plan.PlanAccomodation.Lng); if (distance > MaximumDistanceToAccomodation) //more than 15km { throw new UserFriendlyException($"Odległość między celem podróży a miejscem zakwaterowania nie może być większa nić {(int)(MaximumDistanceToAccomodation/1000)} km"); } } plan.PlanForm = planForm; plan.Assumptions = new PlanAssumptions(planForm); // 3. Generate weight vector based on user preferences DecisionArray.WeightVector = _weightVectorProvider.Generate(planForm); plan.PlanFormWeightVector = PlanFormWeightVector.Create(DecisionArray.WeightVector); // 4. Get plan candidates var candidates = await _planElementCandidateFactory.GetCandidates(plan, DecisionArray.WeightVector); //5. Create decision row with values based on candidates int init = 1; foreach (var candidate in candidates) { DecisionArray.DecisionRows.Add(_decisionRowFactory.Create(candidate, init, plan.StartLocation)); ++init; } // 6. SCORE FUNCTION -> SAW Normalization (3 types - chosen first) and then calculate Score var minVector = DecisionArray.GetMinVector(); var maxVector = DecisionArray.GetMaxVector(); foreach (var decisionRow in DecisionArray.DecisionRows) { decisionRow.NormalizedScore = _sawMethod.CalculateNormalizedScore(SawNormalizationMethod.LinearFirstType, DecisionArray.WeightVector, decisionRow.DecisionValues, minVector, maxVector); } // 7. Clasification DecisionArray.DecisionRows = DecisionArray.DecisionRows.OrderByDescending(x => x.NormalizedScore).ToList(); int newPos = 1; foreach (var row in DecisionArray.DecisionRows) { row.ScorePosition = newPos; ++newPos; } // 8. Create Plan based on decision rows and optimize routes with travel salesman problem plan.Elements = await _planElementsProvider.GenerateAsync(DecisionArray, plan); return(plan); }
public async Task <IList <PlanElement> > GenerateAsync(DecisionArray decisionArray, Plan plan) { var elements = new List <PlanElement>(); var optimizedElementsOrder = await _optimizePlanElementsOrder.Optimize(decisionArray, plan, plan.Assumptions.AssumedNumberOfElement); //travel salesman problem optimization var optimizedPosition = 1; foreach (var optimizedIter in optimizedElementsOrder) { decisionArray.DecisionRows[optimizedIter].OptimizedPosition = optimizedPosition; ++optimizedPosition; } //Sort by optimized position decisionArray.DecisionRows = decisionArray.DecisionRows.OrderBy(x => x.OptimizedPosition).ToList(); var iterParams = new PlanElementIteratorParams(plan.StartLocation, plan.PlanForm.StartDateTime, plan.Assumptions); while (DateTime.Compare(iterParams.CurrentDateTime, plan.PlanForm.EndDateTime) <= 0 || iterParams.OrderNo >= decisionArray.DecisionRows.Count) { try { PlanElement planElement = null; //EATING if (iterParams.IsTimeForMeal()) { var elementEndTime = iterParams.CreateEndDateTime(plan.Assumptions.EatingDuration); var candidate = await _planElementEatingProvider.GenerateAsync(decisionArray, plan, iterParams.WhichMeal, iterParams); iterParams.CheckNextMeal(); planElement = new PlanElement(candidate.PlaceName, candidate.PlaceId, candidate.FormattedAddress, iterParams.CurrentDateTime, elementEndTime, candidate.Location.lat, candidate.Location.lng, iterParams.OrderNo, candidate.Rating, candidate.Price, candidate.Popularity, PlanElementType.Eating); } //SLEEPING else if (iterParams.IsTimeForSleep()) { iterParams.IsSleep = true; var elementEndTime = iterParams.CreateEndDateTime(plan.Assumptions.SleepDuration); if (plan.PlanForm.HasAccomodationBooked) { planElement = new PlanElement(plan.PlanAccomodation.PlaceName, plan.PlanAccomodation.PlaceId, plan.PlanAccomodation.FormattedAddress, iterParams.CurrentDateTime, elementEndTime, plan.PlanAccomodation.Lat, plan.PlanAccomodation.Lng, iterParams.OrderNo, plan.PlanAccomodation.Rating, null, null, PlanElementType.Sleeping); } else { planElement = new PlanElement(plan.PlanForm.PlaceName, plan.PlanForm.PlaceId, String.Empty, iterParams.CurrentDateTime, elementEndTime, plan.Latitude, plan.Longitude, iterParams.OrderNo, plan.Rating, null, null, PlanElementType.Sleeping); } } //OTHER PLAN ELEMENTS else { var foundRow = false; var elementEndTime = iterParams.CreateEndDateTime(plan.Assumptions.PlanElementDuration); while (!foundRow) { if (iterParams.LeftRows.Any(x => x.Candidate.IsOpen(iterParams.CurrentDateTime))) //first check lefted row which was temporary closed { var leftedRow = iterParams.LeftRows.First(x => x.Candidate.IsOpen(iterParams.CurrentDateTime)); if (!leftedRow.Candidate.IsOpen(elementEndTime)) //check close date { elementEndTime = leftedRow.Candidate.GetCloseDateTime(iterParams.CurrentDateTime, elementEndTime); } planElement = PlanElement.Create(leftedRow, iterParams.OrderNo, iterParams.CurrentDateTime, elementEndTime); iterParams.LeftRows.Remove(leftedRow); foundRow = true; } else if (decisionArray.DecisionRows[iterParams.CurrentDecisionRowIndex].Candidate.IsOpen(iterParams.CurrentDateTime)) //decision row is open { if (!decisionArray.DecisionRows[iterParams.CurrentDecisionRowIndex].Candidate.IsOpen(elementEndTime)) //check close date { elementEndTime = decisionArray.DecisionRows[iterParams.CurrentDecisionRowIndex].Candidate.GetCloseDateTime(iterParams.CurrentDateTime, elementEndTime); } planElement = PlanElement.Create(decisionArray.DecisionRows[iterParams.CurrentDecisionRowIndex], iterParams.OrderNo, iterParams.CurrentDateTime, elementEndTime); foundRow = true; iterParams.CurrentDecisionRowIndex += 1; } else //decision row is closed { iterParams.LeftRows.Add(decisionArray.DecisionRows[iterParams.CurrentDecisionRowIndex]); iterParams.CurrentDecisionRowIndex += 1; } } } if (iterParams.CurrentDecisionRowIndex == decisionArray.DecisionRows.Count()) { throw new UserFriendlyException($"Skończyły się dostępni kandydaci"); } if (planElement == null) { continue; } iterParams.NextLocation = Location.Create(planElement.Lat, planElement.Lng); //DIRECTIONS var directionsApiInput = _googleDirectionsInputFactory.Create(iterParams.CurrentLocation, iterParams.NextLocation, iterParams.travelMode, iterParams.CurrentDateTime); var directionsApiResult = await _googleDirectionsApiClient.GetAsync(directionsApiInput); if (directionsApiResult.IsOk) { if (directionsApiResult.routes.First().legs.Any()) { var route = directionsApiResult.routes.First().legs.First(); //only 1 leg if no waypoints var planRoute = new PlanRoute(route.distance.value, route.duration.value, iterParams.travelMode); foreach (var step in route.steps) //steps of route { planRoute.Steps.Add(new PlanRouteStep(step.distance.value, step.duration.value, step.start_location.lat, step.start_location.lng, step.end_location.lat, step.end_location.lng, InterpreteEnums.InterpreteTravelMode(step.travel_mode), step.html_instructions, step.maneuver)); } //update plan element times planElement.UpdateDateTimeWithRouteDuration(planRoute.TimeDuration); planElement.EndingRoute = planRoute; } } elements.Add(planElement); //add to plan list iterParams.SetCurrentLocation(planElement.Lat, planElement.Lng); // update currentLocation in iterParams iterParams.CurrentDateTime = planElement.End; //update current time in iterParams ++iterParams.OrderNo; //increase iter) iterParams.ClearIfEndOfCurrentDay(); } catch (Exception e) { var error = e.InnerException; break; } } return(elements); }