113 lines
3.7 KiB
JavaScript
113 lines
3.7 KiB
JavaScript
|
|
/**
|
||
|
|
* 항적분석 검색 결과 CSV 내보내기
|
||
|
|
* BOM + UTF-8 인코딩 (한글 엑셀 호환)
|
||
|
|
*/
|
||
|
|
import { getShipKindName, getSignalSourceName } from '../../tracking/types/trackQuery.types';
|
||
|
|
import { getCountryIsoCode } from '../../replay/components/VesselListManager/utils/countryCodeUtils';
|
||
|
|
|
||
|
|
function formatTimestamp(ms) {
|
||
|
|
if (!ms) return '';
|
||
|
|
const d = new Date(ms);
|
||
|
|
const pad = (n) => String(n).padStart(2, '0');
|
||
|
|
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
function formatPosition(pos) {
|
||
|
|
if (!pos || pos.length < 2) return '';
|
||
|
|
const lon = pos[0];
|
||
|
|
const lat = pos[1];
|
||
|
|
const latDir = lat >= 0 ? 'N' : 'S';
|
||
|
|
const lonDir = lon >= 0 ? 'E' : 'W';
|
||
|
|
return `${Math.abs(lat).toFixed(4)}\u00B0${latDir} ${Math.abs(lon).toFixed(4)}\u00B0${lonDir}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
function escapeCsvField(value) {
|
||
|
|
const str = String(value ?? '');
|
||
|
|
if (str.includes(',') || str.includes('"') || str.includes('\n')) {
|
||
|
|
return `"${str.replace(/"/g, '""')}"`;
|
||
|
|
}
|
||
|
|
return str;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 검색 결과를 CSV로 내보내기
|
||
|
|
*
|
||
|
|
* @param {Array} tracks ProcessedTrack 배열
|
||
|
|
* @param {Object} hitDetails { vesselId: [{ polygonId, entryTimestamp, exitTimestamp }] }
|
||
|
|
* @param {Array} zones 구역 배열
|
||
|
|
*/
|
||
|
|
export function exportSearchResultToCSV(tracks, hitDetails, zones) {
|
||
|
|
const zoneNames = zones.map((z) => z.name);
|
||
|
|
|
||
|
|
// 헤더 구성
|
||
|
|
const baseHeaders = [
|
||
|
|
'신호원', '식별번호', '선박명', '선종', '국적',
|
||
|
|
'포인트수', '이동거리(NM)', '평균속도(kn)', '최대속도(kn)',
|
||
|
|
];
|
||
|
|
|
||
|
|
const zoneHeaders = [];
|
||
|
|
zoneNames.forEach((name) => {
|
||
|
|
zoneHeaders.push(
|
||
|
|
`구역${name}_진입시각`, `구역${name}_진입위치`,
|
||
|
|
`구역${name}_진출시각`, `구역${name}_진출위치`,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
const headers = [...baseHeaders, ...zoneHeaders];
|
||
|
|
|
||
|
|
// 데이터 행 생성
|
||
|
|
const rows = tracks.map((track) => {
|
||
|
|
const baseRow = [
|
||
|
|
getSignalSourceName(track.sigSrcCd),
|
||
|
|
track.targetId || '',
|
||
|
|
track.shipName || '',
|
||
|
|
getShipKindName(track.shipKindCode),
|
||
|
|
track.nationalCode ? getCountryIsoCode(track.nationalCode) : '',
|
||
|
|
track.stats?.pointCount ?? track.geometry.length,
|
||
|
|
track.stats?.totalDistance != null ? track.stats.totalDistance.toFixed(2) : '',
|
||
|
|
track.stats?.avgSpeed != null ? track.stats.avgSpeed.toFixed(1) : '',
|
||
|
|
track.stats?.maxSpeed != null ? track.stats.maxSpeed.toFixed(1) : '',
|
||
|
|
];
|
||
|
|
|
||
|
|
const hits = hitDetails[track.vesselId] || [];
|
||
|
|
const zoneData = [];
|
||
|
|
zones.forEach((zone) => {
|
||
|
|
const hit = hits.find((h) => h.polygonId === zone.id);
|
||
|
|
if (hit) {
|
||
|
|
zoneData.push(
|
||
|
|
formatTimestamp(hit.entryTimestamp),
|
||
|
|
formatPosition(hit.entryPosition),
|
||
|
|
formatTimestamp(hit.exitTimestamp),
|
||
|
|
formatPosition(hit.exitPosition),
|
||
|
|
);
|
||
|
|
} else {
|
||
|
|
zoneData.push('', '', '', '');
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
return [...baseRow, ...zoneData];
|
||
|
|
});
|
||
|
|
|
||
|
|
// CSV 문자열 생성
|
||
|
|
const csvLines = [
|
||
|
|
headers.map(escapeCsvField).join(','),
|
||
|
|
...rows.map((row) => row.map(escapeCsvField).join(',')),
|
||
|
|
];
|
||
|
|
const csvContent = csvLines.join('\n');
|
||
|
|
|
||
|
|
// BOM + UTF-8 Blob 생성 및 다운로드
|
||
|
|
const BOM = '\uFEFF';
|
||
|
|
const blob = new Blob([BOM + csvContent], { 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 = `항적분석_${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}_${pad(now.getHours())}${pad(now.getMinutes())}.csv`;
|
||
|
|
|
||
|
|
const link = document.createElement('a');
|
||
|
|
link.href = url;
|
||
|
|
link.download = filename;
|
||
|
|
link.click();
|
||
|
|
URL.revokeObjectURL(url);
|
||
|
|
}
|