ERP-node/backend-node/src/utils/geoUtils.ts

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,
};
}