feat(aerial): WingAI (AI 탐지/분석) 서브탭 추가

- MMSI 선종 불일치 탐지: AIS 등록 선종 vs AI 영상 분석 선종 비교, 지도 위 위치 표시
- 변화 감지: AS-IS/현재 시점 복합 정보원(위성/CCTV/드론/AIS) 오버레이 비교
- 연안자동감지: 지도 폴리곤 드로잉으로 감시 구역 등록, 주기/모니터링 방법 설정
- 위성요청 라벨 '위성영상'으로 변경, 서브탭 순서 재배치
- aerial:spectral 권한 트리 마이그레이션 추가 (022)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nan Kyung Lee 2026-03-17 12:07:47 +09:00
부모 7fb98ebb08
커밋 7110d76276
5개의 변경된 파일1177개의 추가작업 그리고 9개의 파일을 삭제

파일 보기

@ -0,0 +1,19 @@
-- aerial:spectral (AI 탐지/분석) 서브탭 권한 추가
-- 기존 aerial 서브탭(satellite) 뒤, cctv 앞에 배치 (SORT_ORD = 6)
-- 기존 cctv, theory 순서 밀기
UPDATE AUTH_PERM_TREE SET SORT_ORD = 7 WHERE RSRC_CD = 'aerial:cctv';
UPDATE AUTH_PERM_TREE SET SORT_ORD = 8 WHERE RSRC_CD = 'aerial:theory';
-- spectral 리소스 추가
INSERT INTO AUTH_PERM_TREE (RSRC_CD, PARENT_CD, RSRC_NM, RSRC_LEVEL, SORT_ORD)
VALUES ('aerial:spectral', 'aerial', 'AI 탐지/분석', 1, 6)
ON CONFLICT (RSRC_CD) DO NOTHING;
-- 기존 역할에 spectral READ 권한 부여 (aerial READ 권한이 있는 역할)
INSERT INTO AUTH_PERM (ROLE_SN, RSRC_CD, OPER_CD, USE_YN)
SELECT ap.ROLE_SN, 'aerial:spectral', ap.OPER_CD, ap.USE_YN
FROM AUTH_PERM ap
WHERE ap.RSRC_CD = 'aerial'
AND ap.USE_YN = 'Y'
ON CONFLICT (ROLE_SN, RSRC_CD, OPER_CD) DO NOTHING;

파일 보기

@ -40,9 +40,10 @@ const subMenuConfigs: Record<MainTab, SubMenuItem[] | null> = {
{ id: 'media', label: '영상사진관리', icon: '📷' },
{ id: 'analysis', label: '유출유면적분석', icon: '🧩' },
{ id: 'realtime', label: '실시간드론', icon: '🛸' },
{ id: 'sensor', label: '오염/선박3D분석', icon: '🔍' },
{ id: 'satellite', label: '위성요청', icon: '🛰' },
{ id: 'satellite', label: '위성영상', icon: '🛰' },
{ id: 'cctv', label: 'CCTV 조회', icon: '📹' },
{ id: 'spectral', label: 'AI 탐지/분석', icon: '🤖' },
{ id: 'sensor', label: '오염/선박3D분석', icon: '🔍' },
{ id: 'theory', label: '항공탐색 이론', icon: '📐' }
],
assets: null,

파일 보기

@ -5,6 +5,7 @@ import { OilAreaAnalysis } from './OilAreaAnalysis'
import { RealtimeDrone } from './RealtimeDrone'
import { SensorAnalysis } from './SensorAnalysis'
import { SatelliteRequest } from './SatelliteRequest'
import { WingAI } from './WingAI'
import { CctvView } from './CctvView'
export function AerialView() {
@ -16,6 +17,8 @@ export function AerialView() {
return <AerialTheoryView />
case 'satellite':
return <SatelliteRequest />
case 'spectral':
return <WingAI />
case 'cctv':
return <CctvView />
case 'analysis':

파일 보기

@ -115,6 +115,9 @@ export function SatelliteRequest() {
const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
})
const [mapSelectedItem, setMapSelectedItem] = useState<SatRequest | null>(null)
const satImgOpacity = 90
const satImgBrightness = 100
const satShowOverlay = true
const modalRef = useRef<HTMLDivElement>(null)
@ -436,26 +439,31 @@ export function SatelliteRequest() {
)
})}
{/* 선택된 완료 항목: VWorld 위성 영상 오버레이 */}
{mapSelectedItem && mapSelectedItem.status === '완료' && VWORLD_API_KEY && (
{/* 선택된 완료 항목: VWorld 위성 영상 오버레이 (BlackSky 스타일) */}
{mapSelectedItem && mapSelectedItem.status === '완료' && satShowOverlay && VWORLD_API_KEY && (
<Source
id="sat-vworld-overlay"
type="raster"
tiles={[`https://api.vworld.kr/req/wmts/1.0.0/${VWORLD_API_KEY}/Satellite/{z}/{y}/{x}.jpeg`]}
tileSize={256}
>
<Layer id="sat-vworld-layer" type="raster" paint={{ 'raster-opacity': 0.9 }} />
<Layer id="sat-vworld-layer" type="raster" paint={{
'raster-opacity': satImgOpacity / 100,
'raster-brightness-max': Math.min(satImgBrightness / 100 * 1.2, 1),
'raster-brightness-min': Math.max((satImgBrightness - 100) / 200, 0),
}} />
</Source>
)}
{/* 선택된 항목 마커 + 팝업 */}
{/* 선택된 항목 마커 */}
{mapSelectedItem && (() => {
const coord = parseCoord(mapSelectedItem.zoneCoord)
if (!coord) return null
return (
<Marker longitude={coord.lon} latitude={coord.lat} anchor="bottom">
<div className="flex flex-col items-center">
<div className="w-5 h-5 rounded-full flex items-center justify-center text-[10px]" style={{ background: 'rgba(6,182,212,.8)', border: '2px solid #fff', boxShadow: '0 0 10px rgba(6,182,212,.5)' }}>📷</div>
<Marker longitude={coord.lon} latitude={coord.lat} anchor="center">
<div className="relative">
<div className="w-4 h-4 rounded-full" style={{ background: 'rgba(6,182,212,.6)', border: '2px solid #fff', boxShadow: '0 0 12px rgba(6,182,212,.5)' }} />
<div className="absolute inset-0 w-4 h-4 rounded-full animate-ping" style={{ background: 'rgba(6,182,212,.3)' }} />
</div>
</Marker>
)

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다. Load Diff