133 lines
4.0 KiB
JavaScript
133 lines
4.0 KiB
JavaScript
|
|
/**
|
||
|
|
* 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);
|
||
|
|
}
|