release: 2026-03-16 (81건 커밋) #93
@ -7,11 +7,11 @@ import type { DroneStreamItem } from '../services/aerialApi'
|
||||
import { CCTVPlayer } from './CCTVPlayer'
|
||||
import type { CCTVPlayerHandle } from './CCTVPlayer'
|
||||
|
||||
/** 드론 위치 좌표 (함정 모항 기준) */
|
||||
const DRONE_COORDS: Record<string, { lat: number; lon: number }> = {
|
||||
'busan-1501': { lat: 35.0796, lon: 129.0756 },
|
||||
'incheon-3008': { lat: 37.4541, lon: 126.5986 },
|
||||
'mokpo-3015': { lat: 34.7780, lon: 126.3780 },
|
||||
/** 함정 위치 + 드론 비행 위치 */
|
||||
const DRONE_POSITIONS: Record<string, { ship: { lat: number; lon: number }; drone: { lat: number; lon: number } }> = {
|
||||
'busan-1501': { ship: { lat: 35.0796, lon: 129.0756 }, drone: { lat: 35.1100, lon: 129.1100 } },
|
||||
'incheon-3008': { ship: { lat: 37.4541, lon: 126.5986 }, drone: { lat: 37.4800, lon: 126.5600 } },
|
||||
'mokpo-3015': { ship: { lat: 34.7780, lon: 126.3780 }, drone: { lat: 34.8050, lon: 126.4100 } },
|
||||
}
|
||||
|
||||
const DRONE_MAP_STYLE: StyleSpecification = {
|
||||
@ -266,55 +266,61 @@ export function RealtimeDrone() {
|
||||
attributionControl={false}
|
||||
>
|
||||
{streams.map(stream => {
|
||||
const coord = DRONE_COORDS[stream.id]
|
||||
if (!coord) return null
|
||||
const si = statusInfo(stream.status)
|
||||
const pos = DRONE_POSITIONS[stream.id]
|
||||
if (!pos) return null
|
||||
const statusColor = stream.status === 'streaming' ? '#22c55e' : stream.status === 'starting' ? '#06b6d4' : stream.status === 'error' ? '#ef4444' : '#94a3b8'
|
||||
return (
|
||||
<Marker
|
||||
key={stream.id}
|
||||
longitude={coord.lon}
|
||||
latitude={coord.lat}
|
||||
anchor="bottom"
|
||||
longitude={(pos.ship.lon + pos.drone.lon) / 2}
|
||||
latitude={(pos.ship.lat + pos.drone.lat) / 2}
|
||||
anchor="center"
|
||||
onClick={e => { e.originalEvent.stopPropagation(); setMapPopup(stream) }}
|
||||
>
|
||||
<div className="flex flex-col items-center cursor-pointer group" title={stream.shipName}>
|
||||
{/* 드론 SVG 아이콘 */}
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" className="drop-shadow-lg transition-transform group-hover:scale-110">
|
||||
<div className="cursor-pointer group" title={stream.shipName}>
|
||||
<svg width="120" height="80" viewBox="0 0 120 80" fill="none" className="drop-shadow-lg transition-transform group-hover:scale-105" style={{ overflow: 'visible' }}>
|
||||
{/* 연결선 (점선) */}
|
||||
<line x1="30" y1="55" x2="85" y2="28" stroke={statusColor} strokeWidth="1.5" strokeDasharray="4 3" opacity="0.6" />
|
||||
|
||||
{/* 함정 삼각형 (좌하단) */}
|
||||
<polygon points="30,45 20,65 40,65" fill="rgba(0,0,0,.5)" stroke={statusColor} strokeWidth="1.5" />
|
||||
<text x="30" y="59" textAnchor="middle" fill="#fff" fontSize="7" fontWeight="bold">▲</text>
|
||||
{/* 함정명 */}
|
||||
<rect x="5" y="67" width="50" height="13" rx="3" fill="rgba(0,0,0,.7)" />
|
||||
<text x="30" y="76" textAnchor="middle" fill="#fff" fontSize="7" fontFamily="sans-serif" fontWeight="bold">{stream.shipName.replace(/서 /, ' ')}</text>
|
||||
|
||||
{/* 드론 원형 아이콘 (우상단) */}
|
||||
<circle cx="85" cy="28" r="16" fill="rgba(0,0,0,.6)" stroke={statusColor} strokeWidth="2" />
|
||||
{/* 드론 내부 — 십자 프로펠러 */}
|
||||
<line x1="73" y1="28" x2="97" y2="28" stroke={statusColor} strokeWidth="1" opacity="0.4" />
|
||||
<line x1="85" y1="16" x2="85" y2="40" stroke={statusColor} strokeWidth="1" opacity="0.4" />
|
||||
{/* 프로펠러 4개 */}
|
||||
<circle cx="73" cy="28" r="4" fill={statusColor} opacity="0.25" />
|
||||
<circle cx="97" cy="28" r="4" fill={statusColor} opacity="0.25" />
|
||||
<circle cx="85" cy="16" r="4" fill={statusColor} opacity="0.25" />
|
||||
<circle cx="85" cy="40" r="4" fill={statusColor} opacity="0.25" />
|
||||
{/* 본체 */}
|
||||
<ellipse cx="16" cy="16" rx="5" ry="3" fill={si.color} opacity="0.9" />
|
||||
{/* 팔 4개 */}
|
||||
<line x1="11" y1="13" x2="5" y2="7" stroke={si.color} strokeWidth="1.5" />
|
||||
<line x1="21" y1="13" x2="27" y2="7" stroke={si.color} strokeWidth="1.5" />
|
||||
<line x1="11" y1="19" x2="5" y2="25" stroke={si.color} strokeWidth="1.5" />
|
||||
<line x1="21" y1="19" x2="27" y2="25" stroke={si.color} strokeWidth="1.5" />
|
||||
{/* 프로펠러 */}
|
||||
<circle cx="5" cy="7" r="3" fill={si.color} opacity="0.3" />
|
||||
<circle cx="27" cy="7" r="3" fill={si.color} opacity="0.3" />
|
||||
<circle cx="5" cy="25" r="3" fill={si.color} opacity="0.3" />
|
||||
<circle cx="27" cy="25" r="3" fill={si.color} opacity="0.3" />
|
||||
{/* 카메라 */}
|
||||
<circle cx="16" cy="16" r="1.5" fill="#fff" />
|
||||
{/* 송출중 표시 */}
|
||||
<circle cx="85" cy="28" r="5" fill={statusColor} opacity="0.8" />
|
||||
<circle cx="85" cy="28" r="2.5" fill="#fff" opacity="0.9" />
|
||||
{/* 송출중 LED */}
|
||||
{stream.status === 'streaming' && (
|
||||
<circle cx="16" cy="10" r="2" fill="#ef4444">
|
||||
<animate attributeName="opacity" values="1;0.3;1" dur="1.5s" repeatCount="indefinite" />
|
||||
<circle cx="95" cy="18" r="3" fill="#ef4444">
|
||||
<animate attributeName="opacity" values="1;0.2;1" dur="1.2s" repeatCount="indefinite" />
|
||||
</circle>
|
||||
)}
|
||||
{/* 드론 이름 */}
|
||||
<rect x="62" y="46" width="46" height="12" rx="3" fill="rgba(0,0,0,.7)" />
|
||||
<text x="85" y="55" textAnchor="middle" fill={statusColor} fontSize="7" fontFamily="sans-serif" fontWeight="bold">{stream.droneModel.split(' ').slice(-1)[0]}</text>
|
||||
</svg>
|
||||
{/* 라벨 */}
|
||||
<div className="px-1.5 py-px rounded text-[8px] font-bold font-korean whitespace-nowrap mt-0.5"
|
||||
style={{ background: 'rgba(0,0,0,.7)', color: '#fff' }}>
|
||||
{stream.shipName}
|
||||
</div>
|
||||
</div>
|
||||
</Marker>
|
||||
)
|
||||
})}
|
||||
{/* 드론 클릭 팝업 */}
|
||||
{mapPopup && DRONE_COORDS[mapPopup.id] && (
|
||||
{mapPopup && DRONE_POSITIONS[mapPopup.id] && (
|
||||
<Popup
|
||||
longitude={DRONE_COORDS[mapPopup.id].lon}
|
||||
latitude={DRONE_COORDS[mapPopup.id].lat}
|
||||
longitude={DRONE_POSITIONS[mapPopup.id].drone.lon}
|
||||
latitude={DRONE_POSITIONS[mapPopup.id].drone.lat}
|
||||
anchor="bottom"
|
||||
onClose={() => setMapPopup(null)}
|
||||
closeOnClick={false}
|
||||
|
||||
불러오는 중...
Reference in New Issue
Block a user