feat(prediction): 원 분석 기능 — 중심점/반경 입력으로 원형 오염 면적 계산
- 원 분석 버튼 클릭 시 입력 폼 토글 (중심 위도, 경도, 반경 km) - 사고 지점 좌표를 기본값으로 자동 설정 - πr² 면적, 2πr 둘레 계산 결과 표시 - 결과: 오염 면적(km²), 원 둘레(km), 반경(km), 중심 좌표 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
부모
fb74df5c1f
커밋
6944a9e342
@ -18,6 +18,11 @@ export function RightPanel({ onOpenBacktrack, onOpenRecalc, onOpenReport, detail
|
|||||||
const [shipExpanded, setShipExpanded] = useState(false)
|
const [shipExpanded, setShipExpanded] = useState(false)
|
||||||
const [insuranceExpanded, setInsuranceExpanded] = useState(false)
|
const [insuranceExpanded, setInsuranceExpanded] = useState(false)
|
||||||
const [polygonResult, setPolygonResult] = useState<{ areaKm2: number; perimeterKm: number; particleCount: number; hullPoints: number } | null>(null)
|
const [polygonResult, setPolygonResult] = useState<{ areaKm2: number; perimeterKm: number; particleCount: number; hullPoints: number } | null>(null)
|
||||||
|
const [showCircleInput, setShowCircleInput] = useState(false)
|
||||||
|
const [circleCenterLat, setCircleCenterLat] = useState('')
|
||||||
|
const [circleCenterLon, setCircleCenterLon] = useState('')
|
||||||
|
const [circleRadiusKm, setCircleRadiusKm] = useState('')
|
||||||
|
const [circleResult, setCircleResult] = useState<{ areaKm2: number; circumferenceKm: number; centerLat: number; centerLon: number; radiusKm: number } | null>(null)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-[300px] min-w-[300px] bg-bg-1 border-l border-border flex flex-col">
|
<div className="w-[300px] min-w-[300px] bg-bg-1 border-l border-border flex flex-col">
|
||||||
@ -60,12 +65,115 @@ export function RightPanel({ onOpenBacktrack, onOpenRecalc, onOpenReport, detail
|
|||||||
📐 다각형 분석
|
📐 다각형 분석
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => alert('원 분석 기능은 향후 오픈 예정입니다.')}
|
onClick={() => {
|
||||||
|
setShowCircleInput(!showCircleInput)
|
||||||
|
// 사고 지점 좌표를 기본값으로 설정
|
||||||
|
if (!showCircleInput && detail?.spill) {
|
||||||
|
if (!circleCenterLat) setCircleCenterLat(String(detail.spill.lat ?? ''))
|
||||||
|
if (!circleCenterLon) setCircleCenterLon(String(detail.spill.lon ?? ''))
|
||||||
|
}
|
||||||
|
}}
|
||||||
className="flex-1 py-2.5 px-2 rounded text-[10px] font-bold font-korean border cursor-pointer transition-colors hover:brightness-110"
|
className="flex-1 py-2.5 px-2 rounded text-[10px] font-bold font-korean border cursor-pointer transition-colors hover:brightness-110"
|
||||||
style={{ background: 'rgba(6,182,212,0.1)', border: '1px solid rgba(6,182,212,0.3)', color: 'var(--cyan)' }}>
|
style={showCircleInput
|
||||||
|
? { background: 'rgba(6,182,212,0.2)', border: '1px solid var(--cyan)', color: 'var(--cyan)' }
|
||||||
|
: { background: 'rgba(6,182,212,0.1)', border: '1px solid rgba(6,182,212,0.3)', color: 'var(--cyan)' }
|
||||||
|
}>
|
||||||
⭕ 원 분석
|
⭕ 원 분석
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 원 분석 입력 폼 */}
|
||||||
|
{showCircleInput && (
|
||||||
|
<div className="flex flex-col gap-2 p-2.5 bg-bg-0 border border-[rgba(6,182,212,0.2)] rounded-md mb-2">
|
||||||
|
<div className="text-[9px] font-bold text-primary-cyan font-korean">⭕ 원 분석 — 중심점 · 반경 입력</div>
|
||||||
|
<div className="grid grid-cols-2 gap-1.5">
|
||||||
|
<div>
|
||||||
|
<label className="text-[8px] text-text-3 font-korean block mb-0.5">중심 위도 (°N)</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
step="0.0001"
|
||||||
|
value={circleCenterLat}
|
||||||
|
onChange={e => setCircleCenterLat(e.target.value)}
|
||||||
|
placeholder="예: 34.7312"
|
||||||
|
className="w-full px-2 py-1.5 bg-bg-3 border border-border rounded text-[10px] font-mono text-text-1 outline-none focus:border-[var(--cyan)] transition-colors"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-[8px] text-text-3 font-korean block mb-0.5">중심 경도 (°E)</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
step="0.0001"
|
||||||
|
value={circleCenterLon}
|
||||||
|
onChange={e => setCircleCenterLon(e.target.value)}
|
||||||
|
placeholder="예: 127.6845"
|
||||||
|
className="w-full px-2 py-1.5 bg-bg-3 border border-border rounded text-[10px] font-mono text-text-1 outline-none focus:border-[var(--cyan)] transition-colors"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-[8px] text-text-3 font-korean block mb-0.5">반경 (km)</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
step="0.1"
|
||||||
|
min="0.1"
|
||||||
|
value={circleRadiusKm}
|
||||||
|
onChange={e => setCircleRadiusKm(e.target.value)}
|
||||||
|
placeholder="예: 3.0"
|
||||||
|
className="w-full px-2 py-1.5 bg-bg-3 border border-border rounded text-[10px] font-mono text-text-1 outline-none focus:border-[var(--cyan)] transition-colors"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
const lat = parseFloat(circleCenterLat)
|
||||||
|
const lon = parseFloat(circleCenterLon)
|
||||||
|
const r = parseFloat(circleRadiusKm)
|
||||||
|
if (isNaN(lat) || isNaN(lon) || isNaN(r) || r <= 0) {
|
||||||
|
alert('중심 좌표와 반경을 올바르게 입력하세요.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const areaKm2 = Math.PI * r * r
|
||||||
|
const circumferenceKm = 2 * Math.PI * r
|
||||||
|
setCircleResult({ areaKm2, circumferenceKm, centerLat: lat, centerLon: lon, radiusKm: r })
|
||||||
|
}}
|
||||||
|
disabled={!circleCenterLat || !circleCenterLon || !circleRadiusKm}
|
||||||
|
className="w-full py-2 rounded text-[10px] font-bold font-korean cursor-pointer transition-colors"
|
||||||
|
style={{
|
||||||
|
background: (circleCenterLat && circleCenterLon && circleRadiusKm) ? 'rgba(6,182,212,0.15)' : 'var(--bg3)',
|
||||||
|
border: (circleCenterLat && circleCenterLon && circleRadiusKm) ? '1px solid var(--cyan)' : '1px solid var(--bd)',
|
||||||
|
color: (circleCenterLat && circleCenterLon && circleRadiusKm) ? 'var(--cyan)' : 'var(--t3)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
⭕ 원 면적 계산
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 원 분석 결과 */}
|
||||||
|
{circleResult && (
|
||||||
|
<div className="flex flex-col gap-1.5 mb-2">
|
||||||
|
<div className="text-[9px] font-bold text-primary-cyan font-korean mb-0.5">⭕ 원 분석 결과</div>
|
||||||
|
<div className="grid grid-cols-2 gap-1">
|
||||||
|
<div className="text-center py-2 px-1 bg-bg-0 border border-border rounded">
|
||||||
|
<div className="text-sm font-extrabold font-mono text-primary-cyan">{circleResult.areaKm2.toFixed(2)}</div>
|
||||||
|
<div className="text-[7px] text-text-3 font-korean">오염 면적 (km²)</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-center py-2 px-1 bg-bg-0 border border-border rounded">
|
||||||
|
<div className="text-sm font-extrabold font-mono text-status-orange">{circleResult.circumferenceKm.toFixed(1)}</div>
|
||||||
|
<div className="text-[7px] text-text-3 font-korean">원 둘레 (km)</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-center py-2 px-1 bg-bg-0 border border-border rounded">
|
||||||
|
<div className="text-sm font-extrabold font-mono text-text-1">{circleResult.radiusKm.toFixed(1)}</div>
|
||||||
|
<div className="text-[7px] text-text-3 font-korean">반경 (km)</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-center py-2 px-1 bg-bg-0 border border-border rounded">
|
||||||
|
<div className="text-[9px] font-bold font-mono text-text-2">{circleResult.centerLat.toFixed(4)}°N</div>
|
||||||
|
<div className="text-[9px] font-bold font-mono text-text-2">{circleResult.centerLon.toFixed(4)}°E</div>
|
||||||
|
<div className="text-[7px] text-text-3 font-korean">중심 좌표</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{polygonResult && (
|
{polygonResult && (
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<div className="text-[9px] font-bold text-purple-400 font-korean mb-0.5">📐 Convex Hull 다각형 분석 결과</div>
|
<div className="text-[9px] font-bold text-purple-400 font-korean mb-0.5">📐 Convex Hull 다각형 분석 결과</div>
|
||||||
|
|||||||
불러오는 중...
Reference in New Issue
Block a user