import { useEffect, useState } from 'react'; import { Map, useControl } from '@vis.gl/react-maplibre'; import { MapboxOverlay } from '@deck.gl/mapbox'; import { GeoJsonLayer } from '@deck.gl/layers'; import type { Layer } from '@deck.gl/core'; import 'maplibre-gl/dist/maplibre-gl.css'; import { useBaseMapStyle } from '@common/hooks/useBaseMapStyle'; import { S57EncOverlay } from '@common/components/map/S57EncOverlay'; import { useMapStore } from '@common/store/mapStore'; const MAP_CENTER: [number, number] = [127.5, 36.0]; const MAP_ZOOM = 5.5; const CONSIDER_FILL: [number, number, number, number] = [59, 130, 246, 60]; const CONSIDER_LINE: [number, number, number, number] = [59, 130, 246, 220]; const RESTRICT_FILL: [number, number, number, number] = [239, 68, 68, 60]; const RESTRICT_LINE: [number, number, number, number] = [239, 68, 68, 220]; type ZoneKey = 'consider' | 'restrict'; // deck.gl 오버레이 컴포넌트 function DeckGLOverlay({ layers }: { layers: Layer[] }) { const overlay = useControl(() => new MapboxOverlay({ interleaved: true })); overlay.setProps({ layers }); return null; } // 구역 설명 데이터 const ZONE_INFO: Record = { consider: { label: '사용고려해역', rows: [ { key: '수심', value: '20m 이상 ※ (IMO) 대형 20m, 중소형 10m 이상' }, { key: '사용거리', value: '해안 2km, 중요 민감자원으로부터 5km 이상 떨어진 경우 ※ (IMO) 대형 1km, 중소형 0.5km 이상', }, { key: '사용승인(절차)', value: '현장 방제책임자 재량 사용 ※ (IMO) 의결정 절차 지침' }, ], }, restrict: { label: '사용제한해역', rows: [ { key: '수심', value: '수심 10m 이하' }, { key: '사용거리', value: '어장·양식장, 발전소 취수구, 종묘배양장 및 폐쇄성 해역 특정해역중 수자원 보호구역', }, { key: '사용승인(절차)', value: '심의위원회 승인을 받아 관할 방제책임기관 또는 방제대책 본부장이 결정 ※ 긴급한 경우 先사용, 後심의', }, ], }, }; const DispersingZonePanel = () => { const currentMapStyle = useBaseMapStyle(); const mapToggles = useMapStore((s) => s.mapToggles); const [showConsider, setShowConsider] = useState(true); const [showRestrict, setShowRestrict] = useState(true); const [expandedZone, setExpandedZone] = useState(null); // eslint-disable-next-line @typescript-eslint/no-explicit-any const [considerData, setConsiderData] = useState(null); // eslint-disable-next-line @typescript-eslint/no-explicit-any const [restrictData, setRestrictData] = useState(null); useEffect(() => { fetch('/dispersant-consider.geojson') .then(r => r.json()) .then(setConsiderData) .catch(() => {/* GeoJSON 없을 때 빈 상태 유지 */}); fetch('/dispersant-restrict.geojson') .then(r => r.json()) .then(setRestrictData) .catch(() => {/* GeoJSON 없을 때 빈 상태 유지 */}); }, []); const layers: Layer[] = [ ...(showConsider && considerData ? [ new GeoJsonLayer({ id: 'dispersant-consider', data: considerData, getFillColor: CONSIDER_FILL, getLineColor: CONSIDER_LINE, lineWidthMinPixels: 1.5, pickable: false, }), ] : []), ...(showRestrict && restrictData ? [ new GeoJsonLayer({ id: 'dispersant-restrict', data: restrictData, getFillColor: RESTRICT_FILL, getLineColor: RESTRICT_LINE, lineWidthMinPixels: 1.5, pickable: false, }), ] : []), ]; const handleToggleExpand = (zone: ZoneKey) => { setExpandedZone(prev => (prev === zone ? null : zone)); }; const renderZoneCard = (zone: ZoneKey) => { const info = ZONE_INFO[zone]; const isConsider = zone === 'consider'; const showLayer = isConsider ? showConsider : showRestrict; const setShowLayer = isConsider ? setShowConsider : setShowRestrict; const swatchColor = isConsider ? 'bg-blue-500' : 'bg-red-500'; const isExpanded = expandedZone === zone; return (
{/* 카드 헤더 */}
handleToggleExpand(zone)} > {info.label} {/* 토글 스위치 */} {/* 펼침 화살표 */} {isExpanded ? '▲' : '▼'}
{/* 펼침 영역 */} {isExpanded && (
{info.rows.map(row => ( ))}
{row.key} {row.value}
)}
); }; return (
{/* 지도 영역 */}
{/* 범례 */}
사용고려해역
사용제한해역
{/* 우측 패널 */}
{/* 헤더 */}

유처리제 제한구역

해양환경관리법 기준

{/* 구역 카드 목록 */}
{renderZoneCard('consider')} {renderZoneCard('restrict')}
); }; export default DispersingZonePanel;