|
1 | 1 | package com.ODG.ODG_back.strategy; |
2 | 2 |
|
| 3 | +import com.ODG.ODG_back.domain.*; |
| 4 | +import com.ODG.ODG_back.dto.external.OverpassStationInfoDTO; |
3 | 5 | import com.ODG.ODG_back.dto.midpoint.response.MidpointResponseDto; |
| 6 | +import com.ODG.ODG_back.exception.ErrorCode; |
| 7 | +import com.ODG.ODG_back.exception.custom.NotFoundException; |
| 8 | +import com.ODG.ODG_back.external.google.GoogleMatrixApiClient; |
| 9 | +import com.ODG.ODG_back.external.openStreetMap.OpenStreetMapApiClient; |
| 10 | +import com.ODG.ODG_back.external.overpass.OverpassApiClient; |
| 11 | +import com.ODG.ODG_back.mapper.MidpointMapper; |
| 12 | +import com.ODG.ODG_back.repository.MeetingRepository; |
| 13 | +import com.ODG.ODG_back.repository.MidpointRepository; |
| 14 | +import com.ODG.ODG_back.repository.RecommendedMidpointRepository; |
| 15 | +import com.ODG.ODG_back.repository.SubwayDurationTimeRepository; |
4 | 16 | import lombok.RequiredArgsConstructor; |
5 | 17 | import org.springframework.stereotype.Component; |
6 | 18 |
|
7 | 19 | import java.math.BigDecimal; |
| 20 | +import java.util.*; |
| 21 | + |
| 22 | +import static java.util.stream.Collectors.groupingBy; |
8 | 23 |
|
9 | 24 | @Component("subwayStrategy") |
10 | 25 | @RequiredArgsConstructor |
11 | 26 | public class SubwayMidpointStrategy implements MidpointStrategy{ |
| 27 | + private final int MAX_DISTANCE = 2000; // 최대 검색 거리 (미터 단위) |
| 28 | + private final MeetingRepository meetingRepository; |
| 29 | + private final RecommendedMidpointRepository recommendedMidpointRepository; |
| 30 | + private final MidpointRepository midpointRepository; |
| 31 | + private final MidpointMapper midpointMapper; |
| 32 | + private final SubwayDurationTimeRepository timeRepository; // 지하철역간 이동 시간 저장소 |
12 | 33 |
|
13 | 34 | @Override |
14 | 35 | public MidpointResponseDto calculateMidpoints(String inviteCode) { |
15 | | - return new MidpointResponseDto(1L, "dummy", BigDecimal.ZERO, BigDecimal.ZERO); |
| 36 | + |
| 37 | + Meeting meeting = meetingRepository.findByInviteCode(inviteCode).orElseThrow( |
| 38 | + () -> new NotFoundException(ErrorCode.MEETING_NOT_FOUND) |
| 39 | + ); |
| 40 | + // 출발지 = 참가자 위치 |
| 41 | + List<Participant> participants = meeting.getParticipants(); |
| 42 | + // 목적지 = 모든 지하철역 |
| 43 | + List<Midpoint> allMidpoints = midpointRepository.findAll(); |
| 44 | + |
| 45 | + // 각 participant별로 allMidpoints에서 가장 가까운 지하철역을 찾아 리스트에 추가 |
| 46 | + |
| 47 | + List<Midpoint> nearestMidpoints = new ArrayList<>(); |
| 48 | + for (Participant participant : participants) { |
| 49 | + Midpoint nearest = allMidpoints.stream() |
| 50 | + .min(Comparator.comparing(midpoint -> |
| 51 | + participant.getLatitude().subtract(midpoint.getLatitude()).abs() |
| 52 | + .add(participant.getLongitude().subtract(midpoint.getLongitude()).abs()) |
| 53 | + )) |
| 54 | + .orElseThrow(RuntimeException::new); |
| 55 | + nearestMidpoints.add(nearest); |
| 56 | + } |
| 57 | + |
| 58 | + List<MidpointScore> midpointScores = calculateMidpointScores(nearestMidpoints, allMidpoints, participants.size()); |
| 59 | + |
| 60 | + MidpointScore best = midpointScores.stream() |
| 61 | + .min(Comparator.comparingDouble(MidpointScore::totalDeviation)) |
| 62 | + .orElseThrow(RuntimeException::new); |
| 63 | + |
| 64 | + saveRecommendedMidpoint(best, meeting); |
| 65 | + return midpointMapper.toDto(best.midpoint()); |
| 66 | + } |
| 67 | + |
| 68 | + // 각 참가자별 가장 가까운 역(nearbyStationsForEachParticipant)과 모든 Midpoint의 역 정보를 기반으로 |
| 69 | + // 각 Midpoint까지의 소요 시간 합(midPointScore)을 계산하여 반환하는 메서드 |
| 70 | + public List<MidpointScore> calculateMidpointScores( |
| 71 | + List<Midpoint> nearbyStationsForEachParticipant, |
| 72 | + List<Midpoint> allMidpoints, |
| 73 | + int numberOfParticipants) { |
| 74 | + |
| 75 | + List<MidpointScore> scores = new ArrayList<>(); |
| 76 | + |
| 77 | + for (Midpoint midpoint : allMidpoints) { |
| 78 | + var score = calculateMidpointScore(nearbyStationsForEachParticipant, numberOfParticipants, midpoint); |
| 79 | + |
| 80 | + if(score != null) { |
| 81 | + scores.add(score); |
| 82 | + } |
| 83 | + } |
| 84 | + |
| 85 | + return scores; |
| 86 | + } |
| 87 | + |
| 88 | + public MidpointScore calculateMidpointScore(List<Midpoint> nearbyStationsForEachParticipant, int numberOfParticipants, Midpoint midpoint) { |
| 89 | + int totalDuration = 0; |
| 90 | + |
| 91 | + try{ |
| 92 | + |
| 93 | + for (Midpoint startStation : nearbyStationsForEachParticipant) { |
| 94 | + // 시작역과 목적지역 이름이 모두 존재해야 함 |
| 95 | + |
| 96 | + // SubwayDurationTimeRepository에서 소요 시간 조회 |
| 97 | + SubwayDurationTime durationOpt = getDurationInfo(startStation, midpoint); |
| 98 | + totalDuration += durationOpt.getShortestDurationTime(); |
| 99 | + |
| 100 | + return new MidpointScore(midpoint, (double) totalDuration / numberOfParticipants, totalDuration); |
| 101 | + } |
| 102 | + } |
| 103 | + catch (NotFoundException e) { |
| 104 | + // 해당 midpoint에 대한 정보가 없으면 유효하지 않은 것으로 간주 |
| 105 | + } |
| 106 | + |
| 107 | + return null; |
| 108 | + } |
| 109 | + |
| 110 | + public SubwayDurationTime getDurationInfo( |
| 111 | + Midpoint start, |
| 112 | + Midpoint end) { |
| 113 | + // 시작역과 목적지역 이름이 모두 존재해야 함 |
| 114 | + |
| 115 | + if(Objects.equals(start.getId(), end.getId())){ |
| 116 | + new SubwayDurationTime(); |
| 117 | + return SubwayDurationTime.builder() |
| 118 | + .start(start.getId()) |
| 119 | + .end(end.getId()) |
| 120 | + .shortestDurationTime(0) |
| 121 | + .transferCountForShortestDuration(0) |
| 122 | + .build(); |
| 123 | + } |
| 124 | + Optional<SubwayDurationTime> durationTime = timeRepository.findByStartAndEnd( |
| 125 | + start.getId(), end.getId()); |
| 126 | + |
| 127 | + if (durationTime.isEmpty()) { |
| 128 | + throw new NotFoundException(ErrorCode.INTERNAL_SERVER_ERROR); |
| 129 | + } |
| 130 | + |
| 131 | + return durationTime.get(); |
16 | 132 | } |
| 133 | + |
| 134 | + private void saveRecommendedMidpoint(MidpointScore best, Meeting meeting) { |
| 135 | + RecommendedMidpoint recommended = new RecommendedMidpoint( |
| 136 | + null, |
| 137 | + best.avg(), |
| 138 | + 1, |
| 139 | + java.time.LocalDateTime.now(), |
| 140 | + meeting, |
| 141 | + best.midpoint() |
| 142 | + ); |
| 143 | + recommendedMidpointRepository.save(recommended); |
| 144 | + } |
| 145 | + |
| 146 | + public record MidpointScore(Midpoint midpoint, double avg, double totalDeviation) {} |
17 | 147 | } |
0 commit comments