gc-wing/apps/web/src/features/legacyDashboard/dev/mockOverlayData.ts
htlee e72e2f14f6 feat(ship-image): 선박 이미지 썸네일 및 갤러리 기능
- AIS 타겟에 shipImagePath/shipImageCount 필드 추가
- 선박 이미지 API 연동 (fetchShipImagesByImo)
- 지도 위 사진 인디케이터 (ScatterplotLayer)
- 호버 툴팁에 썸네일 표시
- 정보 패널 카드 갤러리 (스크롤+화살표)
- 고화질 이미지 모달 (initialIndex 지원)
- Vite 프록시 /shipimg 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 03:45:25 +09:00

217 lines
9.2 KiB
TypeScript

/**
* 오버레이/경고 시각 검증용 더미 데이터 — 김개발(DEV) 모드 전용.
*
* 서해 해역에 12척의 가상 선박을 배치하여 다음 시나리오를 재현:
* - 정상 쌍끌이 (pair link, ~1 NM)
* - 이격 쌍끌이 (pair_separation alarm, ~8 NM)
* - 선단 원 (fleet circle, 5척 동일 선주)
* - 환적 의심 (transshipment alarm, FC ↔ PS < 0.5 NM)
* - AIS 지연 (ais_stale alarm, 2시간 전 타임스탬프)
* - 수역 이탈 (zone_violation alarm, PT가 zone 4에 위치)
* - closed_season은 계절(월)에 따라 자동 판정 (2월에는 미발생)
*
* 사용법:
* DashboardPage에서 isDevMode일 때 targetsInScope + legacyHits에 병합.
* 기존 computePairLinks / computeFcLinks / computeFleetCircles /
* computeLegacyAlarms 파이프라인이 자동으로 오버레이/경고를 생성.
*/
import type { AisTarget } from '../../../entities/aisTarget/model/types';
import type { LegacyVesselInfo } from '../../../entities/legacyVessel/model/types';
// ── 타임스탬프 ──────────────────────────────────────────────
const FRESH_TS = new Date().toISOString();
const STALE_TS = new Date(Date.now() - 120 * 60_000).toISOString(); // 2시간 전 → ais_stale
// ── 팩토리 ──────────────────────────────────────────────────
function makeAis(o: Partial<AisTarget> & Pick<AisTarget, 'mmsi' | 'lat' | 'lon'>): AisTarget {
return {
imo: 0,
name: '',
callsign: '',
vesselType: '',
heading: o.cog ?? 0,
sog: 0,
cog: 0,
rot: 0,
length: 30,
width: 8,
draught: 4,
destination: '',
eta: '',
status: 'underway',
messageTimestamp: FRESH_TS,
receivedDate: FRESH_TS,
source: 'mock',
classType: 'A',
...o,
};
}
function makeLegacy(
o: Partial<LegacyVesselInfo> & Pick<LegacyVesselInfo, 'permitNo' | 'shipCode' | 'mmsiList'>,
): LegacyVesselInfo {
return {
shipNameRoman: '',
shipNameCn: null,
ton: 100,
callSign: '',
workSeaArea: '서해',
workTerm1: '2025-01-01',
workTerm2: '2025-12-31',
quota: '',
ownerCn: null,
ownerRoman: null,
pairPermitNo: null,
pairShipNameCn: null,
checklistSheet: null,
sources: { permittedList: true, checklist: false, fleet906: false },
...o,
};
}
// ── 선박 정의 (12척) ────────────────────────────────────────
/*
* Group 1 — 정상 쌍끌이 (간격 ~1 NM, 경고 없음)
* 위치: 서해남부(zone 3) 125.3°E 34.0°N 부근
*/
const PT_01_AIS = makeAis({ mmsi: 990001, lat: 34.00, lon: 125.30, sog: 3.3, cog: 45, name: 'MOCK VESSEL 1', shipImagePath: 'https://picsum.photos/seed/ship990001v1/800/600', shipImageCount: 3 });
const PT_02_AIS = makeAis({ mmsi: 990002, lat: 34.01, lon: 125.32, sog: 3.3, cog: 45, name: 'MOCK VESSEL 2' });
const PT_01_LEG = makeLegacy({
permitNo: 'MOCK-P001', shipCode: 'PT', mmsiList: [990001],
shipNameRoman: 'MOCK VESSEL 1', shipNameCn: '模拟渔船一号',
pairPermitNo: 'MOCK-P002', pairShipNameCn: '模拟渔船二号',
ownerRoman: 'MOCK Owner A', ownerCn: '模拟A渔业',
});
const PT_02_LEG = makeLegacy({
permitNo: 'MOCK-P002', shipCode: 'PT-S', mmsiList: [990002],
shipNameRoman: 'MOCK VESSEL 2', shipNameCn: '模拟渔船二号',
pairPermitNo: 'MOCK-P001', pairShipNameCn: '模拟渔船一号',
ownerRoman: 'MOCK Owner A', ownerCn: '模拟A渔业',
});
/*
* Group 2 — 이격 쌍끌이 (간격 ~8 NM → pair_separation alarm)
* 위치: 서해남부(zone 3) 125.0°E 34.5°N 부근
*/
const PT_03_AIS = makeAis({ mmsi: 990003, lat: 34.50, lon: 125.00, sog: 3.5, cog: 90, name: 'MOCK VESSEL 3', shipImagePath: 'https://picsum.photos/seed/ship990003v1/800/600', shipImageCount: 2 });
const PT_04_AIS = makeAis({ mmsi: 990004, lat: 34.60, lon: 125.12, sog: 3.5, cog: 90, name: 'MOCK VESSEL 4' });
const PT_03_LEG = makeLegacy({
permitNo: 'MOCK-P003', shipCode: 'PT', mmsiList: [990003],
shipNameRoman: 'MOCK VESSEL 3', shipNameCn: '模拟渔船三号',
pairPermitNo: 'MOCK-P004', pairShipNameCn: '模拟渔船四号',
ownerRoman: 'MOCK Owner B', ownerCn: '模拟B渔业',
});
const PT_04_LEG = makeLegacy({
permitNo: 'MOCK-P004', shipCode: 'PT-S', mmsiList: [990004],
shipNameRoman: 'MOCK VESSEL 4', shipNameCn: '模拟渔船四号',
pairPermitNo: 'MOCK-P003', pairShipNameCn: '模拟渔船三号',
ownerRoman: 'MOCK Owner B', ownerCn: '模拟B渔业',
});
/*
* Group 3 — 선단 (동일 선주 5척 → fleet circle)
* 위치: 서해중간(zone 4) 124.8°E 35.2°N 부근
* #11(GN)은 AIS 지연 2시간 → ais_stale alarm 동시 발생
*/
const GN_01_AIS = makeAis({ mmsi: 990005, lat: 35.20, lon: 124.80, sog: 1.0, cog: 180, name: 'MOCK VESSEL 5', shipImagePath: 'https://picsum.photos/seed/ship990005v1/800/600', shipImageCount: 1 });
const GN_02_AIS = makeAis({ mmsi: 990006, lat: 35.22, lon: 124.85, sog: 1.2, cog: 170, name: 'MOCK VESSEL 6' });
const GN_03_AIS = makeAis({ mmsi: 990007, lat: 35.18, lon: 124.82, sog: 0.8, cog: 200, name: 'MOCK VESSEL 7' });
const OT_01_AIS = makeAis({ mmsi: 990008, lat: 35.25, lon: 124.78, sog: 3.5, cog: 160, name: 'MOCK VESSEL 8', shipImagePath: 'https://picsum.photos/seed/ship990008v1/800/600', shipImageCount: 4 });
const GN_04_AIS = makeAis({
mmsi: 990011, lat: 35.00, lon: 125.20, sog: 1.5, cog: 190, name: 'MOCK VESSEL 10',
messageTimestamp: STALE_TS, receivedDate: STALE_TS,
});
const GN_01_LEG = makeLegacy({
permitNo: 'MOCK-P005', shipCode: 'GN', mmsiList: [990005],
shipNameRoman: 'MOCK VESSEL 5', shipNameCn: '模拟渔船五号',
ownerRoman: 'MOCK Owner C', ownerCn: '模拟C渔业',
});
const GN_02_LEG = makeLegacy({
permitNo: 'MOCK-P006', shipCode: 'GN', mmsiList: [990006],
shipNameRoman: 'MOCK VESSEL 6', shipNameCn: '模拟渔船六号',
ownerRoman: 'MOCK Owner C', ownerCn: '模拟C渔业',
});
const GN_03_LEG = makeLegacy({
permitNo: 'MOCK-P007', shipCode: 'GN', mmsiList: [990007],
shipNameRoman: 'MOCK VESSEL 7', shipNameCn: '模拟渔船七号',
ownerRoman: 'MOCK Owner C', ownerCn: '模拟C渔业',
});
const OT_01_LEG = makeLegacy({
permitNo: 'MOCK-P008', shipCode: 'OT', mmsiList: [990008],
shipNameRoman: 'MOCK VESSEL 8', shipNameCn: '模拟渔船八号',
ownerRoman: 'MOCK Owner C', ownerCn: '模拟C渔业',
});
const GN_04_LEG = makeLegacy({
permitNo: 'MOCK-P011', shipCode: 'GN', mmsiList: [990011],
shipNameRoman: 'MOCK VESSEL 10', shipNameCn: '模拟渔船十号',
ownerRoman: 'MOCK Owner C', ownerCn: '模拟C渔业',
});
/*
* Group 4 — 환적 의심 (FC ↔ PS 거리 ~0.15 NM → transshipment alarm)
* 위치: 서해남부(zone 3) 125.5°E 34.3°N 부근
*/
const FC_01_AIS = makeAis({ mmsi: 990009, lat: 34.30, lon: 125.50, sog: 1.0, cog: 0, name: 'MOCK CARRIER 1', shipImagePath: 'https://picsum.photos/seed/ship990009v1/800/600', shipImageCount: 2 });
const PS_01_AIS = makeAis({ mmsi: 990010, lat: 34.302, lon: 125.502, sog: 0.5, cog: 10, name: 'MOCK VESSEL 9' });
const FC_01_LEG = makeLegacy({
permitNo: 'MOCK-P009', shipCode: 'FC', mmsiList: [990009],
shipNameRoman: 'MOCK CARRIER 1', shipNameCn: '模拟运船一号',
ownerRoman: 'MOCK Owner D', ownerCn: '模拟D渔业',
});
const PS_01_LEG = makeLegacy({
permitNo: 'MOCK-P010', shipCode: 'PS', mmsiList: [990010],
shipNameRoman: 'MOCK VESSEL 9', shipNameCn: '模拟渔船九号',
ownerRoman: 'MOCK Owner E', ownerCn: '模拟E渔业',
});
/*
* Group 5 — 수역 이탈 (PT가 zone 4 에 위치 → zone_violation alarm)
* PT는 zone 2,3만 허가. zone 4(서해중간)에 위치하면 이탈 판정.
* 위치: 서해중간(zone 4) 125.0°E 36.5°N 부근
*/
const PT_05_AIS = makeAis({ mmsi: 990012, lat: 36.50, lon: 125.00, sog: 3.3, cog: 270, name: 'MOCK VESSEL 11', shipImagePath: 'https://picsum.photos/seed/ship990012v1/800/600', shipImageCount: 1 });
const PT_05_LEG = makeLegacy({
permitNo: 'MOCK-P012', shipCode: 'PT', mmsiList: [990012],
shipNameRoman: 'MOCK VESSEL 11', shipNameCn: '模拟渔船十一号',
ownerRoman: 'MOCK Owner F', ownerCn: '模拟F渔业',
});
// ── 공개 API ────────────────────────────────────────────────
/** 더미 AIS 타겟 (12척) */
export const MOCK_AIS_TARGETS: AisTarget[] = [
PT_01_AIS, PT_02_AIS, // Group 1: 정상 쌍끌이
PT_03_AIS, PT_04_AIS, // Group 2: 이격 쌍끌이
GN_01_AIS, GN_02_AIS, GN_03_AIS, OT_01_AIS, GN_04_AIS, // Group 3: 선단 + AIS 지연
FC_01_AIS, PS_01_AIS, // Group 4: 환적 의심
PT_05_AIS, // Group 5: 수역 이탈
];
/** 더미 legacy 매칭 엔트리 (MMSI → LegacyVesselInfo) */
export const MOCK_LEGACY_ENTRIES: [number, LegacyVesselInfo][] = [
[990001, PT_01_LEG],
[990002, PT_02_LEG],
[990003, PT_03_LEG],
[990004, PT_04_LEG],
[990005, GN_01_LEG],
[990006, GN_02_LEG],
[990007, GN_03_LEG],
[990008, OT_01_LEG],
[990009, FC_01_LEG],
[990010, PS_01_LEG],
[990011, GN_04_LEG],
[990012, PT_05_LEG],
];
/** 더미 MMSI 집합 — 필터링/하이라이팅에 활용 */
export const MOCK_MMSI_SET = new Set(MOCK_AIS_TARGETS.map((t) => t.mmsi));