perf: 렌더링 성능 최적화 + 환적 Python 이관 + 중국어선감시 통합 #158

병합
htlee perf/rendering-optimization 에서 develop 로 9 commits 를 머지했습니다 2026-03-23 13:16:25 +09:00
Showing only changes of commit 18b827ced0 - Show all commits

파일 보기

@ -189,7 +189,8 @@ export function KoreaMap({ ships, allShips, aircraft, satellites, layers, osintF
);
}, []);
// 줌 레벨별 아이콘/심볼 스케일 배율 — z4=1.0x 기준, 2단계씩 상향
const anyKoreaFilterOn = koreaFilters.illegalFishing || koreaFilters.illegalTransship || koreaFilters.darkVessel || koreaFilters.cableWatch || koreaFilters.dokdoWatch || koreaFilters.ferryWatch;
// 줌 레벨별 아이콘/심볼 스케일 배율 — z4=0.8x, z6=1.0x 시작, 2단계씩 상향
const zoomScale = useMemo(() => {
if (zoomLevel <= 4) return 0.8;
@ -568,7 +569,13 @@ export function KoreaMap({ ships, allShips, aircraft, satellites, layers, osintF
/>
</Source>
{layers.ships && <ShipLayer ships={allShips ?? ships} militaryOnly={layers.militaryOnly} analysisMap={vesselAnalysis?.analysisMap} hiddenShipCategories={hiddenShipCategories} hiddenNationalities={hiddenNationalities} />}
{layers.ships && <ShipLayer
ships={anyKoreaFilterOn ? ships : (allShips ?? ships)}
militaryOnly={layers.militaryOnly}
analysisMap={vesselAnalysis?.analysisMap}
hiddenShipCategories={hiddenShipCategories}
hiddenNationalities={hiddenNationalities}
/>}
{/* Transship suspect labels — Marker DOM, inline styles kept for dynamic border color */}
{transshipSuspects.size > 0 && ships.filter(s => transshipSuspects.has(s.mmsi)).map(s => (
<Marker key={`ts-${s.mmsi}`} longitude={s.lng} latitude={s.lat} anchor="bottom">
@ -671,14 +678,29 @@ export function KoreaMap({ ships, allShips, aircraft, satellites, layers, osintF
{layers.osint && <OsintMapLayer osintFeed={osintFeed} currentTime={currentTime} />}
{layers.eez && <EezLayer />}
{/* Filter Status Banner */}
{/* Filter Status Banner — 필터별 개별 탐지 카운트 */}
{(() => {
const active = (Object.keys(koreaFilters) as (keyof KoreaFiltersState)[]).filter(k => koreaFilters[k]);
if (active.length === 0) return null;
const filterCount: Record<string, number> = {
illegalFishing: (allShips ?? ships).filter(s => {
if (s.mtCategory !== 'fishing' || s.flag === 'KR') return false;
return classifyFishingZone(s.lat, s.lng).zone !== 'OUTSIDE';
}).length,
illegalTransship: transshipSuspects.size,
darkVessel: ships.filter(s => {
const dto = vesselAnalysis?.analysisMap.get(s.mmsi);
return dto?.algorithms.darkVessel.isDark || (s.lastSeen && currentTime - s.lastSeen > 3600000);
}).length,
cableWatch: cableWatchSuspects.size,
dokdoWatch: dokdoWatchSuspects.size,
ferryWatch: (allShips ?? ships).filter(s => s.mtCategory === 'passenger').length,
};
return (
<div className="absolute top-2.5 left-1/2 -translate-x-1/2 z-20 flex gap-1.5 backdrop-blur-lg">
{active.map(k => {
const color = FILTER_COLOR[k];
const count = filterCount[k] ?? 0;
return (
<div
key={k}
@ -690,12 +712,10 @@ export function KoreaMap({ ships, allShips, aircraft, satellites, layers, osintF
>
<span className="text-[13px]">{FILTER_ICON[k]}</span>
{t(FILTER_I18N_KEY[k])}
<span className="ml-0.5 text-white/80">{count}</span>
</div>
);
})}
<div className="rounded-lg px-3 py-1.5 font-mono text-xs font-bold flex items-center bg-kcg-glass border border-kcg-border-light text-white">
{t('korea.detected', { count: ships.length })}
</div>
</div>
);
})()}