Skip to content

Commit e9d4c5c

Browse files
authored
Merge pull request #12 from DevKor-github/midpoint-subway
Feature/Midpoint-Subway 지하철역 기반 중간지점 계산 로직 구현 완료
2 parents 3cc973f + 2c203ed commit e9d4c5c

10 files changed

Lines changed: 404 additions & 7 deletions

File tree

src/main/java/com/ODG/ODG_back/domain/Midpoint.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,16 @@
1717
public class Midpoint {
1818

1919
@Id
20-
@GeneratedValue(strategy = GenerationType.IDENTITY)
2120
private Long id;
2221

2322
private String name;
2423

25-
private String line;
26-
24+
@Column(precision = 15, scale = 8)
2725
private BigDecimal latitude;
2826

27+
@Column(precision = 15, scale = 8)
2928
private BigDecimal longitude;
3029

31-
@OneToMany(mappedBy = "midpoint")
32-
private List<RecommendedMidpoint> recommendedMidpoints;
33-
3430
@ManyToMany(mappedBy = "midpoints")
3531
private List<Place> places;
3632
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.ODG.ODG_back.domain;
2+
3+
import jakarta.persistence.*;
4+
import lombok.*;
5+
6+
@Entity
7+
@Getter
8+
@Builder
9+
@Setter
10+
@NoArgsConstructor
11+
@AllArgsConstructor
12+
@Table(indexes = { @Index(name = "idx_start_end", columnList = "start,end") })
13+
public class SubwayDurationTime {
14+
15+
@Id
16+
@GeneratedValue(strategy = GenerationType.IDENTITY)
17+
private Long id;
18+
19+
@Column(nullable = false)
20+
Long start;
21+
22+
@Column(nullable = false)
23+
Long end;
24+
25+
int shortestDurationTime; // 최소 소요 시간
26+
int transferCountForShortestDuration; // 최소 소요 시간일 때 환승 횟수
27+
28+
@Column(nullable = false, length = 2000)
29+
String shortestDurationTimePath; // 최소 소요 시간일 때 경로
30+
31+
int durationTimeForMinimumTransfer; // 최소 환승 횟수일 때 소요 시간
32+
int minimumTransferCount; // 최소 환승 횟수
33+
34+
@Column(nullable = false, length = 2000)
35+
String minimumTransferPath; // 최소 환승 횟수일 때 경로
36+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package com.ODG.ODG_back.dto.external;
2+
3+
public class OverpassStationInfoDTO {
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package com.ODG.ODG_back.external.openStreetMap;
2+
3+
public interface OpenStreetMapApiClient {
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package com.ODG.ODG_back.external.openStreetMap;
2+
3+
public class OpenStreetMapApiClientImpl {
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package com.ODG.ODG_back.external.overpass;
2+
3+
public interface OverpassApiClient {
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package com.ODG.ODG_back.external.overpass;
2+
3+
public class OverpassApiClientImpl {
4+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.ODG.ODG_back.repository;
2+
3+
import com.ODG.ODG_back.domain.SubwayDurationTime;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
6+
import java.util.Optional;
7+
8+
public interface SubwayDurationTimeRepository extends JpaRepository<SubwayDurationTime, Long> {
9+
Optional<SubwayDurationTime> findByStartAndEnd(Long start, Long end);
10+
}
Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,147 @@
11
package com.ODG.ODG_back.strategy;
22

3+
import com.ODG.ODG_back.domain.*;
4+
import com.ODG.ODG_back.dto.external.OverpassStationInfoDTO;
35
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;
416
import lombok.RequiredArgsConstructor;
517
import org.springframework.stereotype.Component;
618

719
import java.math.BigDecimal;
20+
import java.util.*;
21+
22+
import static java.util.stream.Collectors.groupingBy;
823

924
@Component("subwayStrategy")
1025
@RequiredArgsConstructor
1126
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; // 지하철역간 이동 시간 저장소
1233

1334
@Override
1435
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();
16132
}
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) {}
17147
}

0 commit comments

Comments
 (0)