Skip to content

Commit 2e9d7f0

Browse files
authored
[release] 진행상황 main 브랜치에 반영
[release] 진행상황 main 브랜치에 반영
2 parents 54c4e8f + 59c9ff5 commit 2e9d7f0

File tree

7 files changed

+382
-29
lines changed

7 files changed

+382
-29
lines changed

src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,14 @@
88

99
@Schema(description = "대중교통 길찾기 응답 DTO")
1010
public record TransportationResponseDto(
11-
List<Step> routes,
11+
List<Route> routes,
1212
PageInfo pageInfo
1313
) {
14+
public record Route(
15+
Integer routeIndex, // 경로 인덱스
16+
List<Step> steps // 해당 경로의 단계들
17+
) {}
18+
1419
public record Step(
1520
DirectionType mode, // 예: START, WALK, SUBWAY, BUS, FINISH
1621
@Nullable List<MoveInfo> moveInfo, // 같은 Step으로 이동한 정류장(Node) 정보 (중간 정류장만)

src/main/java/com/wayble/server/direction/service/TransportationService.java

Lines changed: 192 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public class TransportationService {
3838
private static final int ORIGIN_DESTINATION_WALK_DISTANCE = 1000; // 출발지/도착지에서 정류장까지 도보 연결 가능 거리 (m)
3939
private static final int MAX_NEARBY_NODES = 5; // 출발지/도착지 주변에서 고려할 최대 정류장 수
4040
private static final int MAX_DIJKSTRA_VISITS = 5000; // 다익스트라 알고리즘에서 방문할 수 있는 최대 노드 수 (무한 루프 방지)
41+
private static final int MAX_ROUTES = 5; // 찾을 최대 경로 수
4142

4243
public TransportationResponseDto findRoutes(TransportationRequestDto request){
4344

@@ -54,27 +55,39 @@ public TransportationResponseDto findRoutes(TransportationRequestDto request){
5455
Node start = Node.createNode(-1L, origin.name(), DirectionType.FROM_WAYPOINT ,origin.latitude(), origin.longitude());
5556
Node end = Node.createNode(-2L, destination.name(), DirectionType.TO_WAYPOINT,destination.latitude(), destination.longitude());
5657

57-
// 3. 경로 찾기
58-
List<TransportationResponseDto.Step> steps = findTransportationRoute(start, end);
58+
// 3. 여러 경로 찾기
59+
List<List<TransportationResponseDto.Step>> allRoutes = findMultipleTransportationRoutes(start, end);
5960

6061
// 4. 페이징 처리
6162
int startIndex = (request.cursor() != null) ? request.cursor() : 0;
62-
int pageSize = request.size() != null ? request.size() : steps.size();
63-
int endIndex = Math.min(startIndex + pageSize, steps.size());
64-
boolean hasNext = endIndex < steps.size();
63+
int pageSize = (request.size() != null) ? request.size() : 5; // 기본값 5로 설정
64+
int endIndex = Math.min(startIndex + pageSize, allRoutes.size());
65+
boolean hasNext = endIndex < allRoutes.size();
6566
Integer nextCursor = hasNext ? endIndex : null;
6667
TransportationResponseDto.PageInfo pageInfo = new TransportationResponseDto.PageInfo(nextCursor, hasNext);
6768

6869
// 경로를 찾지 못한 경우 처리
69-
if (steps.isEmpty()) {
70+
if (allRoutes.isEmpty()) {
7071
throw new ApplicationException(PATH_NOT_FOUND);
7172
}
7273

73-
return new TransportationResponseDto(steps, pageInfo);
74+
// 페이징된 경로들을 Route 객체로 변환
75+
List<TransportationResponseDto.Route> routeList = new ArrayList<>();
76+
List<List<TransportationResponseDto.Step>> pagedRoutes = allRoutes.subList(startIndex, endIndex);
77+
for (int i = 0; i < pagedRoutes.size(); i++) {
78+
List<TransportationResponseDto.Step> route = pagedRoutes.get(i);
79+
TransportationResponseDto.Route routeObj = createRoute(route, startIndex + i + 1);
80+
routeList.add(routeObj);
81+
}
82+
83+
return new TransportationResponseDto(routeList, pageInfo);
7484
}
7585

86+
private TransportationResponseDto.Route createRoute(List<TransportationResponseDto.Step> steps, int routeIndex) {
87+
return new TransportationResponseDto.Route(routeIndex, steps);
88+
}
7689

77-
private List<TransportationResponseDto.Step> findTransportationRoute(Node startTmp, Node endTmp){
90+
private List<List<TransportationResponseDto.Step>> findMultipleTransportationRoutes(Node startTmp, Node endTmp){
7891
// 1. 데이터 로드
7992
List<Node> nodes = new ArrayList<>(nodeRepository.findAll());
8093
List<Edge> edges = new ArrayList<>(edgeRepository.findAllWithNodesAndRoute());
@@ -91,13 +104,12 @@ private List<TransportationResponseDto.Step> findTransportationRoute(Node startT
91104
nodes.add(startTmp);
92105
nodes.add(endTmp);
93106

94-
// 4. 그래프 빌드 및 최적 경로 찾기
107+
// 4. 그래프 빌드 및 여러 경로 찾기
95108
TransportationGraphDto graphData = buildGraph(nodes, edges, startTmp, endTmp);
96-
return findOptimalRoute(graphData.graph(), startTmp, endTmp, graphData.weightMap(), nodes, nearestToStart, nearestToEnd);
109+
return findMultipleOptimalRoutes(graphData.graph(), startTmp, endTmp, graphData.weightMap(), nodes, nearestToStart, nearestToEnd);
97110
}
98111

99-
100-
private List<TransportationResponseDto.Step> findOptimalRoute(
112+
private List<List<TransportationResponseDto.Step>> findMultipleOptimalRoutes(
101113
Map<Long, List<Edge>> graph,
102114
Node startTmp,
103115
Node endTmp,
@@ -121,26 +133,179 @@ private List<TransportationResponseDto.Step> findOptimalRoute(
121133
return new ArrayList<>();
122134
}
123135

124-
// 2. 다익스트라 알고리즘으로 최적 경로 찾기
125-
List<TransportationResponseDto.Step> route = runDijkstra(graph, startNode, endNode, weightMap, nodes);
136+
// 2. 여러 경로 찾기
137+
List<List<TransportationResponseDto.Step>> allRoutes = findMultipleRoutes(graph, startNode, endNode, weightMap, nodes);
138+
139+
// 3. 경로 필터링 및 정렬
140+
return filterAndSortRoutes(allRoutes);
141+
}
142+
143+
private List<List<TransportationResponseDto.Step>> findMultipleRoutes(
144+
Map<Long, List<Edge>> graph,
145+
Node start,
146+
Node end,
147+
Map<Pair<Long, Long>, Integer> weightMap,
148+
List<Node> nodes) {
149+
150+
List<List<TransportationResponseDto.Step>> routes = new ArrayList<>();
151+
152+
// 1. 기본 다익스트라로 첫 번째 경로 찾기
153+
List<TransportationResponseDto.Step> firstRoute = runDijkstra(graph, start, end, weightMap, nodes);
154+
if (!firstRoute.isEmpty()) {
155+
routes.add(firstRoute);
156+
}
157+
158+
// 2. 효율적인 다중 경로 찾기 - 한 번의 탐색으로 여러 경로 생성
159+
if (!firstRoute.isEmpty()) {
160+
List<List<TransportationResponseDto.Step>> alternativeRoutes = findAlternativeRoutesEfficiently(
161+
graph, start, end, weightMap, nodes, firstRoute
162+
);
163+
routes.addAll(alternativeRoutes);
164+
}
126165

127-
if (!route.isEmpty()) {
128-
// 3. 대중교통 포함 여부 확인
129-
boolean hasPublicTransport = route.stream()
130-
.anyMatch(step -> step.mode() == DirectionType.BUS || step.mode() == DirectionType.SUBWAY);
166+
return routes;
167+
}
168+
169+
private List<List<TransportationResponseDto.Step>> findAlternativeRoutesEfficiently(
170+
Map<Long, List<Edge>> graph,
171+
Node start,
172+
Node end,
173+
Map<Pair<Long, Long>, Integer> weightMap,
174+
List<Node> nodes,
175+
List<TransportationResponseDto.Step> firstRoute) {
176+
177+
List<List<TransportationResponseDto.Step>> alternativeRoutes = new ArrayList<>();
178+
179+
// 첫 번째 경로에서 실제 사용된 엣지들을 추출
180+
Set<Pair<Long, Long>> usedEdges = extractActualEdgesFromRoute(firstRoute, graph);
181+
182+
// 최대 4개의 추가 경로 찾기
183+
for (int i = 0; i < 4 && alternativeRoutes.size() < MAX_ROUTES - 1; i++) {
184+
// 실제 사용된 엣지들에만 패널티를 적용한 가중치 맵 생성
185+
Map<Pair<Long, Long>, Integer> penalizedWeightMap = createActualEdgePenalizedWeightMap(weightMap, usedEdges, i + 1);
186+
187+
// 다익스트라로 새로운 경로 찾기
188+
List<TransportationResponseDto.Step> newRoute = runDijkstra(graph, start, end, penalizedWeightMap, nodes);
131189

132-
if (!hasPublicTransport) {
133-
return new ArrayList<>();
190+
if (newRoute.isEmpty()) {
191+
break;
134192
}
135193

136-
// 4. 환승 횟수 검증 (4회 이상 제외)
137-
int transferCount = calculateTransferCount(route);
138-
if (transferCount >= 4) {
139-
return new ArrayList<>();
194+
// 첫 번째 경로와 동일한지 확인
195+
if (areRoutesIdentical(newRoute, firstRoute)) {
196+
continue;
140197
}
198+
199+
// 새로운 경로에서 사용된 엣지들도 추가
200+
Set<Pair<Long, Long>> newUsedEdges = extractActualEdgesFromRoute(newRoute, graph);
201+
usedEdges.addAll(newUsedEdges);
202+
203+
alternativeRoutes.add(newRoute);
141204
}
142205

143-
return route;
206+
return alternativeRoutes;
207+
}
208+
209+
210+
211+
212+
213+
private Set<Pair<Long, Long>> extractActualEdgesFromRoute(List<TransportationResponseDto.Step> route, Map<Long, List<Edge>> graph) {
214+
Set<Pair<Long, Long>> usedEdges = new HashSet<>();
215+
216+
for (TransportationResponseDto.Step step : route) {
217+
String fromName = step.from();
218+
String toName = step.to();
219+
220+
for (Map.Entry<Long, List<Edge>> entry : graph.entrySet()) {
221+
Long nodeId = entry.getKey();
222+
List<Edge> edges = entry.getValue();
223+
224+
for (Edge edge : edges) {
225+
Node fromNode = edge.getStartNode();
226+
Node toNode = edge.getEndNode();
227+
228+
if ((fromNode.getStationName().equals(fromName) && toNode.getStationName().equals(toName)) ||
229+
(fromNode.getStationName().equals(toName) && toNode.getStationName().equals(fromName))) {
230+
usedEdges.add(Pair.of(fromNode.getId(), toNode.getId()));
231+
usedEdges.add(Pair.of(toNode.getId(), fromNode.getId()));
232+
}
233+
}
234+
}
235+
}
236+
237+
return usedEdges;
238+
}
239+
240+
private Map<Pair<Long, Long>, Integer> createActualEdgePenalizedWeightMap(Map<Pair<Long, Long>, Integer> originalWeightMap, Set<Pair<Long, Long>> usedEdges, int routeIndex) {
241+
Map<Pair<Long, Long>, Integer> penalizedWeightMap = new HashMap<>();
242+
243+
for (Map.Entry<Pair<Long, Long>, Integer> entry : originalWeightMap.entrySet()) {
244+
Pair<Long, Long> edge = entry.getKey();
245+
int weight = entry.getValue();
246+
247+
if (usedEdges.contains(edge)) {
248+
int penalty = routeIndex * 100000;
249+
penalizedWeightMap.put(edge, weight + penalty);
250+
} else {
251+
penalizedWeightMap.put(edge, weight);
252+
}
253+
}
254+
255+
return penalizedWeightMap;
256+
}
257+
258+
private boolean areRoutesIdentical(List<TransportationResponseDto.Step> route1, List<TransportationResponseDto.Step> route2) {
259+
// 두 경로가 완전히 동일한지 확인
260+
if (route1.size() != route2.size()) {
261+
return false;
262+
}
263+
264+
for (int i = 0; i < route1.size(); i++) {
265+
TransportationResponseDto.Step step1 = route1.get(i);
266+
TransportationResponseDto.Step step2 = route2.get(i);
267+
268+
if (step1.mode() != step2.mode() ||
269+
!Objects.equals(step1.from(), step2.from()) ||
270+
!Objects.equals(step1.to(), step2.to()) ||
271+
!Objects.equals(step1.routeName(), step2.routeName())) {
272+
return false;
273+
}
274+
}
275+
276+
return true;
277+
}
278+
279+
private List<List<TransportationResponseDto.Step>> filterAndSortRoutes(List<List<TransportationResponseDto.Step>> routes) {
280+
return routes.stream()
281+
.filter(route -> {
282+
// 대중교통 포함 여부 확인
283+
boolean hasPublicTransport = route.stream()
284+
.anyMatch(step -> step.mode() == DirectionType.BUS || step.mode() == DirectionType.SUBWAY);
285+
286+
if (!hasPublicTransport) {
287+
return false;
288+
}
289+
290+
// 환승 횟수 검증 (4회 이상 제외)
291+
int transferCount = calculateTransferCount(route);
292+
return transferCount < 4;
293+
})
294+
.sorted(Comparator
295+
.<List<TransportationResponseDto.Step>>comparingInt(this::calculateTransferCount)
296+
.thenComparingInt(this::calculateWalkDistance))
297+
.limit(MAX_ROUTES)
298+
.collect(Collectors.toList());
299+
}
300+
301+
private int calculateWalkDistance(List<TransportationResponseDto.Step> route) {
302+
return route.stream()
303+
.filter(step -> step.mode() == DirectionType.WALK)
304+
.mapToInt(step -> {
305+
// 간단한 도보 거리 추정 (실제로는 정확한 거리 계산 필요)
306+
return 500; // 기본값
307+
})
308+
.sum();
144309
}
145310

146311
private TransportationGraphDto buildGraph(List<Node> nodes, List<Edge> edges, Node startTmp, Node endTmp) {
@@ -457,9 +622,9 @@ private List<TransportationResponseDto.Step> mergeConsecutiveRoutes(List<Edge> p
457622
return new ArrayList<>();
458623
}
459624
}
460-
} catch (Exception e) {
625+
} catch (Exception e) {
461626
log.info("버스 정보 조회 실패: {}", e.getMessage());
462-
}
627+
}
463628
} else if (currentType == DirectionType.SUBWAY) {
464629
try {
465630
if (currentEdge.getStartNode() != null) {

src/main/java/com/wayble/server/explore/controller/WaybleZoneSearchController.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@ public CommonResponse<List<WaybleZoneDistrictResponseDto>> findMostLikesWaybleZo
6767
));
6868
}
6969

70+
@GetMapping("/validate")
71+
public CommonResponse<WaybleZoneSearchResponseDto> findIsValidWaybleZone(
72+
@Valid @ModelAttribute WaybleZoneSearchConditionDto conditionDto
73+
)
74+
{
75+
return CommonResponse.success(waybleZoneSearchService.isValidWaybleZone(conditionDto));
76+
}
77+
7078
@PostMapping("")
7179
public CommonResponse<String> registerDocumentFromDto(@RequestBody WaybleZoneRegisterDto registerDto) {
7280
waybleZoneDocumentService.saveDocumentFromDto(registerDto);

src/main/java/com/wayble/server/explore/dto/search/request/WaybleZoneSearchConditionDto.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ public record WaybleZoneSearchConditionDto(
1919
@NotNull(message = "경도 입력은 필수입니다.")
2020
Double longitude,
2121

22-
@DecimalMin(value = "0.1", message = "검색 반경은 100미터 이상이어야 합니다.")
2322
Double radiusKm,
2423

2524
@Size(min = 2, message = "zoneName은 최소 2글자 이상이어야 합니다.")

0 commit comments

Comments
 (0)