177 lines
4.3 KiB
TypeScript
177 lines
4.3 KiB
TypeScript
/**
|
|
* 지리 좌표 관련 유틸리티 함수
|
|
*/
|
|
|
|
/**
|
|
* Haversine 공식을 사용하여 두 좌표 간의 거리 계산 (km)
|
|
*
|
|
* @param lat1 - 첫 번째 지점의 위도
|
|
* @param lon1 - 첫 번째 지점의 경도
|
|
* @param lat2 - 두 번째 지점의 위도
|
|
* @param lon2 - 두 번째 지점의 경도
|
|
* @returns 두 지점 간의 거리 (km)
|
|
*/
|
|
export function calculateDistance(
|
|
lat1: number,
|
|
lon1: number,
|
|
lat2: number,
|
|
lon2: number
|
|
): number {
|
|
const R = 6371; // 지구 반경 (km)
|
|
|
|
const dLat = toRadians(lat2 - lat1);
|
|
const dLon = toRadians(lon2 - lon1);
|
|
|
|
const a =
|
|
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
|
Math.cos(toRadians(lat1)) *
|
|
Math.cos(toRadians(lat2)) *
|
|
Math.sin(dLon / 2) *
|
|
Math.sin(dLon / 2);
|
|
|
|
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
|
|
return R * c;
|
|
}
|
|
|
|
/**
|
|
* 각도를 라디안으로 변환
|
|
*/
|
|
function toRadians(degrees: number): number {
|
|
return degrees * (Math.PI / 180);
|
|
}
|
|
|
|
/**
|
|
* 라디안을 각도로 변환
|
|
*/
|
|
export function toDegrees(radians: number): number {
|
|
return radians * (180 / Math.PI);
|
|
}
|
|
|
|
/**
|
|
* 좌표 배열에서 총 거리 계산
|
|
*
|
|
* @param coordinates - { latitude, longitude }[] 형태의 좌표 배열
|
|
* @returns 총 거리 (km)
|
|
*/
|
|
export function calculateTotalDistance(
|
|
coordinates: Array<{ latitude: number; longitude: number }>
|
|
): number {
|
|
let totalDistance = 0;
|
|
|
|
for (let i = 1; i < coordinates.length; i++) {
|
|
const prev = coordinates[i - 1];
|
|
const curr = coordinates[i];
|
|
totalDistance += calculateDistance(
|
|
prev.latitude,
|
|
prev.longitude,
|
|
curr.latitude,
|
|
curr.longitude
|
|
);
|
|
}
|
|
|
|
return totalDistance;
|
|
}
|
|
|
|
/**
|
|
* 좌표가 특정 반경 내에 있는지 확인
|
|
*
|
|
* @param centerLat - 중심점 위도
|
|
* @param centerLon - 중심점 경도
|
|
* @param pointLat - 확인할 지점의 위도
|
|
* @param pointLon - 확인할 지점의 경도
|
|
* @param radiusKm - 반경 (km)
|
|
* @returns 반경 내에 있으면 true
|
|
*/
|
|
export function isWithinRadius(
|
|
centerLat: number,
|
|
centerLon: number,
|
|
pointLat: number,
|
|
pointLon: number,
|
|
radiusKm: number
|
|
): boolean {
|
|
const distance = calculateDistance(centerLat, centerLon, pointLat, pointLon);
|
|
return distance <= radiusKm;
|
|
}
|
|
|
|
/**
|
|
* 두 좌표 사이의 방위각(bearing) 계산
|
|
*
|
|
* @param lat1 - 시작점 위도
|
|
* @param lon1 - 시작점 경도
|
|
* @param lat2 - 도착점 위도
|
|
* @param lon2 - 도착점 경도
|
|
* @returns 방위각 (0-360도)
|
|
*/
|
|
export function calculateBearing(
|
|
lat1: number,
|
|
lon1: number,
|
|
lat2: number,
|
|
lon2: number
|
|
): number {
|
|
const dLon = toRadians(lon2 - lon1);
|
|
const lat1Rad = toRadians(lat1);
|
|
const lat2Rad = toRadians(lat2);
|
|
|
|
const x = Math.sin(dLon) * Math.cos(lat2Rad);
|
|
const y =
|
|
Math.cos(lat1Rad) * Math.sin(lat2Rad) -
|
|
Math.sin(lat1Rad) * Math.cos(lat2Rad) * Math.cos(dLon);
|
|
|
|
let bearing = toDegrees(Math.atan2(x, y));
|
|
bearing = (bearing + 360) % 360; // 0-360 범위로 정규화
|
|
|
|
return bearing;
|
|
}
|
|
|
|
/**
|
|
* 좌표 배열의 경계 상자(bounding box) 계산
|
|
*
|
|
* @param coordinates - 좌표 배열
|
|
* @returns { minLat, maxLat, minLon, maxLon }
|
|
*/
|
|
export function getBoundingBox(
|
|
coordinates: Array<{ latitude: number; longitude: number }>
|
|
): { minLat: number; maxLat: number; minLon: number; maxLon: number } | null {
|
|
if (coordinates.length === 0) return null;
|
|
|
|
let minLat = coordinates[0].latitude;
|
|
let maxLat = coordinates[0].latitude;
|
|
let minLon = coordinates[0].longitude;
|
|
let maxLon = coordinates[0].longitude;
|
|
|
|
for (const coord of coordinates) {
|
|
minLat = Math.min(minLat, coord.latitude);
|
|
maxLat = Math.max(maxLat, coord.latitude);
|
|
minLon = Math.min(minLon, coord.longitude);
|
|
maxLon = Math.max(maxLon, coord.longitude);
|
|
}
|
|
|
|
return { minLat, maxLat, minLon, maxLon };
|
|
}
|
|
|
|
/**
|
|
* 좌표 배열의 중심점 계산
|
|
*
|
|
* @param coordinates - 좌표 배열
|
|
* @returns { latitude, longitude } 중심점
|
|
*/
|
|
export function getCenterPoint(
|
|
coordinates: Array<{ latitude: number; longitude: number }>
|
|
): { latitude: number; longitude: number } | null {
|
|
if (coordinates.length === 0) return null;
|
|
|
|
let sumLat = 0;
|
|
let sumLon = 0;
|
|
|
|
for (const coord of coordinates) {
|
|
sumLat += coord.latitude;
|
|
sumLon += coord.longitude;
|
|
}
|
|
|
|
return {
|
|
latitude: sumLat / coordinates.length,
|
|
longitude: sumLon / coordinates.length,
|
|
};
|
|
}
|