kcg-monitoring/frontend/src/components/EezLayer.tsx
htlee 2534faa488 feat: 프론트엔드 모노레포 이관 + signal-batch 연동 + Tailwind/i18n/테마 전환
- frontend/ 폴더로 프론트엔드 전체 이관
- signal-batch API 연동 (한국 선박 위치 데이터)
- Tailwind CSS 4 + CSS 변수 테마 토큰 (dark/light)
- i18next 다국어 (ko/en) 인프라 + 28개 컴포넌트 적용
- 레이어 패널 트리 구조 재설계 (카테고리별 온/오프, 범례)
- Google OAuth 로그인 화면 + DEV LOGIN 우회
- 외부 API CORS 프록시 전환 (Airplanes.live, OpenSky, CelesTrak)
- ShipLayer 이미지 탭 전환 (signal-batch / MarineTraffic)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 13:54:41 +09:00

128 lines
2.9 KiB
TypeScript

import { Source, Layer } from 'react-map-gl/maplibre';
import { KOREA_EEZ_BOUNDARY, KOREA_CHINA_PMZ, NLL_WEST_SEA, NLL_EAST_SEA } from '../services/koreaEez';
import type { FillLayerSpecification, LineLayerSpecification } from 'maplibre-gl';
// Convert [lat, lng][] to GeoJSON [lng, lat][] ring
function toRing(coords: [number, number][]): [number, number][] {
return coords.map(([lat, lng]) => [lng, lat]);
}
function toLineCoords(coords: [number, number][]): [number, number][] {
return coords.map(([lat, lng]) => [lng, lat]);
}
const eezGeoJSON: GeoJSON.FeatureCollection = {
type: 'FeatureCollection',
features: [
// EEZ 경계 폴리곤
{
type: 'Feature',
properties: { type: 'eez' },
geometry: {
type: 'Polygon',
coordinates: [toRing(KOREA_EEZ_BOUNDARY)],
},
},
// 한중 잠정조치수역
{
type: 'Feature',
properties: { type: 'pmz' },
geometry: {
type: 'Polygon',
coordinates: [toRing(KOREA_CHINA_PMZ)],
},
},
// 서해 NLL
{
type: 'Feature',
properties: { type: 'nll' },
geometry: {
type: 'LineString',
coordinates: toLineCoords(NLL_WEST_SEA),
},
},
// 동해 NLL
{
type: 'Feature',
properties: { type: 'nll' },
geometry: {
type: 'LineString',
coordinates: toLineCoords(NLL_EAST_SEA),
},
},
],
};
const eezFillStyle: FillLayerSpecification = {
id: 'eez-fill',
type: 'fill',
source: 'eez-source',
filter: ['==', ['get', 'type'], 'eez'],
paint: {
'fill-color': '#3b82f6',
'fill-opacity': 0.06,
},
};
const eezLineStyle: LineLayerSpecification = {
id: 'eez-line',
type: 'line',
source: 'eez-source',
filter: ['==', ['get', 'type'], 'eez'],
paint: {
'line-color': '#3b82f6',
'line-width': 1.5,
'line-dasharray': [4, 3],
'line-opacity': 0.6,
},
};
const pmzFillStyle: FillLayerSpecification = {
id: 'pmz-fill',
type: 'fill',
source: 'eez-source',
filter: ['==', ['get', 'type'], 'pmz'],
paint: {
'fill-color': '#eab308',
'fill-opacity': 0.08,
},
};
const pmzLineStyle: LineLayerSpecification = {
id: 'pmz-line',
type: 'line',
source: 'eez-source',
filter: ['==', ['get', 'type'], 'pmz'],
paint: {
'line-color': '#eab308',
'line-width': 1.2,
'line-dasharray': [3, 2],
'line-opacity': 0.5,
},
};
const nllLineStyle: LineLayerSpecification = {
id: 'nll-line',
type: 'line',
source: 'eez-source',
filter: ['==', ['get', 'type'], 'nll'],
paint: {
'line-color': '#ef4444',
'line-width': 2,
'line-dasharray': [6, 4],
'line-opacity': 0.7,
},
};
export function EezLayer() {
return (
<Source id="eez-source" type="geojson" data={eezGeoJSON}>
<Layer {...eezFillStyle} />
<Layer {...eezLineStyle} />
<Layer {...pmzFillStyle} />
<Layer {...pmzLineStyle} />
<Layer {...nllLineStyle} />
</Source>
);
}