- 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>
128 lines
2.9 KiB
TypeScript
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>
|
|
);
|
|
}
|