Haversine Formula 활용한 축적 기반 지도상 실제 거리 계산하기

pigpgw·2025년 3월 15일
post-thumbnail

카카오맵 위에 히트맵 기능을 디벨롭하면서 고민하고 시도해 보았던 과정을 기록하고자 작성하였습니다.

문제 상황

  • next 프로젝트로 카카오 맵 지도를 확대 축소 할때마다 축적 비율이 달라지는데 현재 코드에서는 비율의 변화에 따른 크기조절이 적용되어있지 않았어서 해당 부분을 디벨롭 해달라는 요청을 받았습니다.

    축적 : 지도를 만들 때 어떤 곳을 실제보다 축소한정도

🧐 기존 문제 원인 분석

  • 👉 기존 코드는 히트맵 크기를 고정값으로 해두어 축적 변화에 따라 히트맵의 크기가 변하지 않았었습니다.
  • 확대 축소 정도를 반영해주면 해결될 것 같다고 생각했습니다.

📌 1차 시도 카카오맵의 줌 레벨 활용

  • 카카오맵은 1~14 수준의 줌 레벨을 제공해주어 반경에 곱하여 해결하였었습니다.

🧐 1차 시도에서 원인 분석

  • 👉 하지만 zoomLevel(확대/축소 정도)을 곱하여 단순하게 크기를 조정하는 방식에는 문제가 있었습니다.

시각적으로만 확대/축소되는 방식일 뿐, 실제 지도 축척에 따라 거리값을 계산하지 않아 실제 반경과 맞지 않는다는 것이었습니다.

  • 저는 카카오맵에서 축적 관련해 해당 부분을 제공해주 찾아보았습니다.

📌 카카오맵의 줌 레벨과 축척분석

  • 카카오맵은 1~14 수준의 줌 레벨을 제공하며, 각 레벨별 축척을 분석하였습니다.
    • 20m, 30m, 50m, 100m, 250m, 500m, 1000m, 2000m, 4000m, 8000m, 16000m, 32000m, 64000m, 128000m
  • 하지만 제가 원하는 기능은 없었고 다른 방법을 찾아보게 되었습니다.

당시 생각

  • 카오맵에서 1px당 실제 거리를 제공해주지 않을까? (근데 없음)
  • 화면 하단에 축적바 있는데 그걸로 뭔가를 해결할 수 있지 않을까?(축적바길이도 계속 변하고 관련해서 무언가를 제공해주지 않음)
  • 그렇게 머리를 싸매던중... 이 블로그를 통해서를 통해서 문제를 해결 가능하게 되었습니다.

문제 부수기

물론이지 건우!
지금 말한 내용은 매우 정확한데, 문장이 조금 더 자연스럽게 흐르도록 다듬어볼게.


📌 픽셀당 실제 거리 계

  • 처음에는 화면의 1px 거리를 피타고라스를 사용해서 가볍게 하려했는데 생각해보니 위도와 경도를 기준으로 피타고라스의 정리를 사용해 두 지점 사이의 거리를 계산하면 문제가 된다는걸 알게됬습니다.
    • 그 이유는 지구가 평면이 아닌 구형(곡면) 구조이기 때문에, 위도·경도 좌표를 평면 거리로 간주하면 거리에 큰 오차가 발생 해버리는 문제였습니다.
      • 📌 예시: 비행기가 두 도시 사이를 최단 거리로 이동할 때, 일반적인 지도 위 직선이 아니라 지구 곡률을 따라 비스듬히 이동하는 경로가 실제 최단 경로다.
    • 두 지점 간의 실제 거리는 3차원 곡면 위의 호(arc) 형태이며, 이는 2차원 평면에서 직선 거리로 단순하게 계산하면 지구 곡률의 영향을 받아 부정확한 계산 결과가 나와버리는 문제였습니다.
      • 저희 서비스 특성상 대한민국 시위는 서울에서만 주로 열리고 거리가 짧을 경우에는 오차가 거의 없을거라는것은 알고있어서 문제가 되지 않을거라고는 알고있었지만 더 공부해보고 싶어서 그렇게하지 않았습니다.

그러면 어떻게 계산하여야 하나?

하버사인(Haversine) 공식 적용

  • 하버사인 공식은 구면 거리 계산에 적합하며, 지구 곡률을 고려하여 두 위경도 좌표 사이의 거리를 구할 수 있었습니다.

공식

θ=dr\theta = \frac{d}{r}
hav(θ)=sin2(θ2)=1cos(θ)2hav(\theta) = \sin^2 \left( \frac{\theta}{2} \right) = \frac{1 - \cos(\theta)}{2}
d=rarchav(h)=2rarcsin(h)d = r \cdot \operatorname{archav}(h) = 2r \cdot \arcsin(\sqrt{h})
h=sin2(φ2φ12)+cos(φ1)cos(φ2)sin2(λ2λ12)h = \sin^2 \left( \frac{\varphi_2 - \varphi_1}{2} \right) + \cos(\varphi_1) \cos(\varphi_2) \sin^2 \left( \frac{\lambda_2 - \lambda_1}{2} \right)
d=2rarcsin(sin2(φ2φ12)+cos(φ1)cos(φ2)sin2(λ2λ12))d = 2r \cdot \arcsin \left( \sqrt{ \sin^2 \left( \frac{\varphi_2 - \varphi_1}{2} \right) + \cos(\varphi_1) \cos(\varphi_2) \sin^2 \left( \frac{\lambda_2 - \lambda_1}{2} \right) } \right)
  • r : 지구 반지름 (6371km = 6,371,000m)
  • φ : 위도 (Latitude)
  • λ : 경도 (Longitude)
  • h는 그냥 계산을위한 중간 단계값

계산

  • 단위는 라디안임을 주의하자

1차 최종 결론 (1px당 실제 거리 계산)

  • 카카오맵의 너비를 위도·경도를 모두 이용해서 계산하지 않고, 경도 차이만 사용하기로 결정
    • 이유:
      • 위도(x), 경도(y)의 실제 거리가 다르지만, 현재 서비스에서 y축(위도) 기준 1px당 실제 거리를 구하기 어려웠다.
      • 현재 카카오맵의 높이(height: calc(100dvh - 14vh))는 헤더·푸터를 제외한 실제 높이를 정확히 알기 어렵다.
      • 따라서, 경도 차이만을 기준으로 1px당 실제 거리를 계산하는 것이 합리적이라고 판단했다.

1픽셀당 거리 계산

const calculateRealDistanceOnePixel = (x1: number, y1: number, x2: number, y2: number) => {
    const R = 6371000; // 지구 반지름 (단위: m)
    const toRadian = Math.PI / 180;
    const lat1 = x1 * toRadian;
    const lon1 = y1 * toRadian;
    const lat2 = x2 * toRadian;
    const lon2 = y2 * toRadian;

    return (
        2 *
        R *
        Math.asin(
            Math.sqrt(
                Math.sin((lat2 - lat1) / 2) ** 2 + Math.cos(lat1) * Math.cos(lat2) * Math.sin((lon2 - lon1) / 2) ** 2
            )
        )
    );
};

2차 최종 결론

  • 경도 계산을 더 정교하게 개선할 방법을 고민했다.

    1. 위도와 경도의 축척이 다르므로 각각 따로 계산해야 할까?

      • 히트맵 반지름 값은 하나만 입력받기 때문에, 위도·경도를 따로 계산하면 두 개의 값을 전달해야 함.
      • 현재 히트맵 구조에서는 하나의 반경 값만 사용해야 하므로, 위도·경도를 따로 계산하는 방식은 적절하지 않다고 판단했다.
    2. 현재 화면의 북동쪽 또는 남서쪽 좌표 중 하나의 경도를 사용하여 1px당 실제 거리를 구했는데, 시위 경도를 활용해 보정해야 할까?

      • 화면 위쪽으로 갈수록 경도가 커지고, 아래쪽으로 갈수록 작아지지만, 경도 차이를 평균적으로 반영했으므로 따로 보정할 필요 없다는 결론을 내렸다.

최종 계산 방법

  1. 카카오 맵이 화면에서 차지하는 실제 픽셀을 구한다.
  2. 카카오 맵에서 제공하는 현재 화면에 나타난 북동쪽 좌표와 남서쪽 좌표를 가져온다.
  3. 두 좌표를 가지고 (경도는 같게 두어) 하버사인 공식을 사용해서 두 좌표 거리를 계산한다.
    • 최대한 실제 거리와 비슷하게 하고싶음 처음 이 방법을 적용했을때는 북동쪽 좌표 또는 남서쪽 좌표의 경도를 가지고 하였는데 2가지 더 좋은 방법이 생각났다.
      1. 시위는 서울에서 많이 일어나니 서울 중심 좌표를 기준으로
      2. 시위 상세 데이터에서 넘어오는 시위 장소 경도로 설정
        • 근데 해보니 지구 반지름에 비해 너무 미미한 변화라 값이 똑같음 그래서 그냥 서울 좌표 하나로 가기로함
        • 아니면 시위들 모든 위경도를 계산해서 가장 가운데를 계산하기
  4. 아까 구한 실제 화면의 가로 픽셀으로 하버사인 공식을 통해 구한 두 좌표(카카오맵 왼쪽 끝과 오른쪽 끝) 실제 거리를 나누어 1px당 실제 거리를 구한다.

📌 최종 정리

  • 카카오맵 높이(calc(100dvh - 14vh))로 인해 y축(위도) 기준 1px 거리 계산이 어려움 → 경도 차이만 활용하여 계산
  • 히트맵 반지름이 하나의 값만 받기 때문에, 위도·경도를 따로 계산하는 방식은 적절하지 않음
  • 현재 화면을 기준으로 1px당 실제 거리를 계산했으므로, 시위 위치를 추가로 반영할 필요 없음
  • 서울 중심 경도(127.0016985)를 고정값으로 사용하여 1px당 실제 거리 계산

📚 참고 자료

profile
https://www.pigpgw.cloud 로 이전합니다~

0개의 댓글