전체 보기
🍀

CustomException, RayCasting 이용한 위치 인증

작성일자
2023/03/03
태그
DIARY_DEVELOP
프로젝트
BeachCombine
책 종류
1 more property

공부한 내용

CustomException

API를 작성할 때 따로 Exception Handler를 만들어주지 않으면,
Runtime Error시 모두 500 Error로 Response가 가게 된다.
프론트는 이 에러 코드에서 에러의 원인을 알 수 없기에 Custome Exception을 추가하여 프론트에게 내용을 전달하도록 했다.
내가 프로젝트에 적용한 코드는 아래 PR에서 확인할 수 있다.
54
pull
위 코드를 적용해 Error가 아래의 꼴로 나가도록 했다.
{ "timestamp": "2023-03-03T21:21:46.036454", "status": 404, "error": "NOT_FOUND", "code": "NOT_FOUND_BEACH", "detail": "해당 해변을 찾을 수 없습니다." }
Java
복사

참고

RayCasting 알고리즘을 사용한 위치 인증 시스템

ChatGPT와의 대화 결과로 얻은 예시 코드를 조금 변형해 프로젝트에 적용했다.
우선, ChatGPT가 제공한 답변 결과를 핵심만 정리해보겠다.

답변

다음은 Java로 Ray casting 알고리즘을 이용하여 사용자가 해수욕장 내부에 있는지 여부를 판별하는 예시 코드다.
public class RayCastingAlgorithm { // 점 (x, y)가 다각형 내부에 있는지 판별하는 메서드 public boolean isInsidePolygon(double[] xCoords, double[] yCoords, double x, double y) { int nPoints = xCoords.length; int nIntersections = 0; for (int i = 0; i < nPoints; i++) { double x1 = xCoords[i]; double y1 = yCoords[i]; double x2 = xCoords[(i + 1) % nPoints]; double y2 = yCoords[(i + 1) % nPoints]; if (isIntersect(x1, y1, x2, y2, x, y)) { nIntersections++; } } return nIntersections % 2 == 1; } // 선분 (x1, y1) -> (x2, y2)와 y = y0가 교차하는지 판별하는 메서드 private boolean isIntersect(double x1, double y1, double x2, double y2, double x0, double y0) { if (y1 == y2) { return false; } double xIntersect = x1 + (x2 - x1) * (y0 - y1) / (y2 - y1); if (xIntersect < x0) { return false; } return true; } }
Java
복사
위 코드는 isInsidePolygon 메서드를 통해 점 (x, y)가 다각형 내부에 있는지 판별하는 방법을 보여준다. xCoords, yCoords 배열에는 다각형을 이루는 꼭짓점 좌표들이 저장되어 있다. isIntersect 메서드는 선분 (x1, y1) -> (x2, y2)와 y = y0가 교차하는지 판별하는 메서드다.
사용자의 위치가 해수욕장 내부에 있는지 판별하기 위해서는, isInsidePolygon 메서드를 호출하면 된다. xCoords, yCoords 배열에는 해수욕장을 이루는 꼭짓점 좌표들을 입력하고, xy에는 사용자의 위치 좌표를 입력하면 된다. isInsidePolygon 메서드는 해당 좌표가 해수욕장 내부에 있으면 true, 외부에 있으면 false를 반환한다.
RayCastingAlgorithm 클래스의 예시 메인 메서드를 다음과 같이 작성하여, 실제 사용자가 위치한 좌표값과 해수욕장을 나타내는 꼭짓점 좌표값을 입력하면, 해당 좌표가 해수욕장 내부에 있는지 여부를 판별할 수 있다.
public static void main(String[] args) { // 해수욕장을 이루는 4개의 꼭짓점 좌표들 double[] xCoords = { 1, 5, 5, 1 }; double[] yCoords = { 1, 1, 3, 3 }; // 사용자의 위치 좌표 double x = 3; double y = 2; // Ray casting 알고리즘을 이용하여 사용자의 위치가 해수욕장 내부에 있는지 판별 RayCastingAlgorithm rayCasting = new RayCastingAlgorithm(); boolean isInside = rayCasting.isInsidePolygon(xCoords, yCoords, x, y); // 결과 출력 if (isInside) { System.out.println("사용자는 해수욕장 내부에 있습니다."); } else { System.out.println("사용자는 해수욕장 외부에 있습니다."); } }
Java
복사
위 코드에서는 xCoords, yCoords 배열에는 해수욕장을 이루는 4개의 꼭짓점 좌표들을 입력하고, xy에는 사용자의 위치 좌표를 입력힌다. 그리고 RayCastingAlgorithm 클래스의 isInsidePolygon 메서드를 호출하여 사용자의 위치가 해수욕장 내부에 있는지 여부를 판별하고, 결과를 출력한다.
해당 예시 코드를 실행해보면, 사용자의 위치가 (3, 2)이고, 해수욕장을 이루는 4개의 꼭짓점 좌표가 (1, 1), (5, 1), (5, 3), (1, 3)인 경우, 사용자가 해수욕장 내부에 있는지 여부를 판별할 수 있다.

실제로 적용한 코드

해당 코드는 해당 API를 맡으셨던 팀원분이 짜셨다. 비록 이 코드는 내가 짜지 않았지만, 문제 분석부터 예시 코드까지 모두 같이 논의를 했기에 아래 코드도 내 것처럼 이해하려 노력했다.)
public class RayCastingUtil { // 점 (x, y)가 다각형 내부에 있는지 판별하는 메서드 public boolean isInsidePolygon(List<BigDecimal> xCoords, List<BigDecimal> yCoords, BigDecimal x, BigDecimal y) { // 정확도를 위해 double로 계산 // 광선은 왼쪽에서 쏜다고 가정 int nPoints = xCoords.size(); int nIntersections = 0; for (int i = 0; i < nPoints; i++) { BigDecimal x1 = xCoords.get(i); BigDecimal y1 = yCoords.get(i); BigDecimal x2 = xCoords.get((i + 1) % nPoints); BigDecimal y2 = yCoords.get((i + 1) % nPoints); BigDecimal minNum = (x1.compareTo(x2)<=0)? x1:x2; // x1<=x2 BigDecimal maxNum = (x1.compareTo(x2)<=0)? x2:x1; if(y1.equals(y2) && y.equals(y1) && minNum.compareTo(x)<=0 && maxNum.compareTo(x)>=0){ // minNum <= x <= maxNum return true; } if (isIntersect(x1, y1, x2, y2, x, y)) { nIntersections++; } } return nIntersections % 2 == 1; } // 선분 (x1, y1) -> (x2, y2)와 반직선 y = y0가 교차하는지 판별하는 메서드 private boolean isIntersect(BigDecimal x1, BigDecimal y1, BigDecimal x2, BigDecimal y2, BigDecimal x0, BigDecimal y0) { // 선분의 y 좌표값이 모두 y0보다 작거나 모두 y0보다 큰 경우 교차하지 않는다. // (y0-y1)*(y0-y2)>0 BigDecimal result = (y0.subtract(y1)).multiply(y0.subtract(y2)); if (result.compareTo(BigDecimal.ZERO)>0) { return false; } // 선분의 기울기가 0일 경우 (y0==y1==y2) --> x값 비교 필요, 함수 호출 전에 이미 검증함 if(y1.equals(y2)){ return false; // x값 범위 안맞을 때 } // 선분의 기울기가 0이 아닌 경우, 선분의 x 좌표값을 구한다. // (y0 - y1) * (x2 - x1) / (y2 - y1) + x1 BigDecimal x = (((y0.subtract(y1)).multiply(x2.subtract(x1))).divide(y2.subtract(y1), 20, RoundingMode.HALF_UP)).add(x1); // x 좌표값이 범위 안에 있는지 확인 // x 좌표는 이미 (x-x1)(x-x2)<=0 만족해서 확인필요 없음 return x.compareTo(x0) <= 0; // x<=x0 } }
Java
복사

참고

+ChatGPT

하루 정리

TIL 작성하기
BeachCombine
CustomException 적용 안되는 문제 해결하기!!! (급함)
위치 인증 알고리즘 논의 → RayCasting 알고리즘 사용하기로 결정
위치 인증 API 코리
오늘은 BeachCombine 프로젝트에서 사용자 위치 인증에 대한 논의를 했다. 처음엔 특정 좌표로부터 사용자가 특정 반경 내에 있는지 확인하는 간단한 방법을 떠올렸는데, 좀 더 좋은 방법을 모색하기 위해 노력했다. 후에 떠올린 것은 직사각형 모양으로 꼭짓점 좌표들을 잡아주고, 사용자가 해당 직사각형 안에 있나 확인하는 방법이었다. 하지만, 이 역시 바다를 완벽히 직사각형으로 표현하기엔 부족함이 있다 생각되어 더 구글링을 해보던 중 팀원분이 RayCasting 알고리즘이란 것에 대해 찾았다!
RayCasting 알고리즘은 쉽게 말해 점이 다각형 안에 있는지 확인하는 알고리즘인데, 점을 지나는 x축에 평행한 반직선을 그어, 해당 반직선이 다각형과 몇 번 교차하는지 확인한다. 이 반직선이 다각형과 짝수 번 만난다면 점은 다각형 밖에, 홀수 번 만난다면 점은 다각형 안에 있다.
프로젝트에서 RayCasting 알고리즘을 적용해 위치 인증 시스템을 구축한 플로우를 정리해보겠다.
1.
바다를 다각형 모양으로 얼추 맞춰서 꼭짓점들의 좌표를 직접 찾아 DB에 저장해줬다(노가다 했습니당,,)
2.
사용자가 본인의 현재 위치와 특정 바다를 대상으로 위치 인증 요청을 보내면 사용자의 현재 위치 좌표가 해당 바다 위치 범위 안에 있는지 확인한다.
우리 프로젝트는 바다를 청소하는 서비스로 사용자가 바다에 있는지 위치 확인을 해야했기에 위치 인증 시스템이 필요했다. 논의 과정부터 해결까지 굉장히 흥미로웠던 문제였다!
추가로, 배포가 제대로 안 되는 이슈가 있었다. 심지어 언제부터 안 된 건지 모르겠는데, 배포 자동화를 구축해 둔 상태인데 변경된 env 파일 내용을 GitHub 환경에 안 넣어줘서 그랬을 거라 판단한다. 일단은 직접 최신 상태로 배포를 해두었는데, 다음 PR 내용은 배포 자동화로 제대로 배포되는지 꼭 확인이 필요할 것 같다. 또, 앞으로는 PR 내용이 제대로 자동 배포되는지 주기적으로 확인해봐야겠다.
마지막으로 util 패키지 생성에 대한 논의를 했다. geocoding과 raycasting을 util 클래스로 빼줬다. 서비스 로직에서 사용하는 것들인데, 공통되게 사용하거나 서비스 클래스에 넣기엔 너무 코드가 길고 복잡해 util로 빼주었다. geocoding 부분은 재밌는 부분이 많아 다음에 코드 분석 후 TIL에 작성해봐야겠다.