경로를 저장하는 이유는 해당 유저가 다녀온 길을 다시 보여주고 안내하기 위해서이다.
고로 다시 경로를 보여주는데 필요한 데이터만 쓰기 편한 형식으로 저장하면 된다.
이전의 카카오에서 주는 모든 response를 담는 KakaoRouteAllResponseDto를 다시쓰려면 너무나 많은 Entity를 만들어야 한다. 고로 새로운 ResponseDto를 만들기로 하였다.
그전에 navigation.js의 일부 코드를 참고해야 한다. 아래 코드를 보자.
// 사용자가 반경기반 랜덤 길 찾기 버튼을 눌렀을 때의 동작----------------------------------------------------------------------------------------------------//
document.getElementById('all-random-search-form').addEventListener('submit', function(e) {
e.preventDefault(); // 기본 submit 동작을 막습니다.
var originAddress = document.getElementById('all-random-originAddress').value;
var redius = document.getElementById('all-random-redius').value;
fetch('/all-random-route?originAddress=' + originAddress + '&redius=' + redius)
.then(response => response.json())
.then(data => {
// data는 KakaoRouteAllResponseDto 객체
clearPolylines(); // 기존의 선들을 모두 제거
if (!map) {
map = new kakao.maps.Map(document.getElementById('map'), {
level: 3
});
}
// 경로 정보(routes)의 각 섹션(section)별로 반복하여 처리합니다.
for (let route of data.routes) {
for (let section of route.sections) {
// 각 섹션의 경계 상자(bound) 정보를 가져옵니다.
let bound = section.bound;
// 카카오 지도에 섹션을 표시합니다.
var bounds = new kakao.maps.LatLngBounds(
new kakao.maps.LatLng(bound.min_y, bound.min_x),
new kakao.maps.LatLng(bound.max_y, bound.max_x)
);
map.setBounds(bounds);
// polyline 생성
for(let road of section.roads){
let path = [];
for(let i=0; i<road.vertexes.length; i+=2){
console.log("vertexes: ", road.vertexes[i], road.vertexes[i+1]);
path.push(new kakao.maps.LatLng(road.vertexes[i+1], road.vertexes[i]));
}
let polyline = new kakao.maps.Polyline({
path: path,
strokeWeight: 5,
strokeColor: '#007bff',
strokeOpacity: 0.7,
strokeStyle: 'solid'
});
polyline.setMap(map);
polylines.push(polyline); // 선을 배열에 추가
}
}
}
});
});
for문이 많이 돌아가는 것 같지만 response를 뽑아보면 실상은 그렇지 않다.
data의 routes는 여러 경로를 추천해 주는 것이 아니니, 하나의 route로 구성 되어있다.
section별 road는 필요하지만, bound에 필요한 값들도 여러개가 아닌 딱 한가지 경우의 값들만 존재한다.
고로 for문을 돌면서 저장하고, 다시 for문을 돌면서 써야하는 것은 sections.roads의 vertexes 밖에 없다.
그럼 먼저 필요한 정보를 저장할 Entity 들을 만들어보자
Entity
1. Route
bounds와 roads를 List 형태로 저장하는 것이 주 목적인 entity 클래스이다.
모든 기록 조회를 할때 필요한 정보들도 같이 저장해주도록 하자.
또한 Timestamped에서 생성 시간을 가져온다.
@Entity
@Getter
@NoArgsConstructor
@Table(name = "route")
public class Route extends Timestamped{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "originAddress", nullable = false)
private String originAddress;
@Column(name = "destinationAddress", nullable = false)
private String destinationAddress;
@Column(name = "duration", nullable = false)
private int duration;
@Column(name = "distance", nullable = false)
private int distance;
@OneToMany(mappedBy = "route", orphanRemoval = true)
private List<Bound> bounds = new ArrayList<>();
@OneToMany(mappedBy = "route", orphanRemoval = true)
private List<Road> roads = new ArrayList<>();
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user = new User();
public Route(String originAddress, String destinationAddress, int duration, int distance, User user) {
this.originAddress = originAddress;
this.destinationAddress = destinationAddress;
this.duration = duration;
this.distance = distance;
this.user = user;
}
}
2. Bound
Route에서 List로 받을 뿐 아직까지는 OneToOne으로 받아도 무방한 entity 클래스이다.
지도의 표시 범위를 저장하며 혹시 모르는 상황에 대비해 KakaoRouteAllResponseDto의 형식을 유지하고자 Route와는 ManyToOne 관계를 맺어줬다.
@Entity
@Getter
@NoArgsConstructor
@Table(name = "bound")
public class Bound {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "min_x", nullable = false)
private double minX;
@Column(name = "min_y", nullable = false)
private double minY;
@Column(name = "max_x", nullable = false)
private double maxX;
@Column(name = "max_y", nullable = false)
private double maxY;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "route_id")
private Route route = new Route();
public Bound(double minX, double minY, double maxX, double maxY, Route route) {
this.minX = minX;
this.minY = minY;
this.maxX = maxX;
this.maxY = maxY;
this.route = route;
}
}
3. Road
vertexes를 저장하기 위한 entity 클래스이다. entity 클래스에는 Array 형식 저장이 불가해서,
저장시에는 띄어쓰기로 구분해서 String 형으로 저장해 줄 것이다.
@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "road")
public class Road {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "vertexes", nullable = false, length = 20000)
private String vertexes;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "route_id")
private Route route = new Route();
public Road(String vertexes, Route route) {
this.vertexes = vertexes;
this.route = route;
}
}
Entity를 만들었고, 그에따른 Repository 들도 만들어줬다.
이제 Service 클래스의 save메서드를 만들어보자.
Service, Controller
1. Service
해당 메서드는 request에서 KakaoAllresponseDto, orginAddress, destinationAddress를 가져온다.
public void saveHistory(KakaoRouteAllResponseDto requestDto, String originAddress, String destinationAddress, User user) {
for (KakaoRouteAllResponseDto.RouteInfo routeInfo : requestDto.getRoutes()) {
KakaoRouteAllResponseDto.Summary summary = routeInfo.getSummary();
KakaoRouteAllResponseDto.Section section = routeInfo.getSections()[0]; // 첫 번째 Section을 사용하겠습니다.
// Route 객체 생성
Route route = new Route(originAddress, destinationAddress, summary.getDuration(), summary.getDistance(), user);
// Bound 객체 생성 및 설정
KakaoRouteAllResponseDto.BoundingBox bound = section.getBound();
Bound boundEntity = new Bound(bound.getMinX(), bound.getMinY(), bound.getMaxX(), bound.getMaxY(),route);
// RouteRepository를 사용하여 Route 저장
routeRepository.save(route);
boundRepository.save(boundEntity);
// Road 객체 생성 및 설정
KakaoRouteAllResponseDto.Section[] sections = routeInfo.getSections();
// for문 돌면서 모든 Road의 vertexes를 vertexString에 띄어쓰기로 구분해서 넣어라
for (KakaoRouteAllResponseDto.Section allSection : sections) {
KakaoRouteAllResponseDto.Road[] roads = allSection.getRoads();
for (KakaoRouteAllResponseDto.Road roadDto : roads) {
String vertexesString = "";
vertexesString += Arrays.stream(roadDto.getVertexes()).mapToObj(String::valueOf).collect(Collectors.joining(" "));
Road roadEntity = new Road(vertexesString, route);
roadRepository.save(roadEntity);
}
}
}
}
그 후 가져온 정보를 저장하는데 vertex를 저장하는 로직에 주목해야 한다.
비록 road의 수만큼 새로운 데이터가 생성되긴 하지만 꺼내쓰기에는 최적화되어 있다.
이 부분은 나중에 발전시키도록 하자.
2. Controller
@PostMapping("/routes")
public ResponseEntity<String> saveRoutes(@RequestBody HistoryRequestDto requestDto,
@AuthenticationPrincipal UserDetailsImpl userDetails) {
historyService.saveHistory(requestDto.getRequestData(),
requestDto.getOriginAddress(), requestDto.getDestinationAddress(),
userDetails.getUser());
return ResponseEntity.ok("Data received successfully"); // 성공 응답
}
Controller까지 만들었으니 Controller에 맞는 형식의 정보를 넣어줘야 한다.
RequestDto, js, html
1. RequestDto
먼저 Controller에서 원하는 requestDto를 만들어준다.
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class HistoryRequestDto {
@JsonProperty("requestData")
private KakaoRouteAllResponseDto requestData;
@JsonProperty("originAddress")
private String originAddress;
@JsonProperty("destinationAddress")
private String destinationAddress;
}
2. js
"해당 경로로 안내해 드릴까요" 라는 문구 다음에 예, 아니오 버튼을 구현해서,
예를 누르면 해당 데이터를 보내주도록 navigation.js의 해당 기능을 수정하자. 너무 길어서 접어놓겠다.
document.getElementById('search-form').addEventListener('submit', function(e) {
e.preventDefault(); // 기본 submit 동작을 막습니다.
const auth = getToken();
if (auth !== undefined && auth !== '') {
$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
jqXHR.setRequestHeader('Authorization', auth);
});
} else {
window.location.href = host + '/api/user/login-page';
return;
}
var originAddress = document.getElementById('originAddress').value;
var destinationAddress = document.getElementById('destinationAddress').value;
fetch('/route?originAddress=' + originAddress + '&destinationAddress=' + destinationAddress)
.then(response => response.json())
.then(data => {
// data는 KakaoRouteAllResponseDto 객체
clearPolylines(); // 기존의 선들을 모두 제거
if (!map) {
map = new kakao.maps.Map(document.getElementById('map'), {
level: 3
});
}
// 경로 정보(routes)의 각 섹션(section)별로 반복하여 처리합니다.
for (let route of data.routes) {
for (let section of route.sections) {
// 각 섹션의 경계 상자(bound) 정보를 가져옵니다.
let bound = section.bound;
// 카카오 지도에 섹션을 표시합니다.
var bounds = new kakao.maps.LatLngBounds(
new kakao.maps.LatLng(bound.min_y, bound.min_x),
new kakao.maps.LatLng(bound.max_y, bound.max_x)
);
map.setBounds(bounds);
// polyline 생성
for(let road of section.roads){
let path = [];
for(let i=0; i<road.vertexes.length; i+=2){
console.log("vertexes: ", road.vertexes[i], road.vertexes[i+1]);
path.push(new kakao.maps.LatLng(road.vertexes[i+1], road.vertexes[i]));
}
let polyline = new kakao.maps.Polyline({
path: path,
strokeWeight: 5,
strokeColor: '#007bff',
strokeOpacity: 0.7,
strokeStyle: 'solid'
});
polyline.setMap(map);
polylines.push(polyline); // 선을 배열에 추가
}
}
}
// "아니오" 버튼 생성
let noButton = document.createElement('button');
noButton.textContent = '아니오';
document.getElementById('search-form').insertAdjacentElement('afterend', noButton);
noButton.addEventListener('click', function() {
// 여기에 "아니오" 버튼을 눌렀을 때 실행할 동작 추가 가능
confirmationMessage.remove(); // 문구와 버튼 제거
yesButton.remove();
noButton.remove();
});
// "예" 버튼 생성
let yesButton = document.createElement('button');
yesButton.textContent = '예';
document.getElementById('search-form').insertAdjacentElement('afterend', yesButton);
yesButton.addEventListener('click', function() {
fetch('/api/routes', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': auth // 인증 토큰을 Authorization 헤더에 추가
},
body: JSON.stringify({
requestData: data, // KakaoRouteAllResponseDto 객체
originAddress: originAddress,
destinationAddress: destinationAddress
})
})
confirmationMessage.remove(); // 문구와 버튼 제거
yesButton.remove();
noButton.remove();
});
// "해당 경로로 안내해 드릴까요?" 문구 표시
let confirmationMessage = document.createElement('p');
confirmationMessage.textContent = '해당 경로로 안내해 드릴까요?';
document.getElementById('search-form').insertAdjacentElement('afterend', confirmationMessage);
});
});
"예" 버튼을 누르면, 우리가 원하는 형태로 data가 전송되는 것을 볼 수 있다.
3. html
html에 해당 코드를 작성해야 Cookie에서 원하는 정보를 가져올 수 있다.
<script src="https://code.jquery.com/jquery-3.7.0.min.js"
integrity="sha256-2Pmvv0kuTBOenSvLm6bvfBSSHrUJ+3A7x6P5Ebd07/g=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/js-cookie@3.0.5/dist/js.cookie.min.js"></script>
경로 저장 API 구현이 완료되었다
'프로젝트 > RanDrive' 카테고리의 다른 글
Random-Drive-Project 기본 MVC 구현(프론트엔드 작업) (0) | 2023.10.26 |
---|---|
Random-Drive-Project 기본 MVC 구현(경로 조회, 상세조회) (1) | 2023.10.18 |
Random-Drive-Project 기본 MVC 구현(랜덤 네비게이션) (0) | 2023.10.14 |
Random-Drive-Project 기본 MVC 구현(기본 네비게이션) (1) | 2023.10.14 |
팀 Repo에 Docker및 CI/CD 구축 (1) | 2023.10.11 |