/** * CSV 다운로드 유틸리티 * 참조: mda-react-front/src/widgets/rightMenu/ui/RightMenu.tsx (512-579) */ import { Polygon } from 'ol/geom'; import { shipTypeMap } from '../assets/data/shiptype'; import { SHIP_KIND_LABELS, SIGNAL_SOURCE_LABELS } from '../types/constants'; // 해구도 데이터 캐시 (첫 호출 시만 로딩) let trenchCache = null; /** * 해구도 폴리곤 데이터 로딩 (동적 import, 캐시) */ async function loadTrenchData() { if (trenchCache) return trenchCache; const data = await import('../assets/data/largeTrench.json'); const geojson = data.default || data; trenchCache = geojson.features.map((f) => ({ zoneName: f.properties.zone_name, polygon: new Polygon(f.geometry.coordinates), })); return trenchCache; } /** * 선박 좌표 → 해구도 번호 일괄 조회 * @param {Array} ships - 선박 배열 (longitude, latitude 필드 필요) * @returns {Map} index → zone_name 매핑 */ async function lookupTrenchNumbers(ships) { const trenchData = await loadTrenchData(); const result = new Map(); ships.forEach((ship, idx) => { const lon = parseFloat(ship.longitude); const lat = parseFloat(ship.latitude); if (isNaN(lon) || isNaN(lat)) { result.set(idx, 'X'); return; } let found = false; for (const { zoneName, polygon } of trenchData) { if (polygon.intersectsCoordinate([lon, lat])) { result.set(idx, zoneName); found = true; break; } } if (!found) { result.set(idx, 'X'); } }); return result; } /** * 수신시간 포맷 변환 * "YYYYMMDDHHmmss" → "YYYY-MM-DD HH:mm:ss" */ function formatRecvDateTime(raw) { if (!raw || raw.length < 14) return raw || ''; return `${raw.slice(0, 4)}-${raw.slice(4, 6)}-${raw.slice(6, 8)} ${raw.slice(8, 10)}:${raw.slice(10, 12)}:${raw.slice(12, 14)}`; } /** * CSV 안전 필드 (쌍따옴표 감싸기) */ function csvField(val) { const str = val == null ? '' : String(val); return `"${str.replace(/"/g, '""')}"`; } /** * CSV 문자열 생성 * @param {Array} ships - 선박 배열 * @param {Map} trenchMap - index → zone_name 매핑 * @returns {string} CSV 문자열 (BOM 포함) */ function buildCsvString(ships, trenchMap) { const BOM = '\uFEFF'; const headers = [ '타겟ID', '선박명', '선종/기종', '선종/기종-유형', '신호', 'SOG', 'COG', '경도', '위도', '흘수', '수신시간', '해구도', ]; const rows = ships.map((ship, idx) => { const fields = [ csvField(ship.downloadTargetId), csvField(ship.shipName), csvField(SHIP_KIND_LABELS[ship.signalKindCode] || ship.signalKindCode), csvField(shipTypeMap.get(String(ship.shipType)) || ship.shipType), csvField(SIGNAL_SOURCE_LABELS[ship.signalSourceCode] || ship.signalSourceCode), csvField(ship.sog), csvField(ship.cog), csvField(ship.longitude), csvField(ship.latitude), csvField(ship.draught), csvField(formatRecvDateTime(ship.receivedTime)), csvField(trenchMap.get(idx) || 'X'), ]; return fields.join(','); }); return BOM + headers.map(csvField).join(',') + '\n' + rows.join('\n'); } /** * CSV 다운로드 트리거 * @param {Array} ships - getDownloadShips()에서 반환된 선박 배열 */ export async function downloadShipCsv(ships) { const trenchMap = await lookupTrenchNumbers(ships); const csvString = buildCsvString(ships, trenchMap); const blob = new Blob([csvString], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const now = new Date(); const pad = (n) => String(n).padStart(2, '0'); const fileName = `ship_download_${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}_${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}.csv`; const link = document.createElement('a'); link.href = url; link.download = fileName; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); }