Merge pull request 'feat: 어구그룹 선택 하이라이트 + 모선 마커' (#140) from fix/gear-group-highlight into develop
This commit is contained in:
커밋
8323a248a7
@ -1,5 +1,5 @@
|
|||||||
import { useState, useEffect, useMemo, useCallback } from 'react';
|
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 { GeoJSON } from 'geojson';
|
||||||
import type { Ship, VesselAnalysisDto } from '../../types';
|
import type { Ship, VesselAnalysisDto } from '../../types';
|
||||||
import { fetchFleetCompanies } from '../../services/vesselAnalysis';
|
import { fetchFleetCompanies } from '../../services/vesselAnalysis';
|
||||||
@ -89,6 +89,7 @@ export function FleetClusterLayer({ ships, analysisMap, clusters, onShipSelect,
|
|||||||
const [expandedFleet, setExpandedFleet] = useState<number | null>(null);
|
const [expandedFleet, setExpandedFleet] = useState<number | null>(null);
|
||||||
const [hoveredFleetId, setHoveredFleetId] = useState<number | null>(null);
|
const [hoveredFleetId, setHoveredFleetId] = useState<number | null>(null);
|
||||||
const [expandedGearGroup, setExpandedGearGroup] = useState<string | null>(null);
|
const [expandedGearGroup, setExpandedGearGroup] = useState<string | null>(null);
|
||||||
|
const [selectedGearGroup, setSelectedGearGroup] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchFleetCompanies().then(setCompanies).catch(() => {});
|
fetchFleetCompanies().then(setCompanies).catch(() => {});
|
||||||
@ -199,6 +200,8 @@ export function FleetClusterLayer({ ships, analysisMap, clusters, onShipSelect,
|
|||||||
}, [gearGroupMap]);
|
}, [gearGroupMap]);
|
||||||
|
|
||||||
const handleGearGroupZoom = useCallback((parentName: string) => {
|
const handleGearGroupZoom = useCallback((parentName: string) => {
|
||||||
|
setSelectedGearGroup(prev => prev === parentName ? null : parentName);
|
||||||
|
setExpandedGearGroup(parentName);
|
||||||
const entry = gearGroupMap.get(parentName);
|
const entry = gearGroupMap.get(parentName);
|
||||||
if (!entry) return;
|
if (!entry) return;
|
||||||
const all: Ship[] = [...entry.gears];
|
const all: Ship[] = [...entry.gears];
|
||||||
@ -383,6 +386,60 @@ export function FleetClusterLayer({ ships, analysisMap, clusters, onShipSelect,
|
|||||||
/>
|
/>
|
||||||
</Source>
|
</Source>
|
||||||
|
|
||||||
|
{/* 선택된 어구 그룹 하이라이트 + 모선 마커 */}
|
||||||
|
{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 && (
|
||||||
|
<Source id="gear-cluster-selected" type="geojson" data={hlGeoJson}>
|
||||||
|
<Layer id="gear-selected-fill" type="fill" paint={{ 'fill-color': '#f97316', 'fill-opacity': 0.25 }} />
|
||||||
|
<Layer id="gear-selected-line" type="line" paint={{ 'line-color': '#f97316', 'line-width': 3, 'line-opacity': 0.9 }} />
|
||||||
|
</Source>
|
||||||
|
)}
|
||||||
|
{entry.parent && (
|
||||||
|
<Marker longitude={entry.parent.lng} latitude={entry.parent.lat} anchor="center">
|
||||||
|
<div style={{
|
||||||
|
width: 28, height: 28, borderRadius: '50%',
|
||||||
|
border: '3px solid #f97316',
|
||||||
|
backgroundColor: 'rgba(249, 115, 22, 0.3)',
|
||||||
|
boxShadow: '0 0 12px rgba(249, 115, 22, 0.6)',
|
||||||
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
}}>
|
||||||
|
<span style={{ fontSize: 9, fontWeight: 900, color: '#fff' }}>M</span>
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
fontSize: 8, fontWeight: 700, color: '#f97316',
|
||||||
|
textShadow: '0 0 3px #000, 0 0 3px #000',
|
||||||
|
textAlign: 'center', marginTop: 2, whiteSpace: 'nowrap',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
}}>
|
||||||
|
{entry.parent.name || entry.parent.mmsi}
|
||||||
|
</div>
|
||||||
|
</Marker>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
|
||||||
{/* 비허가 어구 클러스터 폴리곤 */}
|
{/* 비허가 어구 클러스터 폴리곤 */}
|
||||||
<Source id="gear-clusters" type="geojson" data={gearClusterGeoJson}>
|
<Source id="gear-clusters" type="geojson" data={gearClusterGeoJson}>
|
||||||
<Layer
|
<Layer
|
||||||
|
|||||||
불러오는 중...
Reference in New Issue
Block a user