- ShipFilterPanel: 선종별 표시/숨김 토글 (전체 ON/OFF 포함) - Sidebar gnb1에 ShipFilterPanel 연결 - aisTargetApi: 백엔드 signalKindCode 필드 우선, 없으면 vesselType fallback Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
138 lines
3.7 KiB
JavaScript
138 lines
3.7 KiB
JavaScript
/**
|
|
* AIS Target API 클라이언트
|
|
* SNP-Batch 서버의 AIS 데이터를 HTTP 폴링으로 조회
|
|
*/
|
|
import axios from 'axios';
|
|
|
|
// dev: Vite 프록시 (/snp-api → 211.208.115.83:8041)
|
|
// prod: 환경변수로 직접 지정
|
|
const BASE_URL = import.meta.env.VITE_API_URL || '/snp-api';
|
|
|
|
/**
|
|
* AIS 타겟 검색 (최근 N분 데이터)
|
|
* @param {number} minutes - 조회 기간 (분)
|
|
* @returns {Promise<Array>} AIS 타겟 데이터 배열
|
|
*/
|
|
export async function searchAisTargets(minutes = 60) {
|
|
const res = await axios.get(`${BASE_URL}/api/ais-target/search`, {
|
|
params: { minutes },
|
|
timeout: 30000,
|
|
});
|
|
return res.data.data || [];
|
|
}
|
|
|
|
/**
|
|
* AIS API 응답 → shipStore feature 객체로 변환
|
|
*
|
|
* API 응답 필드:
|
|
* mmsi, imo, name, callsign, vesselType, lat, lon,
|
|
* heading, sog, cog, rot, length, width, draught,
|
|
* destination, eta, status, messageTimestamp, receivedDate,
|
|
* source, classType
|
|
*
|
|
* @param {Object} aisTarget - API 응답 단건
|
|
* @returns {Object} shipStore 호환 feature 객체
|
|
*/
|
|
export function aisTargetToFeature(aisTarget) {
|
|
const mmsi = String(aisTarget.mmsi || '');
|
|
// 백엔드에서 signalKindCode를 직접 제공, 없으면 vesselType 기반 fallback
|
|
const signalKindCode = aisTarget.signalKindCode || mapVesselTypeToKindCode(aisTarget.vesselType);
|
|
|
|
return {
|
|
// 고유 식별자 (AIS 신호원 코드 + MMSI)
|
|
featureId: `000001${mmsi}`,
|
|
|
|
// 기본 식별 정보
|
|
targetId: mmsi,
|
|
originalTargetId: mmsi,
|
|
signalSourceCode: '000001', // AIS
|
|
shipName: aisTarget.name || '',
|
|
shipType: aisTarget.vesselType || '',
|
|
|
|
// 위치 정보
|
|
longitude: aisTarget.lon || 0,
|
|
latitude: aisTarget.lat || 0,
|
|
|
|
// 항해 정보
|
|
sog: aisTarget.sog || 0,
|
|
cog: aisTarget.cog || 0,
|
|
|
|
// 시간 정보
|
|
receivedTime: formatTimestamp(aisTarget.messageTimestamp),
|
|
|
|
// 선종 코드
|
|
signalKindCode,
|
|
|
|
// 상태 플래그
|
|
lost: false,
|
|
integrate: false,
|
|
isPriority: true,
|
|
|
|
// 위험물 카테고리
|
|
hazardousCategory: '',
|
|
|
|
// 국적 코드
|
|
nationalCode: '',
|
|
|
|
// IMO 번호
|
|
imo: String(aisTarget.imo || ''),
|
|
|
|
// 흘수
|
|
draught: String(aisTarget.draught || ''),
|
|
|
|
// 선박 크기
|
|
dimA: '',
|
|
dimB: '',
|
|
dimC: '',
|
|
dimD: '',
|
|
|
|
// AVETDR 신호장비 플래그
|
|
ais: '1',
|
|
vpass: '',
|
|
enav: '',
|
|
vtsAis: '',
|
|
dMfHf: '',
|
|
vtsRadar: '',
|
|
|
|
// 추가 메타데이터
|
|
callsign: aisTarget.callsign || '',
|
|
heading: aisTarget.heading || 0,
|
|
destination: aisTarget.destination || '',
|
|
status: aisTarget.status || '',
|
|
length: aisTarget.length || 0,
|
|
width: aisTarget.width || 0,
|
|
|
|
_raw: null,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* vesselType 문자열 → 선종 코드 매핑
|
|
*/
|
|
function mapVesselTypeToKindCode(vesselType) {
|
|
if (!vesselType) return '000027'; // 일반
|
|
|
|
const vt = vesselType.toLowerCase();
|
|
if (vt.includes('fishing')) return '000020'; // 어선
|
|
if (vt.includes('passenger')) return '000022'; // 여객선
|
|
if (vt.includes('cargo')) return '000023'; // 화물선
|
|
if (vt.includes('tanker')) return '000024'; // 유조선
|
|
if (vt.includes('military') || vt.includes('law enforcement')) return '000025'; // 관공선
|
|
if (vt.includes('tug') || vt.includes('pilot') || vt.includes('search')) return '000025'; // 관공선
|
|
return '000027'; // 일반
|
|
}
|
|
|
|
/**
|
|
* ISO 타임스탬프 → "YYYYMMDDHHmmss" 형식 변환
|
|
*/
|
|
function formatTimestamp(isoString) {
|
|
if (!isoString) return '';
|
|
try {
|
|
const d = new Date(isoString);
|
|
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())}`;
|
|
} catch {
|
|
return '';
|
|
}
|
|
}
|