From 3e1383f24b930eadb85bd3c024d58dbca66cfc95 Mon Sep 17 00:00:00 2001 From: mikekks Date: Wed, 19 Feb 2025 20:42:24 +0900 Subject: [PATCH] =?UTF-8?q?[UNI-327]=20refactor:=20=EB=A1=9C=EC=BB=AC=20?= =?UTF-8?q?=EC=BA=90=EC=8B=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../uniro_backend/map/cache/LightNode.java | 27 ++++ .../uniro_backend/map/cache/LightRoute.java | 24 ++++ .../map/cache/RouteCacheCalculator.java | 129 ++++++++++++++++++ .../uniro_backend/map/service/MapService.java | 16 ++- 4 files changed, 194 insertions(+), 2 deletions(-) create mode 100644 uniro_backend/src/main/java/com/softeer5/uniro_backend/map/cache/LightNode.java create mode 100644 uniro_backend/src/main/java/com/softeer5/uniro_backend/map/cache/LightRoute.java create mode 100644 uniro_backend/src/main/java/com/softeer5/uniro_backend/map/cache/RouteCacheCalculator.java diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/cache/LightNode.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/cache/LightNode.java new file mode 100644 index 00000000..3a734b32 --- /dev/null +++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/cache/LightNode.java @@ -0,0 +1,27 @@ +package com.softeer5.uniro_backend.map.cache; + +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.softeer5.uniro_backend.map.entity.Node; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +class LightNode implements Serializable { + private long id; + private double lat; + private double lng; + + @JsonProperty("core") + private boolean isCore; + + public LightNode(Node node) { + this.id = node.getId(); + this.lat = node.getY(); + this.lng = node.getX(); + this.isCore = node.isCore(); + } +} diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/cache/LightRoute.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/cache/LightRoute.java new file mode 100644 index 00000000..5d3a3cd4 --- /dev/null +++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/cache/LightRoute.java @@ -0,0 +1,24 @@ +package com.softeer5.uniro_backend.map.cache; + +import java.io.Serializable; + +import com.softeer5.uniro_backend.map.entity.Route; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class LightRoute implements Serializable { + private long id; + private double distance; + private LightNode node1; + private LightNode node2; + + public LightRoute(Route route) { + this.id = route.getId(); + this.distance = route.getDistance(); + this.node1 = new LightNode(route.getNode1()); + this.node2 = new LightNode(route.getNode2()); + } +} diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/cache/RouteCacheCalculator.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/cache/RouteCacheCalculator.java new file mode 100644 index 00000000..75de7bf4 --- /dev/null +++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/cache/RouteCacheCalculator.java @@ -0,0 +1,129 @@ +package com.softeer5.uniro_backend.map.cache; + +import static com.softeer5.uniro_backend.common.constant.UniroConst.*; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; + +import org.locationtech.jts.geom.GeometryFactory; +import org.springframework.stereotype.Component; + +import com.softeer5.uniro_backend.common.utils.GeoUtils; +import com.softeer5.uniro_backend.map.dto.response.AllRoutesInfo; +import com.softeer5.uniro_backend.map.dto.response.BuildingRouteResDTO; +import com.softeer5.uniro_backend.map.dto.response.CoreRouteResDTO; +import com.softeer5.uniro_backend.map.dto.response.NodeInfoResDTO; +import com.softeer5.uniro_backend.map.dto.response.RouteCoordinatesInfoResDTO; + +@Component +public class RouteCacheCalculator { + + private final GeometryFactory geometryFactory = GeoUtils.getInstance(); + + public AllRoutesInfo assembleRoutes(List routes) { + Map> adjMap = new HashMap<>(); + Map nodeMap = new HashMap<>(); + List buildingRoutes = new ArrayList<>(); + + for (LightRoute route : routes) { + + if (isBuildingRoute(route)) { + List routeCoordinates = new ArrayList<>(); + routeCoordinates.add(RouteCoordinatesInfoResDTO.of(route.getId(), route.getNode1().getId(), route.getNode2().getId())); + buildingRoutes.add(BuildingRouteResDTO.of(route.getNode1().getId(), route.getNode2().getId(), routeCoordinates)); + continue; + } + + nodeMap.put(route.getNode1().getId(), route.getNode1()); + nodeMap.put(route.getNode2().getId(), route.getNode2()); + + adjMap.computeIfAbsent(route.getNode1().getId(), k -> new ArrayList<>()).add(route); + adjMap.computeIfAbsent(route.getNode2().getId(), k -> new ArrayList<>()).add(route); + + } + + List nodeInfos = nodeMap.entrySet().stream() + .map(entry -> NodeInfoResDTO.of(entry.getKey(), entry.getValue().getLng(), entry.getValue().getLat())) + .toList(); + + ListstartNode = determineStartNodes(adjMap, nodeMap); + + return AllRoutesInfo.of(nodeInfos, getCoreRoutes(adjMap, startNode), buildingRoutes); + } + + private boolean isBuildingRoute(LightRoute route){ + return route.getDistance() > BUILDING_ROUTE_DISTANCE - 1; + } + + private List determineStartNodes(Map> adjMap, + Map nodeMap) { + return nodeMap.values().stream() + .filter(node -> node.isCore() + || (adjMap.containsKey(node.getId()) && adjMap.get(node.getId()).size() == 1)) + .toList(); + } + + private List getCoreRoutes(Map> adjMap, List startNode) { + List result = new ArrayList<>(); + // core node간의 BFS 할 때 방문여부를 체크하는 set + Set visitedCoreNodes = new HashSet<>(); + // 길 중복을 처리하기 위한 set + Set routeSet = new HashSet<>(); + + // BFS 전처리 + Queue nodeQueue = new LinkedList<>(); + startNode.forEach(n-> { + nodeQueue.add(n); + visitedCoreNodes.add(n.getId()); + }); + + // BFS + while(!nodeQueue.isEmpty()) { + // 현재 노드 (코어노드) + LightNode now = nodeQueue.poll(); + for(LightRoute r : adjMap.get(now.getId())) { + // 만약 now-nxt를 연결하는 길이 이미 등록되어있다면, 해당 coreRoute는 이미 등록된 것이므로 continue; + if(routeSet.contains(r.getId())) continue; + + // 다음 노드 (서브노드일수도 있고 코어노드일 수도 있음) + LightNode currentNode = now.getId() == r.getNode1().getId() ? r.getNode2() : r.getNode1(); + + // 코어루트를 이루는 node들을 List로 저장 + List coreRoute = new ArrayList<>(); + coreRoute.add(RouteCoordinatesInfoResDTO.of(r.getId(),now.getId(), currentNode.getId())); + routeSet.add(r.getId()); + + while (true) { + //코어노드를 만나면 queue에 넣을지 판단한 뒤 종료 (제자리로 돌아오는 경우도 포함) + if (currentNode.isCore() || currentNode.getId() == now.getId()) { + if (!visitedCoreNodes.contains(currentNode.getId())) { + visitedCoreNodes.add(currentNode.getId()); + nodeQueue.add(currentNode); + } + break; + } + // 끝점인 경우 종료 + if (adjMap.get(currentNode.getId()).size() == 1) break; + + // 서브노드에 연결된 두 route 중 방문하지 않았던 route를 선택한 뒤, currentNode를 업데이트 + for (LightRoute R : adjMap.get(currentNode.getId())) { + if (routeSet.contains(R.getId())) continue; + LightNode nextNode = R.getNode1().getId() == currentNode.getId() ? R.getNode2() : R.getNode1(); + coreRoute.add(RouteCoordinatesInfoResDTO.of(R.getId(), currentNode.getId(), nextNode.getId())); + routeSet.add(R.getId()); + currentNode = nextNode; + } + } + result.add(CoreRouteResDTO.of(now.getId(), currentNode.getId(), coreRoute)); + } + + } + return result; + } +} diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/service/MapService.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/service/MapService.java index cbecf56d..64aeab7f 100644 --- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/service/MapService.java +++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/service/MapService.java @@ -18,6 +18,8 @@ import com.softeer5.uniro_backend.common.exception.custom.RouteCalculationException; import com.softeer5.uniro_backend.common.exception.custom.RouteException; import com.softeer5.uniro_backend.external.MapClient; +import com.softeer5.uniro_backend.map.cache.LightRoute; +import com.softeer5.uniro_backend.map.cache.RouteCacheCalculator; import com.softeer5.uniro_backend.map.dto.request.CreateRoutesReqDTO; import com.softeer5.uniro_backend.map.entity.Node; @@ -46,11 +48,21 @@ public class MapService { private final RevInfoRepository revInfoRepository; private final RouteCalculator routeCalculator; + private final RouteCacheCalculator routeCacheCalculator; private final MapClient mapClient; + private final Map> cache = new HashMap<>(); + public GetAllRoutesResDTO getAllRoutes(Long univId) { - List routes = routeRepository.findAllRouteByUnivIdWithNodes(univId); + + if(!cache.containsKey(univId)){ + List routes = routeRepository.findAllRouteByUnivIdWithNodes(univId); + List lightRoutes = routes.stream().map(LightRoute::new).toList(); + cache.put(univId, lightRoutes); + } + + List routes = cache.get(univId); // 맵이 존재하지 않을 경우 예외 if(routes.isEmpty()) { @@ -60,7 +72,7 @@ public GetAllRoutesResDTO getAllRoutes(Long univId) { RevInfo revInfo = revInfoRepository.findFirstByUnivIdOrderByRevDesc(univId) .orElseThrow(() -> new RouteException("Revision not found", RECENT_REVISION_NOT_FOUND)); - AllRoutesInfo allRoutesInfo = routeCalculator.assembleRoutes(routes); + AllRoutesInfo allRoutesInfo = routeCacheCalculator.assembleRoutes(routes); return GetAllRoutesResDTO.of(allRoutesInfo.getNodeInfos(), allRoutesInfo.getCoreRoutes(), allRoutesInfo.getBuildingRoutes(), revInfo.getRev());