-
[개인] DLD Project (5) - 지도상 두 좌표 사이 거리 구하기 (feat. Haversine 🤔)프로젝트 2023. 2. 23. 11:47
틀린 내용이 있을 수 있습니다.
발견하시면 말씀 부탁드립니다! 🙇
현재 내 위치에서 목적지까지의 거리를 구하여 사용자에게 보여주는 기능을 구현해 보자. Google Maps Distance Matrix API를 통해 지점 간의 거리 데이터를 가져올 수 있다고 하며, 검색해 보면 해당 API를 통해 기능을 구현한 블로그 글들도 나온다. 하지만 난 해당 API를 사용하지 않기로 했다. 왜냐하면 우리나라에서는 대중교통을 이용했을 때의 거리만 조회할 수 있도록 한정되어 있기 때문이다. 이는 이번 프로젝트의 주제와는 거리가 있기에 계산식을 통해 두 지점 간의 거리를 구하기로 했다. (사실 걷는 사람 입장에서는 대중교통을 통한 거리나 직선거리나 도긴개긴이다🥲)
처음에는 단순히 두 지점의 좌표를 알고 있기에 어릴 적 배웠던 피타고라스 정의(A² + B² = C²)로 계산하면 되겠지 생각했다. 그러나 거리 계산 함수를 작성한 뒤 함수 결과 값과 실제 구글 지도에서 가져온 값을 비교하니 오차가 있었다. 구글링을 통해 알아보니 지도에서 좌표 간의 거리를 구할 때는 하버사인(Haversine) 공식을 사용해 지구의 곡률까지 계산을 해야 한다고 한다.
하버사인 공식을 통해 두 좌표 간의 거리를 구하기 위한 최종적인 식은 아래와 같다.
해당 식을 통해 두 지점 간의 거리를 구하는 예시는 인터넷에 많았고, 그중 몇 개의 글을 참고해 함수를 작성했다. 참고 1 / 참고 2
const calcDistanceHaversine = (curObj, destObj) => { const currX = curObj['lat']; // 출발지 위도 const currY = curObj['lng']; // 출발지 경도 const destX = destObj['lat']; // 목적지 위도 const destY = destObj['lng']; // 목적지 경도 const radius = 6371; // 지구 반지름(km) const toRadian = Math.PI / 180; const deltaLat = Math.abs(currX - destX) * toRadian; const deltaLng = Math.abs(currY - destY) * toRadian; const squareSinDeltLat = Math.pow(Math.sin(deltaLat / 2), 2); const squareSinDeltLng = Math.pow(Math.sin(deltaLng / 2), 2); const squareRoot = Math.sqrt( squareSinDeltLat + Math.cos(currX * toRadian) * Math.cos(destX * toRadian) * squareSinDeltLng, ); const result = 2 * radius * Math.asin(squareRoot); return result; };
이제 위 함수를 뜯어보며 이해해보자. 아래는 8번째 줄의 코드이다.
const toRadian = Math.PI / 180;
변수명을 통해 추측가능하듯 이건 라디안과 연관 있다. 라디안(Radian, 호도)이란 원의 원점을 중심으로 반지름 길이만큼 한 방향으로 움직였을 때 대응하는 각의 크기를 의미한다(by. 나무위키).
참고로 π 라디안은 180˚이다. 이해하면 좋겠지만 그냥 외우자. 즉, 원의 원점을 중심으로 π(3.14...) 길이만큼 한 방향으로 움직이면 대응하는 각이 180˚가 된다는 이야기다.
π 라디안(rad) = 180˚
1 라디안(rad) = 180˚/ π
1˚ = π / 180돌아가서, Math.PI / 180은 도(˚)를 라디안 단위로 변환할 때 사용할 값이다. 도(˚)를 라디안 단위로 변환하기 위해서는 아래 공식을 사용한다.
도(˚) * π / 180
이를 토대로 10~11번째 줄 코드는 다음과 같이 이해할 수 있다.
// 출발지와 목적지 위도 차를 라디안 단위로 변환 const deltaLat = Math.abs(currX - destX) * toRadian; // 출발지와 목적지 경도 차를 라디안 단위로 변환 const deltaLng = Math.abs(currY - destY) * toRadian;
라디안 값으로 변환함으로서 해당 값을 Math.sin(), Math.cos() 메소드들의 파라미터로 사용할 수 있게 되었다 (참고로 Math.sin(), Math.cos(), 이 식에서는 사용되지 않은 Math.tan()까지 모두 라디안인 수를 파라미터로 받는다). deltaLat, deltaLng 두 개의 숫자를 Math.sin() 메소드를 통해 사인 값으로 변환한 뒤 제곱값을 구한다.
const squareSinDeltLat = Math.pow(Math.sin(deltaLat / 2), 2); const squareSinDeltLng = Math.pow(Math.sin(deltaLng / 2), 2);
이어서 공식에 나온 대로 사인 값과 코사인 값을 계산한 결과의 제곱근을 구하고, 해당 값에 sin의 역함수인 arcsin을 곱한다. 이 값에 2 * radius까지 곱해주면 지도상의 두 지점 간 거리를 구할 수 있다.
const squareRoot = Math.sqrt( squareSinDeltLat + Math.cos(currX * toRadian) * Math.cos(destX * toRadian) * squareSinDeltLng ); const result = 2 * radius * Math.asin(squareRoot);
참고 자료
- https://cloud.google.com/blog/products/maps-platform/how-calculate-distances-map-maps-javascript-api?hl=en
- https://ko.martech.zone/calculate-great-circle-distance/
- https://spiralmoon.tistory.com/entry/Alogrithm-%EC%A7%80%EA%B5%AC%EC%97%90%EC%84%9C-%EB%91%90-%EC%A0%90-%EC%82%AC%EC%9D%B4%EC%9D%98-%EA%B1%B0%EB%A6%AC-%EA%B5%AC%ED%95%98%EA%B8%B0
- https://kayuse88.github.io/haversine/
- https://spiralmoon.tistory.com/entry/Alogrithm-%EC%A7%80%EA%B5%AC%EC%97%90%EC%84%9C-%EB%91%90-%EC%A0%90-%EC%82%AC%EC%9D%B4%EC%9D%98-%EA%B1%B0%EB%A6%AC-%EA%B5%AC%ED%95%98%EA%B8%B0
- https://mathbang.net/496#gsc.tab=0
- https://color-change.tistory.com/65
- https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Math/sin
- https://sseong40.tistory.com/26
'프로젝트' 카테고리의 다른 글
[개인] DLD Project - 중간 점검 (0) 2023.03.28 [개인] DLD Project (6) - Docker를 이용한 MongoDB 설치 & 서버 연결 🐋 (2) 2023.02.24 [개인] DLD Project (4) - Google map Polyline (0) 2023.02.14 [개인] DLD Project (3) - Google map Autocomplete 외 (0) 2023.02.12 [개인] DLD Project (2) (0) 2023.01.11