From bad88223eab52d2926bf0c4aa218dcbba0e59733 Mon Sep 17 00:00:00 2001 From: hyoin Date: Thu, 21 Aug 2025 00:13:44 +0900 Subject: [PATCH 1/9] =?UTF-8?q?[fix]=20=EB=B2=84=EC=8A=A4=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=B6=9C=EB=A0=A5=EC=8B=9C=20=EA=B3=B5=EA=B3=B5=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20api=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../opendata/dto/OpenDataResponse.java | 5 +- .../opendata/dto/StationSearchResponse.java | 1 + .../direction/service/BusInfoService.java | 89 +++++++++++-------- .../service/TransportationService.java | 23 +++-- 4 files changed, 73 insertions(+), 45 deletions(-) diff --git a/src/main/java/com/wayble/server/direction/external/opendata/dto/OpenDataResponse.java b/src/main/java/com/wayble/server/direction/external/opendata/dto/OpenDataResponse.java index 22d2907..63963ae 100644 --- a/src/main/java/com/wayble/server/direction/external/opendata/dto/OpenDataResponse.java +++ b/src/main/java/com/wayble/server/direction/external/opendata/dto/OpenDataResponse.java @@ -34,6 +34,9 @@ public record Item( @JsonProperty("busType1") String busType1, @JsonProperty("busType2") String busType2, @JsonProperty("term") String term, - @JsonProperty("busRouteId") String busRouteId + @JsonProperty("busRouteId") String busRouteId, + @JsonProperty("rtNm") String rtNm, + @JsonProperty("stNm") String stNm, + @JsonProperty("arsId") String arsId ) {} } \ No newline at end of file diff --git a/src/main/java/com/wayble/server/direction/external/opendata/dto/StationSearchResponse.java b/src/main/java/com/wayble/server/direction/external/opendata/dto/StationSearchResponse.java index 51143ac..bb9aa5d 100644 --- a/src/main/java/com/wayble/server/direction/external/opendata/dto/StationSearchResponse.java +++ b/src/main/java/com/wayble/server/direction/external/opendata/dto/StationSearchResponse.java @@ -13,6 +13,7 @@ public record StationSearchMsgBody( @JsonIgnoreProperties(ignoreUnknown = true) public record StationItem( + String arsId, String stId, String stNm, String tmX, diff --git a/src/main/java/com/wayble/server/direction/service/BusInfoService.java b/src/main/java/com/wayble/server/direction/service/BusInfoService.java index 669abd4..3ee715b 100644 --- a/src/main/java/com/wayble/server/direction/service/BusInfoService.java +++ b/src/main/java/com/wayble/server/direction/service/BusInfoService.java @@ -29,11 +29,7 @@ public class BusInfoService { private final RouteRepository routeRepository; public TransportationResponseDto.BusInfo getBusInfo(String stationName, Long busId, Double x, Double y) { - // 나중에 서비스키 문제 해결되면 이 함수 호출 제거 - return createDummyBusInfo(stationName, busId, x, y); - // 실제 API 호출 코드 (현재 주석 처리) - /* List isLowFloor = new ArrayList<>(); Integer dispatchInterval = null; @@ -55,18 +51,18 @@ public TransportationResponseDto.BusInfo getBusInfo(String stationName, Long bus // 2. 여러 정류소가 나올 때, 가장 가까운 정류소 찾기 StationSearchResponse.StationItem closestStation = findClosestStation( - stationSearchResponse.msgBody().itemList(), x, y); + stationSearchResponse.msgBody().itemList(), x, y, busId); if (closestStation == null) { log.warn("가장 가까운 정류소를 찾을 수 없습니다: {}", stationName); return new TransportationResponseDto.BusInfo(isShuttleBus, new ArrayList<>(), null); } - + // 3. 정류소 ID로 버스 도착 정보 조회 - OpenDataResponse openDataResponse = fetchArrivals(Long.parseLong(closestStation.stId()), busId); + OpenDataResponse openDataResponse = fetchArrivals(closestStation.arsId(), busId); if (openDataResponse == null || openDataResponse.msgBody() == null || openDataResponse.msgBody().itemList() == null) { - log.warn("버스 도착 정보를 찾을 수 없습니다: {}", closestStation.stId()); + log.warn("정류소 {}의 버스 도착 정보를 찾을 수 없습니다: {}", stationName, closestStation.arsId()); return new TransportationResponseDto.BusInfo(isShuttleBus, new ArrayList<>(), null); } @@ -89,47 +85,23 @@ public TransportationResponseDto.BusInfo getBusInfo(String stationName, Long bus count++; } - + } catch (Exception e) { log.error("버스 정보 조회 중 오류 발생: {}", e.getMessage()); return new TransportationResponseDto.BusInfo(isShuttleBus, new ArrayList<>(), null); } - return new TransportationResponseDto.BusInfo(isShuttleBus, isLowFloor, dispatchInterval); - */ - - } - - // 나중에 이 함수 제거 - private TransportationResponseDto.BusInfo createDummyBusInfo(String stationName, Long busId, Double x, Double y) { - log.info("🎭 더미 BusInfo 생성 - stationName: {}, busId: {}, x: {}, y: {}", stationName, busId, x, y); - - // 셔틀버스 여부 확인 (기존 로직 유지) - boolean isShuttleBus = false; - if (busId != null) { - var route = routeRepository.findById(busId); - isShuttleBus = route.isPresent() && route.get().getRouteName().contains("마포"); - } - - // 랜덤 더미 데이터 생성 - List isLowFloor = new ArrayList<>(); - isLowFloor.add(Math.random() < 0.7); - isLowFloor.add(Math.random() < 0.5); - - Integer dispatchInterval = (int) (Math.random() * 15) + 1; - - return new TransportationResponseDto.BusInfo(isShuttleBus, isLowFloor, dispatchInterval); } - private OpenDataResponse fetchArrivals(Long stationId, Long busId) { + private OpenDataResponse fetchArrivals(String stationId, Long busId) { try { String serviceKey = openDataProperties.encodedKey(); String uri = openDataProperties.baseUrl() + openDataProperties.endpoints().arrivals() + "?serviceKey=" + serviceKey + - "&stId=" + stationId + + "&arsId=" + stationId + "&resultType=json"; HttpRequest request = HttpRequest.newBuilder() @@ -147,8 +119,17 @@ private OpenDataResponse fetchArrivals(Long stationId, Long busId) { if (busId != null && originalResponse != null && originalResponse.msgBody() != null && originalResponse.msgBody().itemList() != null) { + // busId로 route 정보 조회 + var route = routeRepository.findById(busId); + String projectRouteName = route.isPresent() ? route.get().getRouteName() : null; + List filteredItems = originalResponse.msgBody().itemList().stream() - .filter(item -> busId.toString().equals(item.busRouteId())) + .filter(item -> { + if (projectRouteName == null || item.rtNm() == null) { + return false; + } + return projectRouteName.contains(item.rtNm()) || item.rtNm().contains(projectRouteName); + }) .collect(Collectors.toList()); return new OpenDataResponse( @@ -193,12 +174,40 @@ private StationSearchResponse fetchStationByName(String stationName) { } } - private StationSearchResponse.StationItem findClosestStation(List stations, Double x, Double y) { + private StationSearchResponse.StationItem findClosestStation(List stations, Double x, Double y, Long routeId) { if (stations == null || stations.isEmpty()) { log.warn("❌ 정류소 목록이 비어있음"); return null; } + // routeId가 있으면 해당 버스가 운행되는 정류소를 우선적으로 찾기 + if (routeId != null) { + var route = routeRepository.findById(routeId); + String projectRouteName = route.isPresent() ? route.get().getRouteName() : null; + + if (projectRouteName != null) { + for (StationSearchResponse.StationItem station : stations) { + try { + // 각 정류소에서 버스 정보 조회 + OpenDataResponse busResponse = fetchArrivals(station.arsId(), null); + if (busResponse != null && busResponse.msgBody() != null && busResponse.msgBody().itemList() != null) { + for (OpenDataResponse.Item item : busResponse.msgBody().itemList()) { + if (item.rtNm() != null && + (projectRouteName.contains(item.rtNm()) || item.rtNm().contains(projectRouteName))) { + + return station; + } + } + } + } catch (Exception e) { + log.warn("정류소 {}에서 버스 정보 조회 실패: {}", station.stNm(), e.getMessage()); + continue; + } + } + } + } + + // 해당 버스가 운행되는 정류소가 없으면 기존 로직으로 가장 가까운 정류소 선택 StationSearchResponse.StationItem closestStation = null; double minDistance = Double.MAX_VALUE; @@ -213,6 +222,12 @@ private StationSearchResponse.StationItem findClosestStation(List> findMultipleOptimalRoutes( List> allRoutes = findMultipleRoutes(graph, startNode, endNode, weightMap, nodes); // 3. 경로 필터링 및 정렬 - return filterAndSortRoutes(allRoutes); + List> result = filterAndSortRoutes(allRoutes); + return result; } private List> findMultipleRoutes( @@ -231,7 +232,7 @@ private List> findAlternativeRoutesEfficien // 새로운 경로에서 사용된 엣지들도 추가 Set> newUsedEdges = extractActualEdgesFromRoute(newRoute, graph); usedEdges.addAll(newUsedEdges); - + alternativeRoutes.add(newRoute); } @@ -309,7 +310,7 @@ private boolean areRoutesIdentical(List route1, } private List> filterAndSortRoutes(List> routes) { - return routes.stream() + List> filteredRoutes = routes.stream() .filter(route -> { // 대중교통 포함 여부 확인 boolean hasPublicTransport = route.stream() @@ -328,6 +329,8 @@ private List> filterAndSortRoutes(List route) { @@ -582,6 +585,9 @@ private List runDijkstra(Map> g List pathEdges = new ArrayList<>(); Node current = end; Set backtrackVisited = new HashSet<>(); + + long requestId = System.currentTimeMillis(); + while (current != null && !current.equals(start)) { if (backtrackVisited.contains(current.getId())) break; @@ -594,10 +600,10 @@ private List runDijkstra(Map> g current = prevNode.get(current.getId()); } - return mergeConsecutiveRoutes(pathEdges); + return mergeConsecutiveRoutes(pathEdges, requestId); } - private List mergeConsecutiveRoutes(List pathEdges) { + private List mergeConsecutiveRoutes(List pathEdges, long requestId) { List mergedSteps = new ArrayList<>(); if (pathEdges.isEmpty()) { @@ -637,6 +643,7 @@ private List mergeConsecutiveRoutes(List p // 2. 노드명 및 기본 정보 설정 String fromName = getNodeName(currentEdge.getStartNode()); String toName = getNodeName(pathEdges.get(j - 1).getEndNode()); + if (currentType == DirectionType.WALK) { int walkDistance = 0; // 미터 단위 @@ -660,7 +667,6 @@ private List mergeConsecutiveRoutes(List p // 3. 교통수단 상세 정보 (moveInfo) 설정 List moveInfoList = createMoveInfoList(pathEdges, i, j); - String routeName = getRouteName(pathEdges, i, j); // busInfo / subwayInfo 설정 TransportationResponseDto.BusInfo busInfo = null; TransportationResponseDto.SubwayInfo subwayInfo = null; @@ -668,8 +674,9 @@ private List mergeConsecutiveRoutes(List p if (currentType == DirectionType.BUS) { try { if (currentEdge.getStartNode() != null && currentEdge.getRoute() != null) { + busInfo = busInfoService.getBusInfo( - currentEdge.getStartNode().getStationName(), + fromName, currentEdge.getRoute().getRouteId(), currentEdge.getStartNode().getLatitude(), currentEdge.getStartNode().getLongitude() @@ -715,6 +722,8 @@ private List mergeConsecutiveRoutes(List p int moveNumber = j - i - 1; + String routeName = getRouteName(pathEdges, i, j); + mergedSteps.add(new TransportationResponseDto.Step( currentType, moveInfoList, From 8e2a156dbfbb0c2c1cb002b39b06a6e1f17ba13b Mon Sep 17 00:00:00 2001 From: hyoin Date: Thu, 21 Aug 2025 11:53:52 +0900 Subject: [PATCH 2/9] =?UTF-8?q?[refactor]=20=EC=BF=BC=EB=A6=AC=20=EC=B5=9C?= =?UTF-8?q?=EC=A0=81=ED=99=94,=20=EB=A9=94=EB=AA=A8=EB=A6=AC=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../direction/entity/transportation/Edge.java | 10 ++ .../entity/transportation/Route.java | 12 ++ .../direction/repository/EdgeRepository.java | 14 ++- .../repository/FacilityRepository.java | 5 +- .../direction/repository/NodeRepository.java | 7 +- .../direction/repository/RouteRepository.java | 5 + .../repository/WheelchairInfoRepository.java | 4 +- .../direction/service/BusInfoService.java | 13 ++- .../direction/service/FacilityService.java | 57 +++++----- .../service/TransportationService.java | 106 ++++++++++++++++-- 10 files changed, 177 insertions(+), 56 deletions(-) diff --git a/src/main/java/com/wayble/server/direction/entity/transportation/Edge.java b/src/main/java/com/wayble/server/direction/entity/transportation/Edge.java index 52acb79..5e4c831 100644 --- a/src/main/java/com/wayble/server/direction/entity/transportation/Edge.java +++ b/src/main/java/com/wayble/server/direction/entity/transportation/Edge.java @@ -44,4 +44,14 @@ public static Edge createEdge(Long id, Node startNode, Node endNode, DirectionTy .route(null) .build(); } + + public static Edge createEdgeWithRoute(Long id, Node startNode, Node endNode, DirectionType edgeType, Route route) { + return Edge.builder() + .id(id) + .edgeType(edgeType) + .startNode(startNode) + .endNode(endNode) + .route(route) + .build(); + } } diff --git a/src/main/java/com/wayble/server/direction/entity/transportation/Route.java b/src/main/java/com/wayble/server/direction/entity/transportation/Route.java index 42dfce4..d43d965 100644 --- a/src/main/java/com/wayble/server/direction/entity/transportation/Route.java +++ b/src/main/java/com/wayble/server/direction/entity/transportation/Route.java @@ -7,6 +7,7 @@ import lombok.*; import java.util.List; +import java.util.ArrayList; @Entity @Getter @@ -39,4 +40,15 @@ public class Route { // 휠체어 정보 @OneToMany(mappedBy = "route", fetch = FetchType.LAZY) private List wheelchairs; + + public static Route createRoute(Long routeId, String routeName, DirectionType routeType, Node startNode, Node endNode) { + return Route.builder() + .routeId(routeId) + .routeName(routeName) + .routeType(routeType) + .startNode(startNode) + .endNode(endNode) + .wheelchairs(new ArrayList<>()) + .build(); + } } diff --git a/src/main/java/com/wayble/server/direction/repository/EdgeRepository.java b/src/main/java/com/wayble/server/direction/repository/EdgeRepository.java index faf1434..3eeae9b 100644 --- a/src/main/java/com/wayble/server/direction/repository/EdgeRepository.java +++ b/src/main/java/com/wayble/server/direction/repository/EdgeRepository.java @@ -16,13 +16,17 @@ public interface EdgeRepository extends JpaRepository { "LEFT JOIN FETCH e.route") List findAllWithNodesAndRoute(); - @Query("SELECT DISTINCT e FROM Edge e " + - "JOIN FETCH e.startNode s " + - "JOIN FETCH e.endNode en " + - "LEFT JOIN FETCH e.route " + + @Query("SELECT e.id, e.startNode.id, e.endNode.id, e.edgeType, " + + "s.stationName, s.latitude, s.longitude, " + + "en.stationName, en.latitude, en.longitude, " + + "r.routeId, r.routeName " + + "FROM Edge e " + + "JOIN e.startNode s " + + "JOIN e.endNode en " + + "LEFT JOIN e.route r " + "WHERE (s.latitude BETWEEN :minLat AND :maxLat AND s.longitude BETWEEN :minLon AND :maxLon) OR " + "(en.latitude BETWEEN :minLat AND :maxLat AND en.longitude BETWEEN :minLon AND :maxLon)") - List findEdgesInBoundingBox( + List findEdgesInBoundingBox( @Param("minLat") double minLat, @Param("maxLat") double maxLat, @Param("minLon") double minLon, diff --git a/src/main/java/com/wayble/server/direction/repository/FacilityRepository.java b/src/main/java/com/wayble/server/direction/repository/FacilityRepository.java index d8a12c9..ad7f88d 100644 --- a/src/main/java/com/wayble/server/direction/repository/FacilityRepository.java +++ b/src/main/java/com/wayble/server/direction/repository/FacilityRepository.java @@ -10,8 +10,7 @@ import org.springframework.data.repository.query.Param; public interface FacilityRepository extends JpaRepository { - @Query("SELECT f FROM Facility f " + - "LEFT JOIN FETCH f.lifts " + + @Query("SELECT f.id, f.stationName, f.lnCd, f.railOprLsttCd, f.stinCd FROM Facility f " + "WHERE f.id = :nodeId") - Optional findByNodeId(@Param("nodeId") Long nodeId); + Optional findByNodeId(@Param("nodeId") Long nodeId); } diff --git a/src/main/java/com/wayble/server/direction/repository/NodeRepository.java b/src/main/java/com/wayble/server/direction/repository/NodeRepository.java index 9fe3cb6..ff2ef19 100644 --- a/src/main/java/com/wayble/server/direction/repository/NodeRepository.java +++ b/src/main/java/com/wayble/server/direction/repository/NodeRepository.java @@ -9,10 +9,11 @@ public interface NodeRepository extends JpaRepository { - @Query("SELECT n FROM Node n WHERE " + + @Query("SELECT n.id, n.stationName, n.nodeType, n.latitude, n.longitude FROM Node n WHERE " + "n.latitude BETWEEN :minLat AND :maxLat AND " + - "n.longitude BETWEEN :minLon AND :maxLon") - List findNodesInBoundingBox( + "n.longitude BETWEEN :minLon AND :maxLon " + + "ORDER BY n.latitude, n.longitude") + List findNodesInBoundingBox( @Param("minLat") double minLat, @Param("maxLat") double maxLat, @Param("minLon") double minLon, diff --git a/src/main/java/com/wayble/server/direction/repository/RouteRepository.java b/src/main/java/com/wayble/server/direction/repository/RouteRepository.java index 719a5d5..c052ecc 100644 --- a/src/main/java/com/wayble/server/direction/repository/RouteRepository.java +++ b/src/main/java/com/wayble/server/direction/repository/RouteRepository.java @@ -1,9 +1,14 @@ package com.wayble.server.direction.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import com.wayble.server.direction.entity.transportation.Route; +import java.util.Optional; public interface RouteRepository extends JpaRepository{ + @Query("SELECT r.routeName FROM Route r WHERE r.routeId = :routeId") + Optional findRouteNameById(@Param("routeId") Long routeId); } diff --git a/src/main/java/com/wayble/server/direction/repository/WheelchairInfoRepository.java b/src/main/java/com/wayble/server/direction/repository/WheelchairInfoRepository.java index 007fd89..f938cd9 100644 --- a/src/main/java/com/wayble/server/direction/repository/WheelchairInfoRepository.java +++ b/src/main/java/com/wayble/server/direction/repository/WheelchairInfoRepository.java @@ -10,6 +10,6 @@ public interface WheelchairInfoRepository extends JpaRepository { - @Query("SELECT w FROM Wheelchair w WHERE w.route.routeId = :routeId") - List findByRouteId(@Param("routeId") Long routeId); + @Query("SELECT w.wheelchairLocation FROM Wheelchair w WHERE w.route.routeId = :routeId") + List findWheelchairLocationsByRouteId(@Param("routeId") Long routeId); } diff --git a/src/main/java/com/wayble/server/direction/service/BusInfoService.java b/src/main/java/com/wayble/server/direction/service/BusInfoService.java index 3ee715b..7a3c99a 100644 --- a/src/main/java/com/wayble/server/direction/service/BusInfoService.java +++ b/src/main/java/com/wayble/server/direction/service/BusInfoService.java @@ -35,8 +35,8 @@ public TransportationResponseDto.BusInfo getBusInfo(String stationName, Long bus boolean isShuttleBus = false; if (busId != null) { - var route = routeRepository.findById(busId); - isShuttleBus = route.isPresent() && route.get().getRouteName().contains("마포"); + var routeName = routeRepository.findRouteNameById(busId); + isShuttleBus = routeName.isPresent() && routeName.get().contains("마포"); } try { @@ -120,8 +120,8 @@ private OpenDataResponse fetchArrivals(String stationId, Long busId) { originalResponse.msgBody().itemList() != null) { // busId로 route 정보 조회 - var route = routeRepository.findById(busId); - String projectRouteName = route.isPresent() ? route.get().getRouteName() : null; + var routeName = routeRepository.findRouteNameById(busId); + String projectRouteName = routeName.orElse(null); List filteredItems = originalResponse.msgBody().itemList().stream() .filter(item -> { @@ -166,7 +166,10 @@ private StationSearchResponse fetchStationByName(String stationName) { HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - return new ObjectMapper().readValue(response.body(), StationSearchResponse.class); + StationSearchResponse stationResponse = new ObjectMapper().readValue(response.body(), StationSearchResponse.class); + + log.debug("🔍 [DEBUG] StationSearch API Response: {}", stationResponse != null ? "SUCCESS" : "NULL"); + return stationResponse; } catch (Exception e) { log.error("정류소 검색 중 예외 발생: {}", e.getMessage()); diff --git a/src/main/java/com/wayble/server/direction/service/FacilityService.java b/src/main/java/com/wayble/server/direction/service/FacilityService.java index e00d665..918f293 100644 --- a/src/main/java/com/wayble/server/direction/service/FacilityService.java +++ b/src/main/java/com/wayble/server/direction/service/FacilityService.java @@ -16,6 +16,7 @@ import com.wayble.server.direction.repository.WheelchairInfoRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import lombok.Builder; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClient; @@ -45,39 +46,43 @@ public TransportationResponseDto.NodeInfo getNodeInfo(Long nodeId, Long routeId) List elevator = new ArrayList<>(); Boolean accessibleRestroom = false; - Optional nodeOpt = nodeRepository.findById(nodeId); - if (nodeOpt.isPresent()) { - Node node = nodeOpt.get(); - - if (routeId != null) { - List wheelchairs = wheelchairInfoRepository.findByRouteId(routeId); - for (Wheelchair wheelchairInfo : wheelchairs) { - String location = wheelchairInfo.getWheelchairLocation(); - if (location != null && !location.trim().isEmpty()) { - wheelchair.add(location.trim()); - } + if (routeId != null) { + List wheelchairLocations = wheelchairInfoRepository.findWheelchairLocationsByRouteId(routeId); + for (String location : wheelchairLocations) { + if (location != null && !location.trim().isEmpty()) { + wheelchair.add(location.trim()); } } - - Facility facility = facilityRepository.findByNodeId(nodeId).orElse(null); - if (facility != null) { - String stinCd = facility.getStinCd(); - String railOprLsttCd = facility.getRailOprLsttCd(); - String lnCd = facility.getLnCd(); + } + + Optional facilityData = facilityRepository.findByNodeId(nodeId); + if (facilityData.isPresent()) { + Object[] data = facilityData.get(); + String stinCd = (String) data[3]; // stinCd + String railOprLsttCd = (String) data[4]; // railOprLsttCd + String lnCd = (String) data[2]; // lnCd - if (stinCd != null && railOprLsttCd != null && lnCd != null) { - Map toiletInfo = getToiletInfo(facility); - accessibleRestroom = toiletInfo.getOrDefault(stinCd, false); + if (stinCd != null && railOprLsttCd != null && lnCd != null) { + // Facility 객체 생성 + Facility facility = Facility.builder() + .id((Long) data[0]) + .stationName((String) data[1]) + .lnCd(lnCd) + .railOprLsttCd(railOprLsttCd) + .stinCd(stinCd) + .build(); - elevator = getElevatorInfo(facility, routeId); - } else { - log.error("Facility 정보 누락 - nodeId: {}, stinCd: {}, railOprLsttCd: {}, lnCd: {}", - nodeId, stinCd, railOprLsttCd, lnCd); - } + Map toiletInfo = getToiletInfo(facility); + accessibleRestroom = toiletInfo.getOrDefault(stinCd, false); + + elevator = getElevatorInfo(facility, routeId); } else { - log.error("Facility 정보 없음 - nodeId: {}", nodeId); + log.error("Facility 정보 누락 - nodeId: {}, stinCd: {}, railOprLsttCd: {}, lnCd: {}", + nodeId, stinCd, railOprLsttCd, lnCd); } + } else { + log.error("Facility 정보 없음 - nodeId: {}", nodeId); } return new TransportationResponseDto.NodeInfo( diff --git a/src/main/java/com/wayble/server/direction/service/TransportationService.java b/src/main/java/com/wayble/server/direction/service/TransportationService.java index f8b91ca..d7ff738 100644 --- a/src/main/java/com/wayble/server/direction/service/TransportationService.java +++ b/src/main/java/com/wayble/server/direction/service/TransportationService.java @@ -91,16 +91,77 @@ private TransportationResponseDto.Route createRoute(List> findMultipleTransportationRoutes(Node startTmp, Node endTmp){ + List nodes = null; + List edges = null; + try { // 1. 공간 필터링을 사용한 데이터 로드 double[] boundingBox = calculateBoundingBox(startTmp, endTmp); - List nodes = nodeRepository.findNodesInBoundingBox( + List nodeData = nodeRepository.findNodesInBoundingBox( boundingBox[0], boundingBox[1], boundingBox[2], boundingBox[3] ); - List edges = edgeRepository.findEdgesInBoundingBox( + + // Object[]를 Node 객체로 변환 + nodes = nodeData.stream() + .map(data -> new Node( + (Long) data[0], // id + (String) data[1], // stationName + (DirectionType) data[2], // nodeType + (Double) data[3], // latitude + (Double) data[4] // longitude + )) + .collect(Collectors.toList()); + // 최적화된 쿼리 사용: 필요한 컬럼만 조회 + List edgeData = edgeRepository.findEdgesInBoundingBox( boundingBox[0], boundingBox[1], boundingBox[2], boundingBox[3] ); + // Object[]를 Edge 객체로 변환 + edges = edgeData.stream() + .map(data -> { + // DirectionType 객체 직접 캐스팅 + DirectionType edgeType = (DirectionType) data[3]; + + // Node 객체 생성 + Node startNode = Node.createNode( + (Long) data[1], // startNode.id + (String) data[4], // startNode.stationName + edgeType, // edgeType + (Double) data[5], // startNode.latitude + (Double) data[6] // startNode.longitude + ); + + Node endNode = Node.createNode( + (Long) data[2], // endNode.id + (String) data[7], // endNode.stationName + edgeType, // edgeType + (Double) data[8], // endNode.latitude + (Double) data[9] // endNode.longitude + ); + + // Route 객체 생성 (null일 수 있음) + Route route = null; + if (data[10] != null) { // routeId가 null이 아닌 경우 + route = Route.createRoute( + (Long) data[10], // routeId + (String) data[11], // routeName + edgeType, // routeType + startNode, + endNode + ); + } + + // Edge 객체 생성 + return Edge.createEdgeWithRoute( + (Long) data[0], // edge.id + startNode, + endNode, + edgeType, // edgeType + route + ); + }) + .collect(Collectors.toList()); + log.debug("Spatial filtering loaded {} nodes and {} edges", nodes.size(), edges.size()); // 2. 가장 가까운 정류장 찾기 @@ -121,14 +182,26 @@ private List> findMultipleTransportationRou graphData.graph(), startTmp, endTmp, graphData.weightMap(), nodes, nearestToStart, nearestToEnd ); - // 5. 메모리 정리 (명시적으로 null 설정) - nodes.clear(); - edges.clear(); return result; } catch (OutOfMemoryError e) { log.error("Out of memory error in transportation route finding: {}", e.getMessage()); throw new ApplicationException(PATH_NOT_FOUND); + } finally { + // 5. 메모리 정리 (finally 블록에서 확실히 실행) + if (nodes != null) { + nodes.clear(); + nodes = null; + } + if (edges != null) { + edges.clear(); + edges = null; + } + + // 명시적 GC 호출 + if (Runtime.getRuntime().freeMemory() < Runtime.getRuntime().totalMemory() * 0.1) { + System.gc(); + } } } @@ -457,12 +530,19 @@ private List findNearbyNodes(List nodes, double lat, double lon, int } private List runDijkstra(Map> graph, Node start, Node end, Map, Integer> weightMap, List nodes) { - // 1. 초기화 + // 1. 초기화 - HashMap 대신 Array 사용으로 성능 향상 Map distance = new HashMap<>(); Map prevEdge = new HashMap<>(); Map prevNode = new HashMap<>(); Set visited = new HashSet<>(); + Map nodeMap = nodes.stream() + .collect(Collectors.toMap( + Node::getId, + node -> node, + (existing, replacement) -> existing // 중복 시 기존 값 유지 + )); + for (Node node : nodes) { distance.put(node.getId(), Integer.MAX_VALUE); prevNode.put(node.getId(), null); @@ -470,7 +550,8 @@ private List runDijkstra(Map> g } distance.put(start.getId(), 0); - PriorityQueue pq = new PriorityQueue<>(Comparator.comparingInt(n -> distance.get(n.getId()))); + PriorityQueue pq = new PriorityQueue<>(Math.min(1000, nodes.size()), + Comparator.comparingInt(n -> distance.get(n.getId()))); pq.add(start); int visitedCount = 0; @@ -690,14 +771,13 @@ private List mergeConsecutiveRoutes(List p return new ArrayList<>(); } } - } catch (Exception e) { - log.error("버스 정보 조회 실패: {}", e.getMessage()); - } + } catch (Exception e) { + log.error("버스 정보 조회 실패: {}", e.getMessage()); + } } else if (currentType == DirectionType.SUBWAY) { try { - if (currentEdge.getStartNode() != null) { + if (currentEdge.getStartNode() != null && currentEdge.getRoute() != null) { TransportationResponseDto.NodeInfo nodeInfo = facilityService.getNodeInfo(currentEdge.getStartNode().getId(), currentEdge.getRoute().getRouteId()); - subwayInfo = new TransportationResponseDto.SubwayInfo( nodeInfo.wheelchair(), nodeInfo.elevator(), @@ -892,4 +972,6 @@ private int calculateTransferCount(List steps) { } return transferCount; } + + } From 69b82a02ae9786587e9ecd5254cef89a9327845c Mon Sep 17 00:00:00 2001 From: hyoin Date: Thu, 21 Aug 2025 13:32:16 +0900 Subject: [PATCH 3/9] =?UTF-8?q?[fix]=20=EA=B3=B5=EA=B3=B5=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20api=C2=A0=ED=98=B8=EC=B6=9C=EC=8B=9C=20web?= =?UTF-8?q?client=C2=A0=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/common/config/WebClientConfig.java | 13 ++++ .../external/opendata/OpenDataProperties.java | 2 - .../direction/service/BusInfoService.java | 65 +++++++++++-------- 3 files changed, 52 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/wayble/server/common/config/WebClientConfig.java b/src/main/java/com/wayble/server/common/config/WebClientConfig.java index acbbf29..07e81cf 100644 --- a/src/main/java/com/wayble/server/common/config/WebClientConfig.java +++ b/src/main/java/com/wayble/server/common/config/WebClientConfig.java @@ -2,10 +2,12 @@ import com.wayble.server.common.client.tmap.TMapProperties; import com.wayble.server.direction.external.kric.KricProperties; +import com.wayble.server.direction.external.opendata.OpenDataProperties; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.util.DefaultUriBuilderFactory; @Configuration @RequiredArgsConstructor @@ -13,6 +15,7 @@ public class WebClientConfig { private final TMapProperties tMapProperties; private final KricProperties kricProperties; + private final OpenDataProperties openDataProperties; @Bean public WebClient webClient() { @@ -38,4 +41,14 @@ public WebClient kricWebClient() { .filter(throwable -> throwable instanceof org.springframework.web.reactive.function.client.WebClientRequestException))) .build(); } + + @Bean + public WebClient openDataWebClient() { + DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(openDataProperties.baseUrl()); + factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY); + return WebClient.builder() + .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(512 * 1024)) + .uriBuilderFactory(factory) + .build(); + } } diff --git a/src/main/java/com/wayble/server/direction/external/opendata/OpenDataProperties.java b/src/main/java/com/wayble/server/direction/external/opendata/OpenDataProperties.java index 6819cfb..3a7f569 100644 --- a/src/main/java/com/wayble/server/direction/external/opendata/OpenDataProperties.java +++ b/src/main/java/com/wayble/server/direction/external/opendata/OpenDataProperties.java @@ -4,12 +4,10 @@ @ConfigurationProperties(prefix = "opendata.api") public record OpenDataProperties( - String key, String baseUrl, String encodedKey, Endpoints endpoints, int timeout, - String userAgent, String accept ) { public record Endpoints(String arrivals, String stationByName) {} diff --git a/src/main/java/com/wayble/server/direction/service/BusInfoService.java b/src/main/java/com/wayble/server/direction/service/BusInfoService.java index 7a3c99a..606df84 100644 --- a/src/main/java/com/wayble/server/direction/service/BusInfoService.java +++ b/src/main/java/com/wayble/server/direction/service/BusInfoService.java @@ -10,21 +10,18 @@ import com.wayble.server.direction.repository.RouteRepository; import com.wayble.server.direction.dto.response.TransportationResponseDto; +import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.net.URI; -import java.time.Duration; +import org.springframework.web.reactive.function.client.WebClient; @Service @Slf4j @RequiredArgsConstructor public class BusInfoService { - private final HttpClient httpClient; + private final WebClient openDataWebClient; private final OpenDataProperties openDataProperties; private final RouteRepository routeRepository; @@ -98,22 +95,25 @@ private OpenDataResponse fetchArrivals(String stationId, Long busId) { try { String serviceKey = openDataProperties.encodedKey(); - String uri = openDataProperties.baseUrl() + + String fullUri = openDataProperties.baseUrl() + openDataProperties.endpoints().arrivals() + "?serviceKey=" + serviceKey + "&arsId=" + stationId + "&resultType=json"; - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(uri)) - .header("Accept", openDataProperties.accept()) - .GET() - .timeout(Duration.ofSeconds(openDataProperties.timeout())) - .build(); - HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - - OpenDataResponse originalResponse = new ObjectMapper().readValue(response.body(), OpenDataResponse.class); + OpenDataResponse originalResponse = openDataWebClient + .get() + .uri(uriBuilder -> uriBuilder + .path(openDataProperties.endpoints().arrivals()) + .queryParam("serviceKey", serviceKey) + .queryParam("arsId", stationId) + .queryParam("resultType", "json") + .build()) + .header("Accept", openDataProperties.accept()) + .retrieve() + .bodyToMono(OpenDataResponse.class) + .block(); // busId가 맞는 버스만 필터링 if (busId != null && originalResponse != null && originalResponse.msgBody() != null && @@ -151,24 +151,37 @@ private StationSearchResponse fetchStationByName(String stationName) { try { String serviceKey = openDataProperties.encodedKey(); - String uri = openDataProperties.baseUrl() + + String fullUri = openDataProperties.baseUrl() + openDataProperties.endpoints().stationByName() + "?serviceKey=" + serviceKey + "&stSrch=" + stationName + "&resultType=json"; - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(uri)) - .header("Accept", openDataProperties.accept()) - .GET() - .timeout(Duration.ofSeconds(openDataProperties.timeout())) - .build(); - HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - StationSearchResponse stationResponse = new ObjectMapper().readValue(response.body(), StationSearchResponse.class); + String rawResponse = openDataWebClient + .get() + .uri(uriBuilder -> uriBuilder + .path(openDataProperties.endpoints().stationByName()) + .queryParam("serviceKey", serviceKey) + .queryParam("stSrch", stationName) + .queryParam("resultType", "json") + .build()) + .header("Accept", openDataProperties.accept()) + .retrieve() + .bodyToMono(String.class) + .block(); + + StationSearchResponse stationResponse = null; + if (rawResponse != null) { + try { + ObjectMapper objectMapper = new ObjectMapper(); + stationResponse = objectMapper.readValue(rawResponse, StationSearchResponse.class); + } catch (Exception e) { + log.error("JSON 파싱 실패: {}", e.getMessage()); + } + } - log.debug("🔍 [DEBUG] StationSearch API Response: {}", stationResponse != null ? "SUCCESS" : "NULL"); return stationResponse; } catch (Exception e) { From a0b89883a4e7a2bf43547f6b1a086e471712c769 Mon Sep 17 00:00:00 2001 From: hyoin Date: Thu, 21 Aug 2025 15:11:53 +0900 Subject: [PATCH 4/9] =?UTF-8?q?[fix]=20=EC=A7=80=ED=95=98=EC=B2=A0=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=EA=B0=80=20=EC=95=88=20=EB=9C=A8=EB=8A=94=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/FacilityRepository.java | 4 +-- .../repository/WheelchairInfoRepository.java | 3 +++ .../direction/service/FacilityService.java | 25 ++++++------------- 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/wayble/server/direction/repository/FacilityRepository.java b/src/main/java/com/wayble/server/direction/repository/FacilityRepository.java index ad7f88d..77b4ab0 100644 --- a/src/main/java/com/wayble/server/direction/repository/FacilityRepository.java +++ b/src/main/java/com/wayble/server/direction/repository/FacilityRepository.java @@ -10,7 +10,5 @@ import org.springframework.data.repository.query.Param; public interface FacilityRepository extends JpaRepository { - @Query("SELECT f.id, f.stationName, f.lnCd, f.railOprLsttCd, f.stinCd FROM Facility f " + - "WHERE f.id = :nodeId") - Optional findByNodeId(@Param("nodeId") Long nodeId); + Optional findByNodeId(@Param("nodeId") Long nodeId); } diff --git a/src/main/java/com/wayble/server/direction/repository/WheelchairInfoRepository.java b/src/main/java/com/wayble/server/direction/repository/WheelchairInfoRepository.java index f938cd9..41aea5f 100644 --- a/src/main/java/com/wayble/server/direction/repository/WheelchairInfoRepository.java +++ b/src/main/java/com/wayble/server/direction/repository/WheelchairInfoRepository.java @@ -10,6 +10,9 @@ public interface WheelchairInfoRepository extends JpaRepository { + @Query("SELECT w FROM Wheelchair w WHERE w.route.routeId = :routeId") + List findByRouteId(@Param("routeId") Long routeId); + @Query("SELECT w.wheelchairLocation FROM Wheelchair w WHERE w.route.routeId = :routeId") List findWheelchairLocationsByRouteId(@Param("routeId") Long routeId); } diff --git a/src/main/java/com/wayble/server/direction/service/FacilityService.java b/src/main/java/com/wayble/server/direction/service/FacilityService.java index 918f293..77715b5 100644 --- a/src/main/java/com/wayble/server/direction/service/FacilityService.java +++ b/src/main/java/com/wayble/server/direction/service/FacilityService.java @@ -56,33 +56,24 @@ public TransportationResponseDto.NodeInfo getNodeInfo(Long nodeId, Long routeId) } } - Optional facilityData = facilityRepository.findByNodeId(nodeId); - if (facilityData.isPresent()) { - Object[] data = facilityData.get(); - String stinCd = (String) data[3]; // stinCd - String railOprLsttCd = (String) data[4]; // railOprLsttCd - String lnCd = (String) data[2]; // lnCd + Optional facilityOpt = facilityRepository.findByNodeId(nodeId); + if (facilityOpt.isPresent()) { + Facility facility = facilityOpt.get(); + String stinCd = facility.getStinCd(); + String railOprLsttCd = facility.getRailOprLsttCd(); + String lnCd = facility.getLnCd(); if (stinCd != null && railOprLsttCd != null && lnCd != null) { - // Facility 객체 생성 - Facility facility = Facility.builder() - .id((Long) data[0]) - .stationName((String) data[1]) - .lnCd(lnCd) - .railOprLsttCd(railOprLsttCd) - .stinCd(stinCd) - .build(); - Map toiletInfo = getToiletInfo(facility); accessibleRestroom = toiletInfo.getOrDefault(stinCd, false); elevator = getElevatorInfo(facility, routeId); } else { - log.error("Facility 정보 누락 - nodeId: {}, stinCd: {}, railOprLsttCd: {}, lnCd: {}", + log.warn("Facility 정보 누락 - nodeId: {}, stinCd: {}, railOprLsttCd: {}, lnCd: {}", nodeId, stinCd, railOprLsttCd, lnCd); } } else { - log.error("Facility 정보 없음 - nodeId: {}", nodeId); + log.debug("Facility 정보 없음 - nodeId: {}", nodeId); } return new TransportationResponseDto.NodeInfo( From eedade7e3b7d80e56c3606ff583165a5aac0df3a Mon Sep 17 00:00:00 2001 From: hyoin Date: Thu, 21 Aug 2025 17:20:50 +0900 Subject: [PATCH 5/9] =?UTF-8?q?[refactor]=20api=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=20=EC=A0=95=EB=A6=AC=20=EB=B0=8F=20=EC=86=8D?= =?UTF-8?q?=EB=8F=84=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../direction/service/BusInfoService.java | 125 +++++++++++------- 1 file changed, 80 insertions(+), 45 deletions(-) diff --git a/src/main/java/com/wayble/server/direction/service/BusInfoService.java b/src/main/java/com/wayble/server/direction/service/BusInfoService.java index 606df84..75983d0 100644 --- a/src/main/java/com/wayble/server/direction/service/BusInfoService.java +++ b/src/main/java/com/wayble/server/direction/service/BusInfoService.java @@ -15,6 +15,7 @@ import java.util.List; import java.util.stream.Collectors; import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; @Service @Slf4j @@ -46,9 +47,17 @@ public TransportationResponseDto.BusInfo getBusInfo(String stationName, Long bus return new TransportationResponseDto.BusInfo(isShuttleBus, new ArrayList<>(), null); } - // 2. 여러 정류소가 나올 때, 가장 가까운 정류소 찾기 - StationSearchResponse.StationItem closestStation = findClosestStation( - stationSearchResponse.msgBody().itemList(), x, y, busId); + // 2. 정류소가 1개면 바로 선택, 여러 개면 가장 가까운 정류소 찾기 + List stationList = stationSearchResponse.msgBody().itemList(); + StationSearchResponse.StationItem closestStation; + + if (stationList.size() == 1) { + // 정류소가 1개뿐이면 불필요한 거리 계산 및 API 호출 없이 바로 선택 + closestStation = stationList.get(0); + } else { + // 여러 정류소가 있을 때만 findClosestStation 호출 + closestStation = findClosestStation(stationList, x, y, busId); + } if (closestStation == null) { log.warn("가장 가까운 정류소를 찾을 수 없습니다: {}", stationName); @@ -102,7 +111,8 @@ private OpenDataResponse fetchArrivals(String stationId, Long busId) { "&resultType=json"; - OpenDataResponse originalResponse = openDataWebClient + // 먼저 raw 응답을 확인 + String rawResponse = openDataWebClient .get() .uri(uriBuilder -> uriBuilder .path(openDataProperties.endpoints().arrivals()) @@ -112,8 +122,25 @@ private OpenDataResponse fetchArrivals(String stationId, Long busId) { .build()) .header("Accept", openDataProperties.accept()) .retrieve() - .bodyToMono(OpenDataResponse.class) + .onStatus(status -> status.isError(), response -> { + return response.bodyToMono(String.class) + .flatMap(body -> { + log.error("공공데이터 API 호출 오류: {}", body); + return Mono.error(new RuntimeException("API 호출 실패: " + response.statusCode())); + }); + }) + .bodyToMono(String.class) .block(); + + OpenDataResponse originalResponse = null; + if (rawResponse != null) { + try { + ObjectMapper objectMapper = new ObjectMapper(); + originalResponse = objectMapper.readValue(rawResponse, OpenDataResponse.class); + } catch (Exception e) { + log.error("JSON 파싱 실패: {}", e.getMessage()); + } + } // busId가 맞는 버스만 필터링 if (busId != null && originalResponse != null && originalResponse.msgBody() != null && @@ -169,6 +196,13 @@ private StationSearchResponse fetchStationByName(String stationName) { .build()) .header("Accept", openDataProperties.accept()) .retrieve() + .onStatus(status -> status.isError(), response -> { + return response.bodyToMono(String.class) + .flatMap(body -> { + log.error("공공데이터 API 호출 오류: {}", body); + return Mono.error(new RuntimeException("API 호출 실패: " + response.statusCode())); + }); + }) .bodyToMono(String.class) .block(); @@ -196,13 +230,50 @@ private StationSearchResponse.StationItem findClosestStation(List sortedStations = new ArrayList<>(); + for (StationSearchResponse.StationItem station : stations) { + try { + String tmXStr = station.tmX(); + String tmYStr = station.tmY(); + + if (tmXStr == null || tmYStr == null || tmXStr.trim().isEmpty() || tmYStr.trim().isEmpty() || + "null".equalsIgnoreCase(tmXStr.trim()) || "null".equalsIgnoreCase(tmYStr.trim())) { + continue; + } + + double stationX = Double.parseDouble(tmXStr); + double stationY = Double.parseDouble(tmYStr); + double distance = Math.sqrt(Math.pow(stationX - x, 2) + Math.pow(stationY - y, 2)); + + // 거리 정보를 포함한 Wrapper 클래스 대신 정렬용 로직 사용 + sortedStations.add(station); + } catch (NumberFormatException e) { + log.warn("정류소 좌표 파싱 실패 - {}: tmX={}, tmY={}", station.stNm(), station.tmX(), station.tmY()); + continue; + } + } + + // 거리순으로 정렬 + sortedStations.sort((s1, s2) -> { + try { + double d1 = Math.sqrt(Math.pow(Double.parseDouble(s1.tmX()) - x, 2) + Math.pow(Double.parseDouble(s1.tmY()) - y, 2)); + double d2 = Math.sqrt(Math.pow(Double.parseDouble(s2.tmX()) - x, 2) + Math.pow(Double.parseDouble(s2.tmY()) - y, 2)); + return Double.compare(d1, d2); + } catch (NumberFormatException e) { + return 0; + } + }); + + // 2. routeId가 있으면 가장 가까운 상위 3개 정류소에서만 해당 버스가 운행되는지 확인 if (routeId != null) { var route = routeRepository.findById(routeId); String projectRouteName = route.isPresent() ? route.get().getRouteName() : null; if (projectRouteName != null) { - for (StationSearchResponse.StationItem station : stations) { + int maxStationsToCheck = Math.min(3, sortedStations.size()); // 최대 3개만 확인 + for (int i = 0; i < maxStationsToCheck; i++) { + StationSearchResponse.StationItem station = sortedStations.get(i); try { // 각 정류소에서 버스 정보 조회 OpenDataResponse busResponse = fetchArrivals(station.arsId(), null); @@ -223,43 +294,7 @@ private StationSearchResponse.StationItem findClosestStation(List Date: Thu, 21 Aug 2025 17:22:16 +0900 Subject: [PATCH 6/9] =?UTF-8?q?[refactor]=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20api=C2=A0=ED=98=B8=EC=B6=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/direction/dto/InternalStep.java | 22 ++ .../service/TransportationService.java | 214 ++++++++++-------- 2 files changed, 145 insertions(+), 91 deletions(-) create mode 100644 src/main/java/com/wayble/server/direction/dto/InternalStep.java diff --git a/src/main/java/com/wayble/server/direction/dto/InternalStep.java b/src/main/java/com/wayble/server/direction/dto/InternalStep.java new file mode 100644 index 0000000..d3e6354 --- /dev/null +++ b/src/main/java/com/wayble/server/direction/dto/InternalStep.java @@ -0,0 +1,22 @@ +package com.wayble.server.direction.dto; + +import java.util.List; + +import com.wayble.server.direction.dto.response.TransportationResponseDto; +import com.wayble.server.direction.entity.transportation.Node; +import com.wayble.server.direction.entity.type.DirectionType; + +// 대중교통 길찾기에 사용하기 위한 내부용 DTO +public record InternalStep( + DirectionType mode, + List moveInfo, + String routeName, + Integer moveNumber, + TransportationResponseDto.BusInfo busInfo, + TransportationResponseDto.SubwayInfo subwayInfo, + String from, + String to, + Long routeId, + Node startNode, + Node endNode +) {} \ No newline at end of file diff --git a/src/main/java/com/wayble/server/direction/service/TransportationService.java b/src/main/java/com/wayble/server/direction/service/TransportationService.java index d7ff738..75e9579 100644 --- a/src/main/java/com/wayble/server/direction/service/TransportationService.java +++ b/src/main/java/com/wayble/server/direction/service/TransportationService.java @@ -1,6 +1,7 @@ package com.wayble.server.direction.service; import com.wayble.server.common.exception.ApplicationException; +import com.wayble.server.direction.dto.InternalStep; import com.wayble.server.direction.dto.TransportationGraphDto; import com.wayble.server.direction.dto.request.TransportationRequestDto; import com.wayble.server.direction.dto.response.TransportationResponseDto; @@ -28,7 +29,7 @@ public class TransportationService { private final EdgeRepository edgeRepository; private final FacilityService facilityService; private final BusInfoService busInfoService; - + private static final int TRANSFER_PENALTY = 10000; // 환승 시 추가되는 가중치 (m) private static final int STEP_PENALTY = 500; // 각 이동 단계마다 추가되는 기본 가중치 (m) private static final int METER_CONVERSION = 1000; // 킬로미터를 미터로 변환하는 상수 @@ -59,7 +60,7 @@ public TransportationResponseDto findRoutes(TransportationRequestDto request){ Node end = Node.createNode(-2L, destination.name(), DirectionType.TO_WAYPOINT,destination.latitude(), destination.longitude()); // 3. 여러 경로 찾기 - List> allRoutes = findMultipleTransportationRoutes(start, end); + List> allRoutes = findMultipleTransportationRoutes(start, end); // 4. 페이징 처리 int startIndex = (request.cursor() != null) ? request.cursor() : 0; @@ -76,10 +77,14 @@ public TransportationResponseDto findRoutes(TransportationRequestDto request){ // 페이징된 경로들을 Route 객체로 변환 List routeList = new ArrayList<>(); - List> pagedRoutes = allRoutes.subList(startIndex, endIndex); + List> pagedRoutes = allRoutes.subList(startIndex, endIndex); for (int i = 0; i < pagedRoutes.size(); i++) { - List route = pagedRoutes.get(i); - TransportationResponseDto.Route routeObj = createRoute(route, startIndex + i + 1); + List internalRoute = pagedRoutes.get(i); + + // InternalStep을 TransportationResponseDto.Step으로 변환 (API 호출 포함) + List enrichedRoute = enrichRoutesWithServiceInfo(internalRoute); + + TransportationResponseDto.Route routeObj = createRoute(enrichedRoute, startIndex + i + 1); routeList.add(routeObj); } @@ -90,7 +95,7 @@ private TransportationResponseDto.Route createRoute(List> findMultipleTransportationRoutes(Node startTmp, Node endTmp){ + private List> findMultipleTransportationRoutes(Node startTmp, Node endTmp){ List nodes = null; List edges = null; @@ -178,7 +183,7 @@ private List> findMultipleTransportationRou // 4. 그래프 빌드 및 여러 경로 찾기 TransportationGraphDto graphData = buildGraph(nodes, edges, startTmp, endTmp); - List> result = findMultipleOptimalRoutes( + List> result = findMultipleOptimalRoutes( graphData.graph(), startTmp, endTmp, graphData.weightMap(), nodes, nearestToStart, nearestToEnd ); @@ -214,7 +219,7 @@ private double[] calculateBoundingBox(Node start, Node end) { return new double[]{minLat, maxLat, minLon, maxLon}; } - private List> findMultipleOptimalRoutes( + private List> findMultipleOptimalRoutes( Map> graph, Node startTmp, Node endTmp, @@ -239,31 +244,31 @@ private List> findMultipleOptimalRoutes( } // 2. 여러 경로 찾기 - List> allRoutes = findMultipleRoutes(graph, startNode, endNode, weightMap, nodes); + List> allRoutes = findMultipleRoutes(graph, startNode, endNode, weightMap, nodes); // 3. 경로 필터링 및 정렬 - List> result = filterAndSortRoutes(allRoutes); + List> result = filterAndSortRoutes(allRoutes); return result; } - private List> findMultipleRoutes( + private List> findMultipleRoutes( Map> graph, Node start, Node end, Map, Integer> weightMap, List nodes) { - List> routes = new ArrayList<>(); + List> routes = new ArrayList<>(); // 1. 기본 다익스트라로 첫 번째 경로 찾기 - List firstRoute = runDijkstra(graph, start, end, weightMap, nodes); + List firstRoute = runDijkstra(graph, start, end, weightMap, nodes); if (!firstRoute.isEmpty()) { routes.add(firstRoute); } // 2. 효율적인 다중 경로 찾기 - 한 번의 탐색으로 여러 경로 생성 if (!firstRoute.isEmpty()) { - List> alternativeRoutes = findAlternativeRoutesEfficiently( + List> alternativeRoutes = findAlternativeRoutesEfficiently( graph, start, end, weightMap, nodes, firstRoute ); routes.addAll(alternativeRoutes); @@ -272,15 +277,15 @@ private List> findMultipleRoutes( return routes; } - private List> findAlternativeRoutesEfficiently( + private List> findAlternativeRoutesEfficiently( Map> graph, Node start, Node end, Map, Integer> weightMap, List nodes, - List firstRoute) { + List firstRoute) { - List> alternativeRoutes = new ArrayList<>(); + List> alternativeRoutes = new ArrayList<>(); // 첫 번째 경로에서 실제 사용된 엣지들을 추출 Set> usedEdges = extractActualEdgesFromRoute(firstRoute, graph); @@ -291,7 +296,7 @@ private List> findAlternativeRoutesEfficien Map, Integer> penalizedWeightMap = createActualEdgePenalizedWeightMap(weightMap, usedEdges, i + 1); // 다익스트라로 새로운 경로 찾기 - List newRoute = runDijkstra(graph, start, end, penalizedWeightMap, nodes); + List newRoute = runDijkstra(graph, start, end, penalizedWeightMap, nodes); if (newRoute.isEmpty()) { break; @@ -316,10 +321,10 @@ private List> findAlternativeRoutesEfficien - private Set> extractActualEdgesFromRoute(List route, Map> graph) { + private Set> extractActualEdgesFromRoute(List route, Map> graph) { Set> usedEdges = new HashSet<>(); - for (TransportationResponseDto.Step step : route) { + for (InternalStep step : route) { String fromName = step.from(); String toName = step.to(); @@ -361,15 +366,15 @@ private Map, Integer> createActualEdgePenalizedWeightMap(Map route1, List route2) { + private boolean areRoutesIdentical(List route1, List route2) { // 두 경로가 완전히 동일한지 확인 if (route1.size() != route2.size()) { return false; } for (int i = 0; i < route1.size(); i++) { - TransportationResponseDto.Step step1 = route1.get(i); - TransportationResponseDto.Step step2 = route2.get(i); + InternalStep step1 = route1.get(i); + InternalStep step2 = route2.get(i); if (step1.mode() != step2.mode() || !Objects.equals(step1.from(), step2.from()) || @@ -382,8 +387,8 @@ private boolean areRoutesIdentical(List route1, return true; } - private List> filterAndSortRoutes(List> routes) { - List> filteredRoutes = routes.stream() + private List> filterAndSortRoutes(List> routes) { + List> filteredRoutes = routes.stream() .filter(route -> { // 대중교통 포함 여부 확인 boolean hasPublicTransport = route.stream() @@ -398,7 +403,7 @@ private List> filterAndSortRoutes(List>comparingInt(this::calculateTransferCount) + .>comparingInt(this::calculateTransferCount) .thenComparingInt(this::calculateWalkDistance)) .limit(MAX_ROUTES) .collect(Collectors.toList()); @@ -406,7 +411,7 @@ private List> filterAndSortRoutes(List route) { + private int calculateWalkDistance(List route) { return route.stream() .filter(step -> step.mode() == DirectionType.WALK) .mapToInt(step -> { @@ -529,7 +534,7 @@ private List findNearbyNodes(List nodes, double lat, double lon, int .collect(Collectors.toList()); } - private List runDijkstra(Map> graph, Node start, Node end, Map, Integer> weightMap, List nodes) { + private List runDijkstra(Map> graph, Node start, Node end, Map, Integer> weightMap, List nodes) { // 1. 초기화 - HashMap 대신 Array 사용으로 성능 향상 Map distance = new HashMap<>(); Map prevEdge = new HashMap<>(); @@ -683,9 +688,69 @@ private List runDijkstra(Map> g return mergeConsecutiveRoutes(pathEdges, requestId); } + + private List enrichRoutesWithServiceInfo(List steps) { + List enrichedSteps = new ArrayList<>(); + + for (InternalStep step : steps) { + TransportationResponseDto.BusInfo busInfo = null; + TransportationResponseDto.SubwayInfo subwayInfo = null; + + if (step.mode() == DirectionType.BUS) { + log.info("🚌 최종 경로 - 버스 정보 조회: from={}, to={}, routeId={}", step.from(), step.to(), step.routeId()); + + if (step.routeId() != null && step.startNode() != null) { + try { + busInfo = busInfoService.getBusInfo( + step.from(), + step.routeId(), + step.startNode().getLatitude(), + step.startNode().getLongitude() + ); + } catch (Exception e) { + log.error("버스 정보 조회 실패: {}", e.getMessage()); + } + } + } else if (step.mode() == DirectionType.SUBWAY) { + log.info("🚇 최종 경로 - 지하철 정보 조회: from={}, to={}, routeId={}", step.from(), step.to(), step.routeId()); + if (step.routeId() != null && step.startNode() != null) { + try { + TransportationResponseDto.NodeInfo nodeInfo = facilityService.getNodeInfo(step.startNode().getId(), step.routeId()); + subwayInfo = new TransportationResponseDto.SubwayInfo( + nodeInfo.wheelchair(), + nodeInfo.elevator(), + nodeInfo.accessibleRestroom() + ); + } catch (Exception e) { + log.error("지하철 정보 조회 실패: {}", e.getMessage()); + subwayInfo = new TransportationResponseDto.SubwayInfo( + new ArrayList<>(), + new ArrayList<>(), + false + ); + } + } + } + + // 새로운 Step 생성 (busInfo, subwayInfo 포함) + TransportationResponseDto.Step enrichedStep = new TransportationResponseDto.Step( + step.mode(), + step.moveInfo(), + step.routeName(), + step.moveNumber(), + busInfo, + subwayInfo, + step.from(), + step.to() + ); + enrichedSteps.add(enrichedStep); + } + + return enrichedSteps; + } - private List mergeConsecutiveRoutes(List pathEdges, long requestId) { - List mergedSteps = new ArrayList<>(); + private List mergeConsecutiveRoutes(List pathEdges, long requestId) { + List mergedSteps = new ArrayList<>(); if (pathEdges.isEmpty()) { return mergedSteps; @@ -739,8 +804,8 @@ private List mergeConsecutiveRoutes(List p walkDistance = (int) (distanceKm * 1000); // km를 m로 변환 } - mergedSteps.add(new TransportationResponseDto.Step( - DirectionType.WALK, null, null, walkDistance, null, null, fromName, toName + mergedSteps.add(new InternalStep( + DirectionType.WALK, null, null, walkDistance, null, null, fromName, toName, null, walkStartNode, walkEndNode )); i = j; continue; @@ -748,63 +813,24 @@ private List mergeConsecutiveRoutes(List p // 3. 교통수단 상세 정보 (moveInfo) 설정 List moveInfoList = createMoveInfoList(pathEdges, i, j); - // busInfo / subwayInfo 설정 + // busInfo / subwayInfo는 나중에 설정 (최종 경로 선택 후) TransportationResponseDto.BusInfo busInfo = null; TransportationResponseDto.SubwayInfo subwayInfo = null; - - if (currentType == DirectionType.BUS) { - try { - if (currentEdge.getStartNode() != null && currentEdge.getRoute() != null) { - - busInfo = busInfoService.getBusInfo( - fromName, - currentEdge.getRoute().getRouteId(), - currentEdge.getStartNode().getLatitude(), - currentEdge.getStartNode().getLongitude() - ); - - if (busInfo != null && - busInfo.isLowFloor() != null && !busInfo.isLowFloor().isEmpty() && - busInfo.dispatchInterval() != null && - busInfo.isLowFloor().stream().allMatch(floor -> !floor) && - busInfo.dispatchInterval() == 0) { - return new ArrayList<>(); - } - } - } catch (Exception e) { - log.error("버스 정보 조회 실패: {}", e.getMessage()); - } - } else if (currentType == DirectionType.SUBWAY) { - try { - if (currentEdge.getStartNode() != null && currentEdge.getRoute() != null) { - TransportationResponseDto.NodeInfo nodeInfo = facilityService.getNodeInfo(currentEdge.getStartNode().getId(), currentEdge.getRoute().getRouteId()); - subwayInfo = new TransportationResponseDto.SubwayInfo( - nodeInfo.wheelchair(), - nodeInfo.elevator(), - nodeInfo.accessibleRestroom() - ); - } else { - subwayInfo = new TransportationResponseDto.SubwayInfo( - new ArrayList<>(), - new ArrayList<>(), - false - ); - } - } catch (Exception e) { - log.error("지하철 정보 조회 실패: {}", e.getMessage()); - subwayInfo = new TransportationResponseDto.SubwayInfo( - new ArrayList<>(), - new ArrayList<>(), - false - ); - } - } int moveNumber = j - i - 1; String routeName = getRouteName(pathEdges, i, j); - mergedSteps.add(new TransportationResponseDto.Step( + // routeId와 Node 정보 추출 + Long routeId = null; + Node startNode = currentEdge.getStartNode(); + Node endNode = pathEdges.get(j - 1).getEndNode(); + + if (currentEdge.getRoute() != null) { + routeId = currentEdge.getRoute().getRouteId(); + } + + mergedSteps.add(new InternalStep( currentType, moveInfoList, routeName, @@ -812,7 +838,10 @@ private List mergeConsecutiveRoutes(List p busInfo, subwayInfo, fromName, - toName + toName, + routeId, + startNode, + endNode )); i = j; @@ -822,16 +851,16 @@ private List mergeConsecutiveRoutes(List p return addTransferWalkSteps(mergedSteps, pathEdges); } - private List addTransferWalkSteps(List steps, List pathEdges) { - List result = new ArrayList<>(); + private List addTransferWalkSteps(List steps, List pathEdges) { + List result = new ArrayList<>(); for (int i = 0; i < steps.size(); i++) { - TransportationResponseDto.Step currentStep = steps.get(i); + InternalStep currentStep = steps.get(i); result.add(currentStep); // 마지막 step이 아니고, 현재 step이 walk가 아닌 경우 if (i < steps.size() - 1 && currentStep.mode() != DirectionType.WALK) { - TransportationResponseDto.Step nextStep = steps.get(i + 1); + InternalStep nextStep = steps.get(i + 1); // 다음 step도 walk가 아닌 경우 (bus -> subway, subway -> bus 등) if (nextStep.mode() != DirectionType.WALK) { @@ -842,7 +871,7 @@ private List addTransferWalkSteps(List addTransferWalkSteps(List nodes, double lat, double lon) { .orElse(null); } - private int calculateTransferCount(List steps) { + private int calculateTransferCount(List steps) { int transferCount = 0; DirectionType previousMode = null; String previousRouteName = null; - for (TransportationResponseDto.Step step : steps) { + for (InternalStep step : steps) { if (step.mode() != DirectionType.WALK && step.mode() != DirectionType.FROM_WAYPOINT && step.mode() != DirectionType.TO_WAYPOINT) { if (previousMode != null) { if (previousMode == step.mode() && From 82421231a18d2d59f50a1012da64dcc6096bc253 Mon Sep 17 00:00:00 2001 From: hyoin Date: Thu, 21 Aug 2025 17:41:10 +0900 Subject: [PATCH 7/9] =?UTF-8?q?[refactor]=20=EB=B2=84=EC=8A=A4=20api?= =?UTF-8?q?=EC=97=90=20=EB=8D=94=EB=AF=B8=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../direction/service/BusInfoService.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/main/java/com/wayble/server/direction/service/BusInfoService.java b/src/main/java/com/wayble/server/direction/service/BusInfoService.java index 75983d0..fec024b 100644 --- a/src/main/java/com/wayble/server/direction/service/BusInfoService.java +++ b/src/main/java/com/wayble/server/direction/service/BusInfoService.java @@ -31,6 +31,11 @@ public TransportationResponseDto.BusInfo getBusInfo(String stationName, Long bus List isLowFloor = new ArrayList<>(); Integer dispatchInterval = null; + // 나중에 서비스키 문제 해결되면 이 함수 호출 제거 + return createDummyBusInfo(stationName, busId, x, y); + + + /* boolean isShuttleBus = false; if (busId != null) { var routeName = routeRepository.findRouteNameById(busId); @@ -97,6 +102,29 @@ public TransportationResponseDto.BusInfo getBusInfo(String stationName, Long bus return new TransportationResponseDto.BusInfo(isShuttleBus, new ArrayList<>(), null); } + return new TransportationResponseDto.BusInfo(isShuttleBus, isLowFloor, dispatchInterval); + */ + + } + + // 나중에 이 함수 제거 + private TransportationResponseDto.BusInfo createDummyBusInfo(String stationName, Long busId, Double x, Double y) { + + // 셔틀버스 여부 확인 (기존 로직 유지) + boolean isShuttleBus = false; + if (busId != null) { + var route = routeRepository.findById(busId); + isShuttleBus = route.isPresent() && route.get().getRouteName().contains("마포"); + } + + // 랜덤 더미 데이터 생성 + List isLowFloor = new ArrayList<>(); + isLowFloor.add(Math.random() < 0.7); + isLowFloor.add(Math.random() < 0.5); + + Integer dispatchInterval = (int) (Math.random() * 15) + 1; + + return new TransportationResponseDto.BusInfo(isShuttleBus, isLowFloor, dispatchInterval); } From 341f573f50c7ff0c8ce2a575a5193b72b599af07 Mon Sep 17 00:00:00 2001 From: hyoin Date: Thu, 21 Aug 2025 18:17:48 +0900 Subject: [PATCH 8/9] =?UTF-8?q?[refactor]=20projection=20interface=C2=A0?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/EdgeBoundingBoxProjection.java | 18 ++++++ .../direction/repository/EdgeRepository.java | 23 +++++-- .../repository/NodeBoundingBoxProjection.java | 11 ++++ .../direction/repository/NodeRepository.java | 11 +++- .../service/TransportationService.java | 63 +++++++++---------- 5 files changed, 86 insertions(+), 40 deletions(-) create mode 100644 src/main/java/com/wayble/server/direction/repository/EdgeBoundingBoxProjection.java create mode 100644 src/main/java/com/wayble/server/direction/repository/NodeBoundingBoxProjection.java diff --git a/src/main/java/com/wayble/server/direction/repository/EdgeBoundingBoxProjection.java b/src/main/java/com/wayble/server/direction/repository/EdgeBoundingBoxProjection.java new file mode 100644 index 0000000..66e911d --- /dev/null +++ b/src/main/java/com/wayble/server/direction/repository/EdgeBoundingBoxProjection.java @@ -0,0 +1,18 @@ +package com.wayble.server.direction.repository; + +import com.wayble.server.direction.entity.type.DirectionType; + +public interface EdgeBoundingBoxProjection { + Long getEdgeId(); + Long getStartNodeId(); + Long getEndNodeId(); + DirectionType getEdgeType(); + String getStartStationName(); + Double getStartLatitude(); + Double getStartLongitude(); + String getEndStationName(); + Double getEndLatitude(); + Double getEndLongitude(); + Long getRouteId(); + String getRouteName(); +} diff --git a/src/main/java/com/wayble/server/direction/repository/EdgeRepository.java b/src/main/java/com/wayble/server/direction/repository/EdgeRepository.java index 3eeae9b..d0dbce8 100644 --- a/src/main/java/com/wayble/server/direction/repository/EdgeRepository.java +++ b/src/main/java/com/wayble/server/direction/repository/EdgeRepository.java @@ -1,6 +1,7 @@ package com.wayble.server.direction.repository; import com.wayble.server.direction.entity.transportation.Edge; +import com.wayble.server.direction.repository.EdgeBoundingBoxProjection; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -16,17 +17,27 @@ public interface EdgeRepository extends JpaRepository { "LEFT JOIN FETCH e.route") List findAllWithNodesAndRoute(); - @Query("SELECT e.id, e.startNode.id, e.endNode.id, e.edgeType, " + - "s.stationName, s.latitude, s.longitude, " + - "en.stationName, en.latitude, en.longitude, " + - "r.routeId, r.routeName " + + @Query("SELECT " + + "e.id as edgeId, " + + "s.id as startNodeId, " + + "en.id as endNodeId, " + + "e.edgeType as edgeType, " + + "s.stationName as startStationName, " + + "s.latitude as startLatitude, " + + "s.longitude as startLongitude, " + + "en.stationName as endStationName, " + + "en.latitude as endLatitude, " + + "en.longitude as endLongitude, " + + "r.routeId as routeId, " + + "r.routeName as routeName " + "FROM Edge e " + "JOIN e.startNode s " + "JOIN e.endNode en " + "LEFT JOIN e.route r " + "WHERE (s.latitude BETWEEN :minLat AND :maxLat AND s.longitude BETWEEN :minLon AND :maxLon) OR " + - "(en.latitude BETWEEN :minLat AND :maxLat AND en.longitude BETWEEN :minLon AND :maxLon)") - List findEdgesInBoundingBox( + "(en.latitude BETWEEN :minLat AND :maxLat AND en.longitude BETWEEN :minLon AND :maxLon) " + + "ORDER BY e.id") + List findEdgesInBoundingBox( @Param("minLat") double minLat, @Param("maxLat") double maxLat, @Param("minLon") double minLon, diff --git a/src/main/java/com/wayble/server/direction/repository/NodeBoundingBoxProjection.java b/src/main/java/com/wayble/server/direction/repository/NodeBoundingBoxProjection.java new file mode 100644 index 0000000..b5a02b9 --- /dev/null +++ b/src/main/java/com/wayble/server/direction/repository/NodeBoundingBoxProjection.java @@ -0,0 +1,11 @@ +package com.wayble.server.direction.repository; + +import com.wayble.server.direction.entity.type.DirectionType; + +public interface NodeBoundingBoxProjection { + Long getId(); + String getStationName(); + DirectionType getNodeType(); + Double getLatitude(); + Double getLongitude(); +} diff --git a/src/main/java/com/wayble/server/direction/repository/NodeRepository.java b/src/main/java/com/wayble/server/direction/repository/NodeRepository.java index ff2ef19..b5b0bc5 100644 --- a/src/main/java/com/wayble/server/direction/repository/NodeRepository.java +++ b/src/main/java/com/wayble/server/direction/repository/NodeRepository.java @@ -1,6 +1,7 @@ package com.wayble.server.direction.repository; import com.wayble.server.direction.entity.transportation.Node; +import com.wayble.server.direction.repository.NodeBoundingBoxProjection; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -9,11 +10,17 @@ public interface NodeRepository extends JpaRepository { - @Query("SELECT n.id, n.stationName, n.nodeType, n.latitude, n.longitude FROM Node n WHERE " + + @Query("SELECT " + + "n.id as id, " + + "n.stationName as stationName, " + + "n.nodeType as nodeType, " + + "n.latitude as latitude, " + + "n.longitude as longitude " + + "FROM Node n WHERE " + "n.latitude BETWEEN :minLat AND :maxLat AND " + "n.longitude BETWEEN :minLon AND :maxLon " + "ORDER BY n.latitude, n.longitude") - List findNodesInBoundingBox( + List findNodesInBoundingBox( @Param("minLat") double minLat, @Param("maxLat") double maxLat, @Param("minLon") double minLon, diff --git a/src/main/java/com/wayble/server/direction/service/TransportationService.java b/src/main/java/com/wayble/server/direction/service/TransportationService.java index 75e9579..a56229c 100644 --- a/src/main/java/com/wayble/server/direction/service/TransportationService.java +++ b/src/main/java/com/wayble/server/direction/service/TransportationService.java @@ -9,7 +9,9 @@ import com.wayble.server.direction.entity.transportation.Node; import com.wayble.server.direction.entity.transportation.Route; import com.wayble.server.direction.entity.type.DirectionType; +import com.wayble.server.direction.repository.EdgeBoundingBoxProjection; import com.wayble.server.direction.repository.EdgeRepository; +import com.wayble.server.direction.repository.NodeBoundingBoxProjection; import com.wayble.server.direction.repository.NodeRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -102,55 +104,52 @@ private List> findMultipleTransportationRoutes(Node startTmp, try { // 1. 공간 필터링을 사용한 데이터 로드 double[] boundingBox = calculateBoundingBox(startTmp, endTmp); - List nodeData = nodeRepository.findNodesInBoundingBox( + List nodeRows = nodeRepository.findNodesInBoundingBox( boundingBox[0], boundingBox[1], boundingBox[2], boundingBox[3] ); - // Object[]를 Node 객체로 변환 - nodes = nodeData.stream() - .map(data -> new Node( - (Long) data[0], // id - (String) data[1], // stationName - (DirectionType) data[2], // nodeType - (Double) data[3], // latitude - (Double) data[4] // longitude + // 프로젝션 인터페이스를 Node 객체로 변환 + nodes = nodeRows.stream() + .map(row -> new Node( + row.getId(), // id + row.getStationName(), // stationName + row.getNodeType(), // nodeType + row.getLatitude(), // latitude + row.getLongitude() // longitude )) .collect(Collectors.toList()); // 최적화된 쿼리 사용: 필요한 컬럼만 조회 - List edgeData = edgeRepository.findEdgesInBoundingBox( + List edgeRows = edgeRepository.findEdgesInBoundingBox( boundingBox[0], boundingBox[1], boundingBox[2], boundingBox[3] ); - // Object[]를 Edge 객체로 변환 - edges = edgeData.stream() - .map(data -> { - // DirectionType 객체 직접 캐스팅 - DirectionType edgeType = (DirectionType) data[3]; - + // 프로젝션 인터페이스를 Edge 객체로 변환 + edges = edgeRows.stream() + .map(row -> { // Node 객체 생성 Node startNode = Node.createNode( - (Long) data[1], // startNode.id - (String) data[4], // startNode.stationName - edgeType, // edgeType - (Double) data[5], // startNode.latitude - (Double) data[6] // startNode.longitude + row.getStartNodeId(), // startNode.id + row.getStartStationName(), // startNode.stationName + row.getEdgeType(), // edgeType + row.getStartLatitude(), // startNode.latitude + row.getStartLongitude() // startNode.longitude ); Node endNode = Node.createNode( - (Long) data[2], // endNode.id - (String) data[7], // endNode.stationName - edgeType, // edgeType - (Double) data[8], // endNode.latitude - (Double) data[9] // endNode.longitude + row.getEndNodeId(), // endNode.id + row.getEndStationName(), // endNode.stationName + row.getEdgeType(), // edgeType + row.getEndLatitude(), // endNode.latitude + row.getEndLongitude() // endNode.longitude ); // Route 객체 생성 (null일 수 있음) Route route = null; - if (data[10] != null) { // routeId가 null이 아닌 경우 + if (row.getRouteId() != null) { // routeId가 null이 아닌 경우 route = Route.createRoute( - (Long) data[10], // routeId - (String) data[11], // routeName - edgeType, // routeType + row.getRouteId(), // routeId + row.getRouteName(), // routeName + row.getEdgeType(), // routeType startNode, endNode ); @@ -158,10 +157,10 @@ private List> findMultipleTransportationRoutes(Node startTmp, // Edge 객체 생성 return Edge.createEdgeWithRoute( - (Long) data[0], // edge.id + row.getEdgeId(), // edge.id startNode, endNode, - edgeType, // edgeType + row.getEdgeType(), // edgeType route ); }) From 98962d393a7b98abe365435bb9d320d29e6bb7a1 Mon Sep 17 00:00:00 2001 From: hyoin Date: Thu, 21 Aug 2025 18:32:27 +0900 Subject: [PATCH 9/9] =?UTF-8?q?[refactor]=20N+1=20=EB=B0=A9=EC=A7=80?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=B4=20VO=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/direction/dto/InternalStep.java | 5 ++--- .../wayble/server/direction/dto/NodeRef.java | 8 ++++++++ .../service/TransportationService.java | 17 +++++++++++------ 3 files changed, 21 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/wayble/server/direction/dto/NodeRef.java diff --git a/src/main/java/com/wayble/server/direction/dto/InternalStep.java b/src/main/java/com/wayble/server/direction/dto/InternalStep.java index d3e6354..a41c663 100644 --- a/src/main/java/com/wayble/server/direction/dto/InternalStep.java +++ b/src/main/java/com/wayble/server/direction/dto/InternalStep.java @@ -3,7 +3,6 @@ import java.util.List; import com.wayble.server.direction.dto.response.TransportationResponseDto; -import com.wayble.server.direction.entity.transportation.Node; import com.wayble.server.direction.entity.type.DirectionType; // 대중교통 길찾기에 사용하기 위한 내부용 DTO @@ -17,6 +16,6 @@ public record InternalStep( String from, String to, Long routeId, - Node startNode, - Node endNode + NodeRef startNode, + NodeRef endNode ) {} \ No newline at end of file diff --git a/src/main/java/com/wayble/server/direction/dto/NodeRef.java b/src/main/java/com/wayble/server/direction/dto/NodeRef.java new file mode 100644 index 0000000..d6d7f54 --- /dev/null +++ b/src/main/java/com/wayble/server/direction/dto/NodeRef.java @@ -0,0 +1,8 @@ +package com.wayble.server.direction.dto; + +public record NodeRef( + Long id, + String stationName, + Double latitude, + Double longitude +) {} diff --git a/src/main/java/com/wayble/server/direction/service/TransportationService.java b/src/main/java/com/wayble/server/direction/service/TransportationService.java index a56229c..6a37f97 100644 --- a/src/main/java/com/wayble/server/direction/service/TransportationService.java +++ b/src/main/java/com/wayble/server/direction/service/TransportationService.java @@ -2,6 +2,7 @@ import com.wayble.server.common.exception.ApplicationException; import com.wayble.server.direction.dto.InternalStep; +import com.wayble.server.direction.dto.NodeRef; import com.wayble.server.direction.dto.TransportationGraphDto; import com.wayble.server.direction.dto.request.TransportationRequestDto; import com.wayble.server.direction.dto.response.TransportationResponseDto; @@ -46,6 +47,10 @@ public class TransportationService { // 공간 필터링 private static final double SPATIAL_BUFFER_KM = 15.0; // 지작점/도착점 주변 15km + private NodeRef toNodeRef(Node node) { + return new NodeRef(node.getId(), node.getStationName(), node.getLatitude(), node.getLongitude()); + } + public TransportationResponseDto findRoutes(TransportationRequestDto request){ TransportationRequestDto.Location origin = request.origin(); @@ -703,8 +708,8 @@ private List enrichRoutesWithServiceInfo(List enrichRoutesWithServiceInfo(List mergeConsecutiveRoutes(List pathEdges, long req } mergedSteps.add(new InternalStep( - DirectionType.WALK, null, null, walkDistance, null, null, fromName, toName, null, walkStartNode, walkEndNode + DirectionType.WALK, null, null, walkDistance, null, null, fromName, toName, null, toNodeRef(walkStartNode), toNodeRef(walkEndNode) )); i = j; continue; @@ -839,8 +844,8 @@ private List mergeConsecutiveRoutes(List pathEdges, long req fromName, toName, routeId, - startNode, - endNode + toNodeRef(startNode), + toNodeRef(endNode) )); i = j;