From 8c008c69ec98186b2d413925c4c704a835024c92 Mon Sep 17 00:00:00 2001 From: htlee Date: Fri, 20 Mar 2026 19:07:46 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=84=A0=ED=83=9D=20=EC=96=B4=EA=B5=AC?= =?UTF-8?q?=EA=B7=B8=EB=A3=B9=20=ED=95=98=EC=9D=B4=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20=ED=8F=B4=EB=A6=AC=EA=B3=A4=20+=20=EB=AA=A8?= =?UTF-8?q?=EC=84=A0=20=EA=B0=95=EC=A1=B0=20=EB=A7=88=EC=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 선택된 어구그룹: 진한 주황 fill(0.25) + 굵은 경계선(3px) - 모선 존재 시: 28px 주황 원 + glow + 'M' 라벨 + 선박명 - zoom 시 자동 선택 + 펼침 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../components/korea/FleetClusterLayer.tsx | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/korea/FleetClusterLayer.tsx b/frontend/src/components/korea/FleetClusterLayer.tsx index 920bc45..9163531 100644 --- a/frontend/src/components/korea/FleetClusterLayer.tsx +++ b/frontend/src/components/korea/FleetClusterLayer.tsx @@ -1,5 +1,5 @@ import { useState, useEffect, useMemo, useCallback } from 'react'; -import { Source, Layer } from 'react-map-gl/maplibre'; +import { Source, Layer, Marker } from 'react-map-gl/maplibre'; import type { GeoJSON } from 'geojson'; import type { Ship, VesselAnalysisDto } from '../../types'; import { fetchFleetCompanies } from '../../services/vesselAnalysis'; @@ -89,6 +89,7 @@ export function FleetClusterLayer({ ships, analysisMap, clusters, onShipSelect, const [expandedFleet, setExpandedFleet] = useState(null); const [hoveredFleetId, setHoveredFleetId] = useState(null); const [expandedGearGroup, setExpandedGearGroup] = useState(null); + const [selectedGearGroup, setSelectedGearGroup] = useState(null); useEffect(() => { fetchFleetCompanies().then(setCompanies).catch(() => {}); @@ -199,6 +200,8 @@ export function FleetClusterLayer({ ships, analysisMap, clusters, onShipSelect, }, [gearGroupMap]); const handleGearGroupZoom = useCallback((parentName: string) => { + setSelectedGearGroup(prev => prev === parentName ? null : parentName); + setExpandedGearGroup(parentName); const entry = gearGroupMap.get(parentName); if (!entry) return; const all: Ship[] = [...entry.gears]; @@ -383,6 +386,60 @@ export function FleetClusterLayer({ ships, analysisMap, clusters, onShipSelect, /> + {/* 선택된 어구 그룹 하이라이트 + 모선 마커 */} + {selectedGearGroup && (() => { + const entry = gearGroupMap.get(selectedGearGroup); + if (!entry) return null; + const points: [number, number][] = entry.gears.map(g => [g.lng, g.lat]); + if (entry.parent) points.push([entry.parent.lng, entry.parent.lat]); + + const hlFeatures: GeoJSON.Feature[] = []; + if (points.length >= 3) { + const hull = convexHull(points); + const padded = padPolygon(hull, 0.01); + padded.push(padded[0]); + hlFeatures.push({ + type: 'Feature', + properties: {}, + geometry: { type: 'Polygon', coordinates: [padded] }, + }); + } + const hlGeoJson: GeoJSON.FeatureCollection = { type: 'FeatureCollection', features: hlFeatures }; + + return ( + <> + {hlFeatures.length > 0 && ( + + + + + )} + {entry.parent && ( + +
+ M +
+
+ {entry.parent.name || entry.parent.mmsi} +
+
+ )} + + ); + })()} + {/* 비허가 어구 클러스터 폴리곤 */}