kcg-monitoring/frontend/src/components/korea/FishingZoneLayer.tsx
htlee af02ad12ff feat: 불법어선 필터 시 수역 폴리곤 오버레이 + 선박 마커 가시성 개선
- WGS84 사전 변환 GeoJSON 생성 (런타임 변환 제거)
- FishingZoneLayer: 수역별 색상 fill/line + 이름 라벨
- AnalysisOverlay: 마커 크기 확대, 한글 라벨, 선박명 표시
- fishingAnalysis.ts: EPSG:3857 변환 로직 제거, WGS84 JSON 직접 사용

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 14:05:35 +09:00

94 lines
2.7 KiB
TypeScript

import { useMemo } from 'react';
import { Source, Layer, Marker } from 'react-map-gl/maplibre';
import fishingZonesData from '../../data/zones/fishing-zones-wgs84.json';
const ZONE_FILL: Record<string, string> = {
ZONE_I: 'rgba(59, 130, 246, 0.15)',
ZONE_II: 'rgba(16, 185, 129, 0.15)',
ZONE_III: 'rgba(245, 158, 11, 0.15)',
ZONE_IV: 'rgba(239, 68, 68, 0.15)',
};
const ZONE_LINE: Record<string, string> = {
ZONE_I: 'rgba(59, 130, 246, 0.6)',
ZONE_II: 'rgba(16, 185, 129, 0.6)',
ZONE_III: 'rgba(245, 158, 11, 0.6)',
ZONE_IV: 'rgba(239, 68, 68, 0.6)',
};
/** 폴리곤 중심점 (좌표 평균) */
function centroid(coordinates: number[][][][]): [number, number] {
let sLng = 0, sLat = 0, n = 0;
for (const poly of coordinates) {
for (const ring of poly) {
for (const [lng, lat] of ring) {
sLng += lng; sLat += lat; n++;
}
}
}
return n > 0 ? [sLng / n, sLat / n] : [0, 0];
}
const fillColor = [
'match', ['get', 'id'],
'ZONE_I', ZONE_FILL.ZONE_I,
'ZONE_II', ZONE_FILL.ZONE_II,
'ZONE_III', ZONE_FILL.ZONE_III,
'ZONE_IV', ZONE_FILL.ZONE_IV,
'rgba(0,0,0,0)',
] as maplibregl.ExpressionSpecification;
const lineColor = [
'match', ['get', 'id'],
'ZONE_I', ZONE_LINE.ZONE_I,
'ZONE_II', ZONE_LINE.ZONE_II,
'ZONE_III', ZONE_LINE.ZONE_III,
'ZONE_IV', ZONE_LINE.ZONE_IV,
'rgba(0,0,0,0)',
] as maplibregl.ExpressionSpecification;
export function FishingZoneLayer() {
const labels = useMemo(() =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
fishingZonesData.features.map((f: any) => {
const [lng, lat] = centroid(f.geometry.coordinates);
return { id: f.properties.id as string, name: f.properties.name as string, lng, lat };
}), []);
return (
<>
<Source id="fishing-zones" type="geojson" data={fishingZonesData as GeoJSON.FeatureCollection}>
<Layer
id="fishing-zone-fill"
type="fill"
paint={{ 'fill-color': fillColor, 'fill-opacity': 1 }}
/>
<Layer
id="fishing-zone-line"
type="line"
paint={{
'line-color': lineColor,
'line-opacity': 1,
'line-width': 1.5,
'line-dasharray': [4, 2],
}}
/>
</Source>
{labels.map(({ id, name, lng, lat }) => (
<Marker key={`zone-${id}`} longitude={lng} latitude={lat} anchor="center">
<div style={{
fontSize: 10, fontWeight: 700, color: '#fff',
textShadow: '0 0 3px #000, 0 0 3px #000',
backgroundColor: 'rgba(0,0,0,0.45)',
borderRadius: 3, padding: '1px 5px',
whiteSpace: 'nowrap', pointerEvents: 'none',
}}>
{name}
</div>
</Marker>
))}
</>
);
}