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:
부모
7fb98ebb08
커밋
7110d76276
19
database/migration/022_aerial_spectral_perm.sql
Normal file
19
database/migration/022_aerial_spectral_perm.sql
Normal file
@ -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>
|
||||
)
|
||||
|
||||
1137
frontend/src/tabs/aerial/components/WingAI.tsx
Normal file
1137
frontend/src/tabs/aerial/components/WingAI.tsx
Normal file
파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
Load Diff
불러오는 중...
Reference in New Issue
Block a user