# Typescript : 지구 두 좌표 사이 거리 구하기

- Author: @laetipark
- Published: 2023-12-22
- Updated: 2023-12-22
- Source: http://blex.me/@laetipark/typescript-%EC%A7%80%EA%B5%AC-%EB%91%90-%EC%A2%8C%ED%91%9C-%EC%82%AC%EC%9D%B4-%EA%B1%B0%EB%A6%AC-%EA%B5%AC%ED%95%98%EA%B8%B0
- Tags: algorithm, typescript, radian, haversine, 거리

---

## 라디안(Radian)
- 각의 크기를 재는 `SI 유도 단위`로 기호는 `rad` 또는 `c`를 사용한다.
```
1 rad = 180 / π
```

## Haversine 공식
- `Haversine` 공식은, `구형 지구`로 가정하고 주어지는 두 위도, 경도에 따라 거리를 구하는 공식입니다.
- 지구 표면에서 최단거리를 구하는 것으로, 지구는 실제로 `타원` 모양이기 때문에 최대 0.3%의 오차가 존재한다고 합니다.  
```
a = sin²(Δφ/2) + cos φ 1 ⋅ cos φ 2 ⋅ sin²(Δλ/2)
c = 2 ⋅ atan2( √ a , √ (1−a) )
d = R ⋅ c
// φ는 위도, λ는 경도, R은 지구의 반지름(평균 반지름 = 6,371km)
```

- Haversine 공식 알고리즘 코드
```typescript
/** 두 지점 위치의 거리 반환
  * @param point1 좌표 1
  * @param point2 좌표 2
  */
latLonToKm(point1: [number, number], point2: [number, number]) {
	// 두 좌표를 각각 위도와 경도 변수로 저장
	const lat1 = point1[1];
	const lon1 = point1[0];
	const lat2 = point2[1];
	const lon2 = point2[0];

	const R = 6371; // 지구의 반지름 길이 상수
	
	// 각 위도 및 경도 차이를 라디안 값으로 계산
	const dLat = this.toRadians(lat2 - lat1);
	const dLon = this.toRadians(lon2 - lon1);

	// 각 지점의 위도를 라디안 값으로 변환
	const radLat1 = this.toRadians(lat1);
	const radLat2 = this.toRadians(lat2);

	// Haversine 공식을 사용하여 두 지점 사이의 거리 계산
	const a =
				Math.sin(dLat / 2) * Math.sin(dLat / 2) + // sin²(Δφ/2)
				Math.sin(dLon / 2) * // cos φ1 ⋅ cos φ2 ⋅ sin²(Δλ/2)
				Math.sin(dLon / 2) *
				Math.cos(radLat1) *
				Math.cos(radLat2);
	const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); // 2 ⋅ atan2( √a, √(1−a) )

	// 거리 반환 (단위: 킬로미터)
	return R * c;
}

/** 각도를 라디안 값으로 반환 */
private toRadians(degrees: number) {
	return degrees * (Math.PI / 180);
}
```

## 구면 코사인 법칙(Spherical Law of Cosines)
- 대부분의 컴퓨터 언어는 `IEEE 754` 64비트 부동 소수점 숫자를 사용하기 때문에, 소수점 15자리까지의 정밀도를 제공한다.
- `구면 코사인 법칙`을 함께 이용해 `정밀한 거리 결과`를 가져올 수 있지만, `Haversine 공식`보다는 약간 느린 성능을 갖는다고 합니다.
- 또한 짧은 거리의 경우 아래 서술할 `정방형 근사`가 더 정확하다고 합니다.
```
d = acos( sin φ 1 ⋅ sin φ 2 + cos φ 1 ⋅ cos φ 2 ⋅ cos Δλ ) ⋅ R
```

- 구면 코사인 법칙 알고리즘 코드
```typescript
/** 두 지점 위치의 거리 반환
  * @param point1 좌표 1
  * @param point2 좌표 2
  */
latLonToKm(point1: [number, number], point2: [number, number]) {
	// 두 좌표를 각각 위도와 경도 변수로 저장
	const lat1 = point1[1];
	const lon1 = point1[0];
	const lat2 = point2[1];
	const lon2 = point2[0];

	const R = 6371; // 지구의 반지름 길이 상수
	
	// 경도 차이를 라디안 값으로 계산
	const dLon = this.toRadians(lon2 - lon1);

	// 각 지점의 위도를 라디안 값으로 변환
	const radLat1 = this.toRadians(lat1);
	const radLat2 = this.toRadians(lat2);

	// 구면 코사인 법칙을 이용한 거리 반환 (단위: 킬로미터)
	return Math.acos(
					Math.sin(radLat1) * Math.sin(radLat2) + // sin φ 1 ⋅ sin φ 2
					Math.cos(radLat1) * // cos φ 1 ⋅ cos φ 2 ⋅ cos Δλ
					Math.cos(radLat2) *
					Math.cos(dLon)
				) * R;
}

/** 각도를 라디안 값으로 반환 */
private toRadians(degrees: number) {
	return degrees * (Math.PI / 180);
}
```

## 정방형 근사(Spherical Law of Cosines)
- 정확도보다 `성능`이 중요한 상황이라면, 짧은 거리에 한하여 `피타고라스 법칙`을 사용한 `정방형 근사`를 사용할 수 있습니다.
- 거리가 길어질 수록 오차값이 커집니다.
```
x = Δλ ⋅ cos φ m
y = Δφ
d = R ⋅ √x² + y²
```

- 정방형 근사 알고리즘 코드
```typescript
/** 두 지점 위치의 거리 반환
  * @param point1 좌표 1
  * @param point2 좌표 2
  */
latLonToKm(point1: [number, number], point2: [number, number]) {
	// 두 좌표를 각각 위도와 경도 변수로 저장
	const lat1 = point1[1];
	const lon1 = point1[0];
	const lat2 = point2[1];
	const lon2 = point2[0];

	const R = 6371; // 지구의 반지름 길이 상수
	
	// 정방형 근사를 이용한 거리 반환 (단위: 킬로미터)
	const x = (lon2 - lon1) * Math.cos((lat1 + lat2) / 2);
	const y = (lat2 - lat1);
	return Math.sqrt(x * x + y * y) * R
}
```

## 참고 자료
- [라디안(Radian), 위키백과](https://ko.wikipedia.org/wiki/%EB%9D%BC%EB%94%94%EC%95%88)
- [지구에서 두 점 사이의 거리 구하기, Spiral Moon, Tistory](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)
- [JavaScript에서 Haversine 공식을 사용하여 두 위도/경도 점 사이의 거리와 방위 계산](https://www.movable-type.co.uk/scripts/latlong.html)
