import { useMemo, useCallback, useEffect, useRef } from 'react' import { Map, useControl, useMap } from '@vis.gl/react-maplibre' import { MapboxOverlay } from '@deck.gl/mapbox' import { ScatterplotLayer } from '@deck.gl/layers' 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' import type { AssetOrgCompat } from '../services/assetsApi' import { typeColor } from './assetTypes' import { hexToRgba } from '@common/components/map/mapUtils' // ── DeckGLOverlay ────────────────────────────────────── // eslint-disable-next-line @typescript-eslint/no-explicit-any function DeckGLOverlay({ layers }: { layers: any[] }) { const overlay = useControl(() => new MapboxOverlay({ interleaved: true })) overlay.setProps({ layers }) return null } // ── FlyTo Controller ──────────────────────────────────── function FlyToController({ selectedOrg }: { selectedOrg: AssetOrgCompat }) { const { current: map } = useMap() const prevIdRef = useRef(undefined) useEffect(() => { if (!map) return if (prevIdRef.current !== undefined && prevIdRef.current !== selectedOrg.id) { map.flyTo({ center: [selectedOrg.lng, selectedOrg.lat], zoom: 10, duration: 800 }) } prevIdRef.current = selectedOrg.id }, [map, selectedOrg]) return null } interface AssetMapProps { organizations: AssetOrgCompat[] selectedOrg: AssetOrgCompat onSelectOrg: (o: AssetOrgCompat) => void regionFilter: string onRegionFilterChange: (v: string) => void } function AssetMap({ organizations: orgs, selectedOrg, onSelectOrg, regionFilter, onRegionFilterChange, }: AssetMapProps) { const currentMapStyle = useBaseMapStyle() const mapToggles = useMapStore((s) => s.mapToggles) const handleClick = useCallback( (org: AssetOrgCompat) => { onSelectOrg(org) }, [onSelectOrg], ) const markerLayer = useMemo(() => { return new ScatterplotLayer({ id: 'asset-orgs', data: orgs, getPosition: (d: AssetOrgCompat) => [d.lng, d.lat], getRadius: (d: AssetOrgCompat) => { const baseRadius = d.pinSize === 'hq' ? 14 : d.pinSize === 'lg' ? 10 : 7 const isSelected = selectedOrg.id === d.id return isSelected ? baseRadius + 4 : baseRadius }, getFillColor: (d: AssetOrgCompat) => { const tc = typeColor(d.type) const isSelected = selectedOrg.id === d.id return hexToRgba(isSelected ? tc.selected : tc.border, isSelected ? 229 : 178) }, getLineColor: (d: AssetOrgCompat) => { const tc = typeColor(d.type) const isSelected = selectedOrg.id === d.id return hexToRgba(isSelected ? tc.selected : tc.border, isSelected ? 255 : 200) }, getLineWidth: (d: AssetOrgCompat) => (selectedOrg.id === d.id ? 3 : 2), stroked: true, radiusMinPixels: 4, radiusMaxPixels: 20, radiusUnits: 'pixels', pickable: true, onClick: (info: { object?: AssetOrgCompat }) => { if (info.object) handleClick(info.object) }, updateTriggers: { getRadius: [selectedOrg.id], getFillColor: [selectedOrg.id], getLineColor: [selectedOrg.id], getLineWidth: [selectedOrg.id], }, }) }, [orgs, selectedOrg, handleClick]) return (
{/* Region filter overlay */}
{[ { value: 'all', label: '전체' }, { value: '남해', label: '남해청' }, { value: '서해', label: '서해청' }, { value: '중부', label: '중부청' }, { value: '동해', label: '동해청' }, { value: '제주', label: '제주청' }, ].map(r => ( ))}
{/* Legend overlay */}
범례
{[ { color: '#06b6d4', label: '해경관할' }, { color: '#3b82f6', label: '해경경찰서' }, { color: '#22c55e', label: '파출소' }, { color: '#a855f7', label: '관련기관' }, { color: '#14b8a6', label: '해양환경공단' }, { color: '#f59e0b', label: '업체' }, { color: '#ec4899', label: '지자체' }, { color: '#8b5cf6', label: '기름저장시설' }, { color: '#0d9488', label: '정유사' }, { color: '#64748b', label: '해군' }, { color: '#6b7280', label: '기타' }, ].map((item, i) => (
{item.label}
))}
) } export default AssetMap