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 type { StyleSpecification } from 'maplibre-gl'; import 'maplibre-gl/dist/maplibre-gl.css'; // CartoDB Dark Matter 스타일 const MAP_STYLE: StyleSpecification = { version: 8, sources: { 'carto-dark': { type: 'raster', tiles: [ 'https://a.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png', 'https://b.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png', 'https://c.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png', ], tileSize: 256, attribution: '© OpenStreetMap © CARTO', }, }, layers: [ { id: 'carto-dark-layer', type: 'raster', source: 'carto-dark', minzoom: 0, maxzoom: 22, }, ], }; 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 [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} {/* 토글 스위치 */} { e.stopPropagation(); setShowLayer(prev => !prev); }} title={showLayer ? '레이어 숨기기' : '레이어 표시'} className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors shrink-0 ${ showLayer ? 'bg-primary-cyan' : 'bg-[rgba(255,255,255,0.08)] border border-border' }`} > {/* 펼침 화살표 */} {isExpanded ? '▲' : '▼'} {/* 펼침 영역 */} {isExpanded && ( {info.rows.map(row => ( {row.key} {row.value} ))} )} ); }; return ( {/* 지도 영역 */} {/* 범례 */} 사용고려해역 사용제한해역 {/* 우측 패널 */} {/* 헤더 */} 유처리제 제한구역 해양환경관리법 기준 {/* 구역 카드 목록 */} {renderZoneCard('consider')} {renderZoneCard('restrict')} ); }; export default DispersingZonePanel;
해양환경관리법 기준