ship-gis/src/utils/csvDownload.js

133 lines
4.0 KiB
JavaScript
Raw Normal View 히스토리

/**
* 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<number, string>} 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);
}