Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d6c3570
[feat] 서초구 노드, 간선 그래프 초기 구현
zyovn Jul 24, 2025
0c6d677
[feat] 웨이블 추천 경로 길찾기 초기 구현
zyovn Jul 24, 2025
917b95c
[feat] 웨이블 추천 경로 구현
zyovn Jul 24, 2025
fce8d5e
[fix] requestParam node -> 위도, 경도로 수정
zyovn Jul 28, 2025
e677432
[refactor] 클래스 분리
zyovn Jul 29, 2025
e73cef2
[fix] 변수 수정 및 메서드 위치 변경
zyovn Jul 29, 2025
8428d40
[fix] 에러 메시지 수정
zyovn Jul 29, 2025
b87ea14
[feat] 웨이블 경로 더미 데이터 수정
zyovn Jul 29, 2025
d0d7d56
[fix] polyline 중복 좌표 제거
zyovn Jul 29, 2025
75d5029
[remove] 불필요한 파일 삭제
zyovn Jul 29, 2025
47c6082
[fix] 웨이블 추천 경로 마커 수정
zyovn Jul 29, 2025
f348bc6
[feat] 소요 시간 추가
zyovn Jul 29, 2025
a9902b3
[refactor] 메서드 위치 변경
zyovn Jul 29, 2025
b1b037b
[fix] 시간 및 거리 int로 수정
zyovn Jul 29, 2025
fe29920
[fix] securityConfig, docker-els.yml 수정
zyovn Jul 29, 2025
5d056da
[fix] 소요 시간 수정
zyovn Jul 29, 2025
d865a8e
[docs] swagger 작성
zyovn Jul 29, 2025
13d89e8
Merge branch 'develop' of https://github.com/Wayble-Project/wayble-sp…
zyovn Jul 29, 2025
28fdfa3
[fix] 피드백 반영
zyovn Jul 29, 2025
62caa26
[fix] 피드백 반영
zyovn Jul 29, 2025
3bb249a
[fix] 피드백 반영
zyovn Jul 29, 2025
14e5403
[fix] controller 수정
zyovn Jul 29, 2025
1a87492
[refactor] UserController에서 사용하지않는 import 제거
seung-in-Yoo Jul 30, 2025
db99290
[feat] 엔티티에 리스트를 저장하기 위한 converter 구현
seung-in-Yoo Jul 30, 2025
c327e27
[refactor] 유저 엔티티에 장애유형, 이동보조수단 필드 List로 변경
seung-in-Yoo Jul 30, 2025
5a9475f
[refactor] 유저 정보 등록 관련 Dto 필드 List로 변경 (장애유형,이동보조수단)
seung-in-Yoo Jul 30, 2025
111d37d
[refactor] 유저 정보 수정 관련 Dto 필드 List로 변경 (장애유형,이동보조수단)
seung-in-Yoo Jul 30, 2025
b1c6711
[refactor] ObjectMapper static 선언 및 예외 처리 시 로깅 추가
seung-in-Yoo Jul 30, 2025
4bf2c83
Merge pull request #86 from Wayble-Project/feature/seungin
seung-in-Yoo Jul 30, 2025
3a3bbc5
[fix] 피드백 반영
zyovn Jul 30, 2025
d8bef71
[fix] 피드백 반영
zyovn Jul 30, 2025
2eae9b1
Merge pull request #84 from Wayble-Project/feature/jeongbin
zyovn Jul 30, 2025
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
@@ -0,0 +1,38 @@
package com.wayble.server.common.converter;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import lombok.extern.slf4j.Slf4j;

import java.util.Collections;
import java.util.List;

@Slf4j
@Converter
public class StringListConverter implements AttributeConverter<List<String>, String> {
private static final ObjectMapper objectMapper = new ObjectMapper();

@Override
public String convertToDatabaseColumn(List<String> list) {
try {
return list == null ? "[]" : objectMapper.writeValueAsString(list);
} catch (Exception e) {
log.warn("Failed to convert list to JSON string: {}", list, e);
return "[]";
}
}

@Override
public List<String> convertToEntityAttribute(String json) {
try {
if (json == null || json.isBlank()) return Collections.emptyList();
return objectMapper.readValue(json, new TypeReference<List<String>>() {});
} catch (Exception e) {
log.warn("Failed to convert JSON string to list: {}", json, e);
return Collections.emptyList();
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.wayble.server.common.response.CommonResponse;
import com.wayble.server.direction.controller.swagger.WalkingSwagger;
import com.wayble.server.direction.dto.response.WayblePathResponse;
import com.wayble.server.direction.external.tmap.dto.request.TMapRequest;
import com.wayble.server.direction.external.tmap.dto.response.TMapParsingResponse;
import com.wayble.server.direction.service.WalkingService;
Expand All @@ -13,13 +14,13 @@

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/directions/walking")
@RequestMapping("/api/v1/directions")
public class WalkingController implements WalkingSwagger {

private final WalkingService walkingService;

@Override
@GetMapping()
@GetMapping("/walking")
public CommonResponse<TMapParsingResponse> callTMapApi(
@RequestParam double startX,
@RequestParam double startY,
Expand All @@ -31,4 +32,15 @@ public CommonResponse<TMapParsingResponse> callTMapApi(
TMapRequest request = new TMapRequest(startX, startY, endX, endY, startName, endName);
return CommonResponse.success(walkingService.callTMapApi(request));
}

@Override
@GetMapping("/wayble")
public CommonResponse<WayblePathResponse> getWayblePath(
@RequestParam double startLat,
@RequestParam double startLon,
@RequestParam double endLat,
@RequestParam double endLon
) {
return CommonResponse.success(walkingService.findWayblePath(startLat, startLon, endLat, endLon));
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.wayble.server.direction.controller.swagger;

import com.wayble.server.common.response.CommonResponse;
import com.wayble.server.direction.dto.response.WayblePathResponse;
import com.wayble.server.direction.external.tmap.dto.response.TMapParsingResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
Expand Down Expand Up @@ -28,4 +29,21 @@ CommonResponse<TMapParsingResponse> callTMapApi(
@RequestParam String startName,
@RequestParam String endName
);

@Operation(
summary = "웨이블 추천 경로 길찾기 API",
description = "웨이블 마커를 활용하여 웨이블 추천 경로 길찾기를 진행합니다."
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "웨이블 추천 경로 길찾기가 성공적으로 실행되었습니다."
)
})
CommonResponse<WayblePathResponse> getWayblePath(
@RequestParam double startLat,
@RequestParam double startLon,
@RequestParam double endLat,
@RequestParam double endLon
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.wayble.server.direction.dto.response;

import com.wayble.server.direction.entity.type.Type;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;

import java.util.List;

@Builder
@Schema(description = "웨이블 추천 경로 API")
public record WayblePathResponse(

@Schema(description = "총 거리", example = "1365")
int distance,

@Schema(description = "총 소요 시간", example = "909")
int time,

@Schema(description = "위치", example = "[{\"lat\":37.4941736,\"lon\":127.0247425,\"type\":\"RAMP\"}]")
List<WayblePoint> points,

@Schema(description = "좌표 리스트", example = "[[127.0247425,37.4941736],[127.0249966,37.4942539]]")
List<double[]> polyline
) {
public record WayblePoint(
@Schema(description = "위도", example = "37.4941736")
double lat,

@Schema(description = "경도", example = "127.0247425")
double lon,

@Schema(description = "웨이블 마커 타입", example = "RAMP")
Type type
) {}

public static WayblePathResponse of(
int distance,
int time,
List<WayblePoint> points,
List<double[]> polyline
) {
return WayblePathResponse.builder()
.distance(distance)
.time(time)
.points(points)
.polyline(polyline)
.build();
}
}
11 changes: 11 additions & 0 deletions src/main/java/com/wayble/server/direction/entity/Edge.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.wayble.server.direction.entity;

import java.util.List;

public record Edge(
long from,
long to,
double length,
List<double[]> geometry
) {
}
8 changes: 8 additions & 0 deletions src/main/java/com/wayble/server/direction/entity/Node.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.wayble.server.direction.entity;

public record Node(
Long id,
double lat,
double lon
) {
}
11 changes: 11 additions & 0 deletions src/main/java/com/wayble/server/direction/entity/WaybleMarker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.wayble.server.direction.entity;

import com.wayble.server.direction.entity.type.Type;

public record WaybleMarker(
Long id,
double lat,
double lon,
Type type
) {
}
22 changes: 22 additions & 0 deletions src/main/java/com/wayble/server/direction/entity/type/Type.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.wayble.server.direction.entity.type;

import lombok.Getter;

@Getter
public enum Type {
WHEELCHAIR_CHARGER("휠체어 충전소"),
NO_THRESHOLD("문턱 없음"),
RAMP("경사로"),
TABLE_SEAT("테이블석"),
ELEVATOR("엘리베이터"),
FIRST_FLOOR("1층"),
ACCESSIBLE_TOILET("장애인 화장실"),
WHEELCHAIR_LIFT("휠체어 리프트"),
NONE("해당없음");

private final String description;

Type(String description) {
this.description = description;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
public enum WalkingErrorCase implements ErrorCase {

T_MAP_API_FAILED(500, 8001, "T MAP API 호출에 실패했습니다."),
GRAPH_FILE_NOT_FOUND(500, 8002, "그래프 파일을 찾을 수 없습니다."),
GRAPH_INIT_FAILED(500, 8003, "그래프 초기화에 실패했습니다."),
NODE_NOT_FOUND(400, 8004, "해당 위도, 경도 근처의 노드가 존재하지 않습니다."),
;

private final Integer httpStatusCode;
Expand Down
109 changes: 109 additions & 0 deletions src/main/java/com/wayble/server/direction/init/GraphInit.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package com.wayble.server.direction.init;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wayble.server.common.exception.ApplicationException;
import com.wayble.server.direction.entity.Edge;
import com.wayble.server.direction.entity.Node;
import com.wayble.server.direction.entity.WaybleMarker;
import com.wayble.server.direction.entity.type.Type;
import com.wayble.server.direction.exception.WalkingErrorCase;
import com.wayble.server.direction.service.util.HaversineUtil;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Component
public class GraphInit {

private List<Node> nodes;
private List<Edge> edges;
private List<WaybleMarker> markers;
private Map<Long, List<Edge>> adjacencyList;
private Map<Long, Node> nodeMap;
private Map<Long, Type> markerMap;

@PostConstruct
public void init() {
ObjectMapper objectMapper = new ObjectMapper();

try {
// 그래프
try (InputStream graphStream = getClass().getResourceAsStream("/seocho_pedestrian.json")) {
if (graphStream == null) {
throw new ApplicationException(WalkingErrorCase.GRAPH_FILE_NOT_FOUND);
}
JsonNode root = objectMapper.readTree(graphStream);
nodes = Arrays.asList(objectMapper.convertValue(root.get("nodes"), Node[].class));
edges = Arrays.asList(objectMapper.convertValue(root.get("edges"), Edge[].class));

nodeMap = nodes.stream().collect(Collectors.toMap(Node::id, node -> node));
}

// 웨이블 마커
try (InputStream markerStream = getClass().getResourceAsStream("/wayble_markers.json")) {
markers = markerStream != null
? objectMapper.readValue(markerStream, new TypeReference<>() {})
: new ArrayList<>();
}
markerMap = findWaybleMarkers();
adjacencyList = buildAdjacencyList();
} catch (IOException e) {
log.error("🚨 그래프 초기화 실패: {}", e.getMessage());
throw new ApplicationException(WalkingErrorCase.GRAPH_INIT_FAILED);
}
}
Comment on lines +33 to +62
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

초기화 시 데이터 검증을 추가하세요.

그래프 데이터 로드 후 다음과 같은 검증을 추가하면 런타임 오류를 방지할 수 있습니다:

            markerMap = findWaybleMarkers();
            adjacencyList = buildAdjacencyList();
+           
+           // 데이터 검증
+           if (nodes.isEmpty() || edges.isEmpty()) {
+               log.error("🚨 그래프 데이터가 비어있습니다");
+               throw new ApplicationException(WalkingErrorCase.GRAPH_INIT_FAILED);
+           }
+           
+           log.info("✅ 그래프 초기화 완료: 노드 {}, 엣지 {}, 마커 {}", 
+                   nodes.size(), edges.size(), markers.size());
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@PostConstruct
public void init() {
ObjectMapper objectMapper = new ObjectMapper();
try {
// 그래프
try (InputStream graphStream = getClass().getResourceAsStream("/seocho_pedestrian.json")) {
if (graphStream == null) {
throw new ApplicationException(WalkingErrorCase.GRAPH_FILE_NOT_FOUND);
}
JsonNode root = objectMapper.readTree(graphStream);
nodes = Arrays.asList(objectMapper.convertValue(root.get("nodes"), Node[].class));
edges = Arrays.asList(objectMapper.convertValue(root.get("edges"), Edge[].class));
nodeMap = nodes.stream().collect(Collectors.toMap(Node::id, node -> node));
}
// 웨이블 마커
try (InputStream markerStream = getClass().getResourceAsStream("/wayble_markers.json")) {
markers = markerStream != null
? objectMapper.readValue(markerStream, new TypeReference<>() {})
: new ArrayList<>();
}
markerMap = findWaybleMarkers();
adjacencyList = buildAdjacencyList();
} catch (IOException e) {
log.error("🚨 그래프 초기화 실패: {}", e.getMessage());
throw new ApplicationException(WalkingErrorCase.GRAPH_INIT_FAILED);
}
}
@PostConstruct
public void init() {
ObjectMapper objectMapper = new ObjectMapper();
try {
// 그래프
try (InputStream graphStream = getClass().getResourceAsStream("/seocho_pedestrian.json")) {
if (graphStream == null) {
throw new ApplicationException(WalkingErrorCase.GRAPH_FILE_NOT_FOUND);
}
JsonNode root = objectMapper.readTree(graphStream);
nodes = Arrays.asList(objectMapper.convertValue(root.get("nodes"), Node[].class));
edges = Arrays.asList(objectMapper.convertValue(root.get("edges"), Edge[].class));
nodeMap = nodes.stream().collect(Collectors.toMap(Node::id, node -> node));
}
// 웨이블 마커
try (InputStream markerStream = getClass().getResourceAsStream("/wayble_markers.json")) {
markers = markerStream != null
? objectMapper.readValue(markerStream, new TypeReference<>() {})
: new ArrayList<>();
}
markerMap = findWaybleMarkers();
adjacencyList = buildAdjacencyList();
// 데이터 검증
if (nodes.isEmpty() || edges.isEmpty()) {
log.error("🚨 그래프 데이터가 비어있습니다");
throw new ApplicationException(WalkingErrorCase.GRAPH_INIT_FAILED);
}
log.info("✅ 그래프 초기화 완료: 노드 {}, 엣지 {}, 마커 {}",
nodes.size(), edges.size(), markers.size());
} catch (IOException e) {
log.error("🚨 그래프 초기화 실패: {}", e.getMessage());
throw new ApplicationException(WalkingErrorCase.GRAPH_INIT_FAILED);
}
}
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/direction/init/GraphInit.java between lines
33 and 62, after loading the graph data, add validation checks to ensure that
the nodes and edges lists are not null or empty and that nodeMap contains all
expected keys. If any validation fails, throw an ApplicationException with an
appropriate error case to prevent runtime errors during graph usage.


private Map<Long, List<Edge>> buildAdjacencyList() {
Map<Long, List<Edge>> adjacencyList = new HashMap<>();

for (Edge edge : edges) {
boolean isWaybleMarker = markerMap.containsKey(edge.from()) || markerMap.containsKey(edge.to());
double distance = isWaybleMarker ? edge.length() * 0.5 : edge.length();

// 양방향
adjacencyList.computeIfAbsent(edge.from(), k -> new ArrayList<>())
.add(new Edge(edge.from(), edge.to(), distance, edge.geometry()));
adjacencyList.computeIfAbsent(edge.to(), k -> new ArrayList<>())
.add(new Edge(edge.to(), edge.from(), distance, edge.geometry()));
}
return adjacencyList;
}

private Map<Long, Type> findWaybleMarkers() {
Map<Long, Type> waybleMarkers = new HashMap<>();

for (WaybleMarker marker : markers) {
long nearNode = nodes.stream()
.min(Comparator.comparingDouble(
n -> HaversineUtil.haversine(marker.lat(), marker.lon(), n.lat(), n.lon())
))
.map(Node::id)
.orElse(marker.id());

if (nearNode != marker.id()) {
waybleMarkers.put(nearNode, marker.type());
}
}
return waybleMarkers;
}
Comment on lines +80 to +96
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

마커와 노드 ID 비교 로직에 문제가 있습니다.

findWaybleMarkers 메서드에서 마커 ID와 노드 ID를 비교하는 로직이 잘못되었습니다. 마커와 노드는 서로 다른 엔티티이므로 ID 공간이 다를 수 있습니다.

다음과 같이 수정하세요:

-                .orElse(marker.id());
+                .orElseThrow(() -> new ApplicationException(WalkingErrorCase.NODE_NOT_FOUND));

-        if (nearNode != marker.id()) {
-            waybleMarkers.put(nearNode, marker.type());
-        }
+        waybleMarkers.put(nearNode, marker.type());

또는 거리 임계값을 설정하여 너무 먼 노드는 제외하는 로직을 추가하는 것도 고려해보세요.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private Map<Long, Type> findWaybleMarkers() {
Map<Long, Type> waybleMarkers = new HashMap<>();
for (WaybleMarker marker : markers) {
long nearNode = nodes.stream()
.min(Comparator.comparingDouble(
n -> HaversineUtil.haversine(marker.lat(), marker.lon(), n.lat(), n.lon())
))
.map(Node::id)
.orElse(marker.id());
if (nearNode != marker.id()) {
waybleMarkers.put(nearNode, marker.type());
}
}
return waybleMarkers;
}
private Map<Long, Type> findWaybleMarkers() {
Map<Long, Type> waybleMarkers = new HashMap<>();
for (WaybleMarker marker : markers) {
long nearNode = nodes.stream()
.min(Comparator.comparingDouble(
n -> HaversineUtil.haversine(marker.lat(), marker.lon(), n.lat(), n.lon())
))
.map(Node::id)
.orElseThrow(() -> new ApplicationException(WalkingErrorCase.NODE_NOT_FOUND));
waybleMarkers.put(nearNode, marker.type());
}
return waybleMarkers;
}
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/direction/init/GraphInit.java between lines
80 and 96, the current logic incorrectly compares marker IDs with node IDs,
which belong to different ID spaces. To fix this, avoid directly comparing these
IDs; instead, implement a distance threshold to determine if a node is close
enough to a marker before associating them. Modify the method to calculate the
nearest node's distance to the marker and only add it to waybleMarkers if this
distance is below a defined threshold, thereby excluding nodes that are too far.


public Map<Long, Node> getNodeMap() {
return Collections.unmodifiableMap(nodeMap);
}

public Map<Long, Type> getMarkerMap() {
return Collections.unmodifiableMap(markerMap);
}

public Map<Long, List<Edge>> getGraph() {
return Collections.unmodifiableMap(adjacencyList);
}
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
package com.wayble.server.direction.service;

import com.wayble.server.common.exception.ApplicationException;
import com.wayble.server.direction.dto.response.WayblePathResponse;
import com.wayble.server.direction.entity.Node;
import com.wayble.server.direction.exception.WalkingErrorCase;
import com.wayble.server.direction.external.tmap.TMapClient;
import com.wayble.server.direction.external.tmap.dto.request.TMapRequest;
import com.wayble.server.direction.external.tmap.dto.response.TMapParsingResponse;
import com.wayble.server.direction.external.tmap.dto.response.TMapResponse;
import com.wayble.server.direction.external.tmap.mapper.TMapMapper;
import com.wayble.server.direction.init.GraphInit;
import com.wayble.server.direction.service.util.HaversineUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.Comparator;

@Slf4j
@Service
@RequiredArgsConstructor
public class WalkingService {

private final TMapClient tMapClient;
private final TMapMapper tMapMapper;
private final GraphInit graphInit;
private final WaybleDijkstraService waybleDijkstraService;

public TMapParsingResponse callTMapApi(TMapRequest request) {
try {
Expand All @@ -29,4 +37,25 @@ public TMapParsingResponse callTMapApi(TMapRequest request) {
throw new ApplicationException(WalkingErrorCase.T_MAP_API_FAILED);
}
}

public WayblePathResponse findWayblePath(
double startLat,
double startLon,
double endLat,
double endLon
) {
long startNode = findNearestNode(startLat, startLon);
long endNode = findNearestNode(endLat, endLon);

return waybleDijkstraService.createWayblePath(startNode, endNode);
}

private long findNearestNode(double lat, double lon) {
return graphInit.getNodeMap().values().stream()
.min(Comparator.comparingDouble(
node -> HaversineUtil.haversine(lat, lon, node.lat(), node.lon())
))
.map(Node::id)
.orElseThrow(() -> new ApplicationException(WalkingErrorCase.NODE_NOT_FOUND));
}
}
Loading
Loading