Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.wayble.server.direction.entity.transportation.Edge;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;
Expand All @@ -14,4 +15,17 @@ public interface EdgeRepository extends JpaRepository<Edge, Long> {
"JOIN FETCH e.endNode " +
"LEFT JOIN FETCH e.route")
List<Edge> findAllWithNodesAndRoute();

@Query("SELECT DISTINCT e FROM Edge e " +
"JOIN FETCH e.startNode s " +
"JOIN FETCH e.endNode en " +
"LEFT JOIN FETCH e.route " +
"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<Edge> findEdgesInBoundingBox(
@Param("minLat") double minLat,
@Param("maxLat") double maxLat,
@Param("minLon") double minLon,
@Param("maxLon") double maxLon
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@

import com.wayble.server.direction.entity.transportation.Node;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;

public interface NodeRepository extends JpaRepository<Node, Long> {

@Query("SELECT n FROM Node n WHERE " +
"n.latitude BETWEEN :minLat AND :maxLat AND " +
"n.longitude BETWEEN :minLon AND :maxLon")
List<Node> findNodesInBoundingBox(
@Param("minLat") double minLat,
@Param("maxLat") double maxLat,
@Param("minLon") double minLon,
@Param("maxLon") double maxLon
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ 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<Boolean> isLowFloor = new ArrayList<>();
Integer dispatchInterval = null;

Expand Down Expand Up @@ -79,6 +84,7 @@ public TransportationResponseDto.BusInfo getBusInfo(String stationName, Long bus
dispatchInterval = Integer.parseInt(item.term());
} catch (NumberFormatException e) {
dispatchInterval = 0;
log.warn("⚠️ 배차간격 파싱 실패: {}", item.term());
}

count++;
Expand All @@ -89,6 +95,30 @@ 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) {
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<Boolean> 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);
}

Expand Down Expand Up @@ -165,6 +195,7 @@ private StationSearchResponse fetchStationByName(String stationName) {

private StationSearchResponse.StationItem findClosestStation(List<StationSearchResponse.StationItem> stations, Double x, Double y) {
if (stations == null || stations.isEmpty()) {
log.warn("❌ 정류소 목록이 비어있음");
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ public class TransportationService {
private static final int MAX_NEARBY_NODES = 5; // 출발지/도착지 주변에서 고려할 최대 정류장 수
private static final int MAX_DIJKSTRA_VISITS = 5000; // 다익스트라 알고리즘에서 방문할 수 있는 최대 노드 수 (무한 루프 방지)
private static final int MAX_ROUTES = 5; // 찾을 최대 경로 수

// 공간 필터링
private static final double SPATIAL_BUFFER_KM = 15.0; // 지작점/도착점 주변 15km

public TransportationResponseDto findRoutes(TransportationRequestDto request){

Expand Down Expand Up @@ -88,25 +91,54 @@ private TransportationResponseDto.Route createRoute(List<TransportationResponseD
}

private List<List<TransportationResponseDto.Step>> findMultipleTransportationRoutes(Node startTmp, Node endTmp){
// 1. 데이터 로드
List<Node> nodes = new ArrayList<>(nodeRepository.findAll());
List<Edge> edges = new ArrayList<>(edgeRepository.findAllWithNodesAndRoute());

// 2. 가장 가까운 정류장 찾기
Node nearestToStart = findNearestNode(nodes, startTmp.getLatitude(), startTmp.getLongitude());
Node nearestToEnd = findNearestNode(nodes, endTmp.getLatitude(), endTmp.getLongitude());

if (nearestToStart == null || nearestToEnd == null) {
try {
// 1. 공간 필터링을 사용한 데이터 로드
double[] boundingBox = calculateBoundingBox(startTmp, endTmp);
List<Node> nodes = nodeRepository.findNodesInBoundingBox(
boundingBox[0], boundingBox[1], boundingBox[2], boundingBox[3]
);
List<Edge> edges = edgeRepository.findEdgesInBoundingBox(
boundingBox[0], boundingBox[1], boundingBox[2], boundingBox[3]
);

log.debug("Spatial filtering loaded {} nodes and {} edges", nodes.size(), edges.size());

// 2. 가장 가까운 정류장 찾기
Node nearestToStart = findNearestNode(nodes, startTmp.getLatitude(), startTmp.getLongitude());
Node nearestToEnd = findNearestNode(nodes, endTmp.getLatitude(), endTmp.getLongitude());

if (nearestToStart == null || nearestToEnd == null) {
throw new ApplicationException(PATH_NOT_FOUND);
}

// 3. 임시 노드 추가
nodes.add(startTmp);
nodes.add(endTmp);

// 4. 그래프 빌드 및 여러 경로 찾기
TransportationGraphDto graphData = buildGraph(nodes, edges, startTmp, endTmp);
List<List<TransportationResponseDto.Step>> result = findMultipleOptimalRoutes(
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);
}
}

private double[] calculateBoundingBox(Node start, Node end) {
double minLat = Math.min(start.getLatitude(), end.getLatitude()) - SPATIAL_BUFFER_KM / 111.0;
double maxLat = Math.max(start.getLatitude(), end.getLatitude()) + SPATIAL_BUFFER_KM / 111.0;
double minLon = Math.min(start.getLongitude(), end.getLongitude()) - SPATIAL_BUFFER_KM / (111.0 * Math.cos(Math.toRadians(start.getLatitude())));
double maxLon = Math.max(start.getLongitude(), end.getLongitude()) + SPATIAL_BUFFER_KM / (111.0 * Math.cos(Math.toRadians(start.getLatitude())));

// 3. 임시 노드 추가
nodes.add(startTmp);
nodes.add(endTmp);

// 4. 그래프 빌드 및 여러 경로 찾기
TransportationGraphDto graphData = buildGraph(nodes, edges, startTmp, endTmp);
return findMultipleOptimalRoutes(graphData.graph(), startTmp, endTmp, graphData.weightMap(), nodes, nearestToStart, nearestToEnd);
return new double[]{minLat, maxLat, minLon, maxLon};
}

private List<List<TransportationResponseDto.Step>> findMultipleOptimalRoutes(
Expand Down Expand Up @@ -312,15 +344,18 @@ private TransportationGraphDto buildGraph(List<Node> nodes, List<Edge> edges, No
Map<Long, List<Edge>> graph = new HashMap<>();
Map<Pair<Long, Long>, Integer> weightMap = new HashMap<>();

// 1. 노드 초기화
for (Node node : nodes) {
Long nodeId = node.getId();
if (nodeId != null) {
graph.put(nodeId, new ArrayList<>());
}
// 1. 노드 ID를 Set으로 변환해 빠른 검색
Set<Long> nodeIds = nodes.stream()
.map(Node::getId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());

// 2. 노드 초기화
for (Long nodeId : nodeIds) {
graph.put(nodeId, new ArrayList<>());
}

// 2. 기존 엣지 추가 및 가중치 계산
// 3. 기존 엣지 추가 및 가중치 계산 (필터링된 노드만)
for (Edge edge : edges) {
if (edge == null) continue;

Expand All @@ -331,7 +366,8 @@ private TransportationGraphDto buildGraph(List<Node> nodes, List<Edge> edges, No
Long startId = start.getId();
Long endId = end.getId();

if (!graph.containsKey(startId)) continue;
// 공간 필터링된 노드들만 처리
if (!nodeIds.contains(startId) || !nodeIds.contains(endId)) continue;

graph.get(startId).add(edge);

Expand All @@ -342,7 +378,7 @@ private TransportationGraphDto buildGraph(List<Node> nodes, List<Edge> edges, No
weightMap.put(Pair.of(startId, endId), weight);
}

// 3. 출발지/도착지 도보 연결 추가
// 4. 출발지/도착지 도보 연결 추가
addOriginDestinationWalkConnections(graph, weightMap, nodes, startTmp, endTmp);

return new TransportationGraphDto(graph, weightMap);
Expand Down Expand Up @@ -393,8 +429,21 @@ private void addOriginDestinationWalkConnections(Map<Long, List<Edge>> graph, Ma
}

private List<Node> findNearbyNodes(List<Node> nodes, double lat, double lon, int maxDistanceMeters) {
// 대략적인 거리 필터링
double maxDistanceKm = maxDistanceMeters / 1000.0;

return nodes.stream()
.filter(node -> {
// 빠른 거리 계산 (대략적)
double latDiff = Math.abs(lat - node.getLatitude());
double lonDiff = Math.abs(lon - node.getLongitude());

// 필터링 (1도 ≈ 111km)
if (latDiff > maxDistanceKm / 111.0 || lonDiff > maxDistanceKm / (111.0 * Math.cos(Math.toRadians(lat)))) {
return false;
}

// 정확한 거리 계산
double distance = haversine(lat, lon, node.getLatitude(), node.getLongitude()) * METER_CONVERSION;
return distance <= maxDistanceMeters;
})
Expand Down Expand Up @@ -680,7 +729,82 @@ private List<TransportationResponseDto.Step> mergeConsecutiveRoutes(List<Edge> p
i = j;
}

return mergedSteps;
// 환승 시 walk step 추가
return addTransferWalkSteps(mergedSteps, pathEdges);
}

private List<TransportationResponseDto.Step> addTransferWalkSteps(List<TransportationResponseDto.Step> steps, List<Edge> pathEdges) {
List<TransportationResponseDto.Step> result = new ArrayList<>();

for (int i = 0; i < steps.size(); i++) {
TransportationResponseDto.Step 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);

// 다음 step도 walk가 아닌 경우 (bus -> subway, subway -> bus 등)
if (nextStep.mode() != DirectionType.WALK) {
// 환승 walk step 추가
String transferFrom = currentStep.to();
String transferTo = nextStep.from();

// 이전 step의 도착지와 다음 step의 출발지 사이의 직선거리 계산
int walkDistance = calculateTransferWalkDistance(transferFrom, transferTo, pathEdges);

TransportationResponseDto.Step walkStep = new TransportationResponseDto.Step(
DirectionType.WALK,
null,
null,
walkDistance,
null,
null,
transferFrom,
transferTo
);

result.add(walkStep);
}
}
}

return result;
}

private int calculateTransferWalkDistance(String fromStation, String toStation, List<Edge> pathEdges) {
// pathEdges에서 해당 정류장의 노드 정보 찾기
Node fromNode = null;
Node toNode = null;

for (Edge edge : pathEdges) {
if (edge.getStartNode() != null && edge.getStartNode().getStationName() != null &&
edge.getStartNode().getStationName().equals(fromStation)) {
fromNode = edge.getStartNode();
}
if (edge.getEndNode() != null && edge.getEndNode().getStationName() != null &&
edge.getEndNode().getStationName().equals(fromStation)) {
fromNode = edge.getEndNode();
}
if (edge.getStartNode() != null && edge.getStartNode().getStationName() != null &&
edge.getStartNode().getStationName().equals(toStation)) {
toNode = edge.getStartNode();
}
if (edge.getEndNode() != null && edge.getEndNode().getStationName() != null &&
edge.getEndNode().getStationName().equals(toStation)) {
toNode = edge.getEndNode();
}
}

if (fromNode != null && toNode != null) {
double distanceKm = haversine(
fromNode.getLatitude(), fromNode.getLongitude(),
toNode.getLatitude(), toNode.getLongitude()
);
return (int) (distanceKm * 1000); // km를 m로 변환
}

return 0; // 노드를 찾지 못한 경우
}

private String getNodeName(Node node) {
Expand Down
Loading