- useTrackingMode 훅 (함정 중심 지도 이동 + 반경 원) - useRadiusFilter 훅 (Bounding Box + Haversine 거리 계산) - shipStore 반경 필터 연동 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
168 lines
4.9 KiB
JavaScript
168 lines
4.9 KiB
JavaScript
/**
|
|
* 반경 필터링 훅
|
|
* 선박 모드일 때 추적 함정 중심으로 반경 내 선박만 필터링
|
|
*
|
|
* 성능 최적화:
|
|
* - Bounding Box 사전 필터링 (빠른 사각형 체크)
|
|
* - 그 후 정확한 원형 거리 계산
|
|
*/
|
|
import { useCallback, useMemo } from 'react';
|
|
import useTrackingModeStore, {
|
|
isWithinRadius,
|
|
NM_TO_METERS,
|
|
} from '../stores/trackingModeStore';
|
|
|
|
/**
|
|
* 경도 1도당 대략적인 미터 (위도에 따라 다름)
|
|
* 중위도(35도) 기준 약 91km
|
|
*/
|
|
const LON_DEGREE_METERS = 91000;
|
|
const LAT_DEGREE_METERS = 111000; // 위도 1도당 약 111km
|
|
|
|
/**
|
|
* 반경 필터링 훅
|
|
* @returns {Object} { filterByRadius, isRadiusFilterActive, getRadiusCenter, radiusNM }
|
|
*/
|
|
export default function useRadiusFilter() {
|
|
const mode = useTrackingModeStore((s) => s.mode);
|
|
const trackedShip = useTrackingModeStore((s) => s.trackedShip);
|
|
const radiusNM = useTrackingModeStore((s) => s.radiusNM);
|
|
|
|
// 반경 필터 활성화 여부
|
|
const isRadiusFilterActive = mode === 'ship' && trackedShip !== null;
|
|
|
|
// 반경 중심 좌표
|
|
const radiusCenter = useMemo(() => {
|
|
if (!isRadiusFilterActive || !trackedShip) return null;
|
|
return {
|
|
lon: trackedShip.longitude,
|
|
lat: trackedShip.latitude,
|
|
};
|
|
}, [isRadiusFilterActive, trackedShip]);
|
|
|
|
/**
|
|
* Bounding Box 계산 (사전 필터링용)
|
|
* 반경을 감싸는 사각형 영역
|
|
*/
|
|
const boundingBox = useMemo(() => {
|
|
if (!radiusCenter) return null;
|
|
|
|
const radiusMeters = radiusNM * NM_TO_METERS;
|
|
const lonDelta = radiusMeters / LON_DEGREE_METERS;
|
|
const latDelta = radiusMeters / LAT_DEGREE_METERS;
|
|
|
|
return {
|
|
minLon: radiusCenter.lon - lonDelta,
|
|
maxLon: radiusCenter.lon + lonDelta,
|
|
minLat: radiusCenter.lat - latDelta,
|
|
maxLat: radiusCenter.lat + latDelta,
|
|
};
|
|
}, [radiusCenter, radiusNM]);
|
|
|
|
/**
|
|
* 선박이 Bounding Box 내에 있는지 빠른 체크
|
|
*/
|
|
const isInBoundingBox = useCallback((ship) => {
|
|
if (!boundingBox) return true;
|
|
if (!ship.longitude || !ship.latitude) return false;
|
|
|
|
return (
|
|
ship.longitude >= boundingBox.minLon &&
|
|
ship.longitude <= boundingBox.maxLon &&
|
|
ship.latitude >= boundingBox.minLat &&
|
|
ship.latitude <= boundingBox.maxLat
|
|
);
|
|
}, [boundingBox]);
|
|
|
|
/**
|
|
* 선박 배열을 반경 내 선박만 필터링
|
|
* @param {Array} ships - 선박 배열
|
|
* @returns {Array} 반경 내 선박만
|
|
*/
|
|
const filterByRadius = useCallback((ships) => {
|
|
// 반경 필터 비활성화 시 전체 반환
|
|
if (!isRadiusFilterActive || !radiusCenter) {
|
|
return ships;
|
|
}
|
|
|
|
return ships.filter((ship) => {
|
|
// 1단계: Bounding Box 체크 (빠른 사전 필터)
|
|
if (!isInBoundingBox(ship)) return false;
|
|
|
|
// 2단계: 정확한 원형 거리 체크
|
|
return isWithinRadius(ship, radiusCenter.lon, radiusCenter.lat, radiusNM);
|
|
});
|
|
}, [isRadiusFilterActive, radiusCenter, radiusNM, isInBoundingBox]);
|
|
|
|
/**
|
|
* 단일 선박이 반경 내에 있는지 확인
|
|
* @param {Object} ship
|
|
* @returns {boolean}
|
|
*/
|
|
const isShipInRadius = useCallback((ship) => {
|
|
if (!isRadiusFilterActive || !radiusCenter) return true;
|
|
if (!isInBoundingBox(ship)) return false;
|
|
return isWithinRadius(ship, radiusCenter.lon, radiusCenter.lat, radiusNM);
|
|
}, [isRadiusFilterActive, radiusCenter, radiusNM, isInBoundingBox]);
|
|
|
|
/**
|
|
* Map의 features를 반경 내 선박만 필터링
|
|
* @param {Map} featuresMap - featureId -> ship Map
|
|
* @returns {Map} 반경 내 선박만
|
|
*/
|
|
const filterFeaturesMapByRadius = useCallback((featuresMap) => {
|
|
if (!isRadiusFilterActive || !radiusCenter) {
|
|
return featuresMap;
|
|
}
|
|
|
|
const filteredMap = new Map();
|
|
featuresMap.forEach((ship, featureId) => {
|
|
if (isInBoundingBox(ship) && isWithinRadius(ship, radiusCenter.lon, radiusCenter.lat, radiusNM)) {
|
|
filteredMap.set(featureId, ship);
|
|
}
|
|
});
|
|
|
|
return filteredMap;
|
|
}, [isRadiusFilterActive, radiusCenter, radiusNM, isInBoundingBox]);
|
|
|
|
return {
|
|
filterByRadius,
|
|
filterFeaturesMapByRadius,
|
|
isShipInRadius,
|
|
isRadiusFilterActive,
|
|
radiusCenter,
|
|
radiusNM,
|
|
boundingBox,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 반경 필터 유틸리티 (비훅 버전)
|
|
* shipStore나 다른 스토어에서 직접 사용
|
|
*/
|
|
export function getRadiusFilterState() {
|
|
const state = useTrackingModeStore.getState();
|
|
const { mode, trackedShip, radiusNM } = state;
|
|
|
|
const isActive = mode === 'ship' && trackedShip !== null;
|
|
|
|
if (!isActive || !trackedShip) {
|
|
return { isActive: false, center: null, radiusNM: 0 };
|
|
}
|
|
|
|
return {
|
|
isActive: true,
|
|
center: { lon: trackedShip.longitude, lat: trackedShip.latitude },
|
|
radiusNM,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 선박이 반경 내에 있는지 확인 (비훅 버전)
|
|
*/
|
|
export function checkShipInRadius(ship) {
|
|
const { isActive, center, radiusNM } = getRadiusFilterState();
|
|
if (!isActive || !center) return true;
|
|
return isWithinRadius(ship, center.lon, center.lat, radiusNM);
|
|
}
|