230 lines
6.0 KiB
TypeScript
230 lines
6.0 KiB
TypeScript
import { logger } from "../utils/logger";
|
|
import { query } from "../database/db";
|
|
import { ExternalDbConnectionService } from "./externalDbConnectionService";
|
|
|
|
interface MapDataQuery {
|
|
connectionId?: number;
|
|
tableName: string;
|
|
latColumn: string;
|
|
lngColumn: string;
|
|
labelColumn?: string;
|
|
statusColumn?: string;
|
|
additionalColumns?: string[];
|
|
whereClause?: string;
|
|
}
|
|
|
|
export interface MapMarker {
|
|
id: string | number;
|
|
latitude: number;
|
|
longitude: number;
|
|
label?: string;
|
|
status?: string;
|
|
additionalInfo?: Record<string, any>;
|
|
}
|
|
|
|
/**
|
|
* 지도 데이터 서비스
|
|
* 외부/내부 DB에서 위도/경도 데이터를 조회하여 지도 마커로 변환
|
|
*/
|
|
export class MapDataService {
|
|
constructor() {
|
|
// ExternalDbConnectionService는 static 메서드를 사용
|
|
}
|
|
|
|
/**
|
|
* 외부 DB에서 지도 데이터 조회
|
|
*/
|
|
async getMapData(params: MapDataQuery): Promise<MapMarker[]> {
|
|
try {
|
|
logger.info("🗺️ 외부 DB 지도 데이터 조회 시작:", params);
|
|
|
|
// SELECT할 컬럼 목록 구성
|
|
const selectColumns = [
|
|
params.latColumn,
|
|
params.lngColumn,
|
|
params.labelColumn,
|
|
params.statusColumn,
|
|
...(params.additionalColumns || []),
|
|
].filter(Boolean);
|
|
|
|
// 중복 제거
|
|
const uniqueColumns = Array.from(new Set(selectColumns));
|
|
|
|
// SQL 쿼리 구성
|
|
let sql = `SELECT ${uniqueColumns.map((col) => `"${col}"`).join(", ")} FROM "${params.tableName}"`;
|
|
|
|
if (params.whereClause) {
|
|
sql += ` WHERE ${params.whereClause}`;
|
|
}
|
|
|
|
logger.info("📝 실행할 SQL:", sql);
|
|
|
|
// 외부 DB 쿼리 실행 (static 메서드 사용)
|
|
const result = await ExternalDbConnectionService.executeQuery(
|
|
params.connectionId!,
|
|
sql
|
|
);
|
|
|
|
if (!result.success || !result.data) {
|
|
throw new Error("외부 DB 쿼리 실패");
|
|
}
|
|
|
|
// 데이터를 MapMarker 형식으로 변환
|
|
const markers = this.convertToMarkers(
|
|
result.data,
|
|
params.latColumn,
|
|
params.lngColumn,
|
|
params.labelColumn,
|
|
params.statusColumn,
|
|
params.additionalColumns
|
|
);
|
|
|
|
logger.info(`✅ ${markers.length}개의 마커 데이터 변환 완료`);
|
|
|
|
return markers;
|
|
} catch (error) {
|
|
logger.error("❌ 외부 DB 지도 데이터 조회 오류:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 내부 DB에서 지도 데이터 조회
|
|
*/
|
|
async getInternalMapData(
|
|
params: Omit<MapDataQuery, "connectionId">
|
|
): Promise<MapMarker[]> {
|
|
try {
|
|
logger.info("🗺️ 내부 DB 지도 데이터 조회 시작:", params);
|
|
|
|
// SELECT할 컬럼 목록 구성
|
|
const selectColumns = [
|
|
params.latColumn,
|
|
params.lngColumn,
|
|
params.labelColumn,
|
|
params.statusColumn,
|
|
...(params.additionalColumns || []),
|
|
].filter(Boolean);
|
|
|
|
// 중복 제거
|
|
const uniqueColumns = Array.from(new Set(selectColumns));
|
|
|
|
// SQL 쿼리 구성
|
|
let sql = `SELECT ${uniqueColumns.map((col) => `"${col}"`).join(", ")} FROM "${params.tableName}"`;
|
|
|
|
if (params.whereClause) {
|
|
sql += ` WHERE ${params.whereClause}`;
|
|
}
|
|
|
|
logger.info("📝 실행할 SQL:", sql);
|
|
|
|
// 내부 DB 쿼리 실행
|
|
const rows = await query(sql);
|
|
|
|
// 데이터를 MapMarker 형식으로 변환
|
|
const markers = this.convertToMarkers(
|
|
rows,
|
|
params.latColumn,
|
|
params.lngColumn,
|
|
params.labelColumn,
|
|
params.statusColumn,
|
|
params.additionalColumns
|
|
);
|
|
|
|
logger.info(`✅ ${markers.length}개의 마커 데이터 변환 완료`);
|
|
|
|
return markers;
|
|
} catch (error) {
|
|
logger.error("❌ 내부 DB 지도 데이터 조회 오류:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* DB 결과를 MapMarker 배열로 변환
|
|
*/
|
|
private convertToMarkers(
|
|
data: any[],
|
|
latColumn: string,
|
|
lngColumn: string,
|
|
labelColumn?: string,
|
|
statusColumn?: string,
|
|
additionalColumns?: string[]
|
|
): MapMarker[] {
|
|
const markers: MapMarker[] = [];
|
|
|
|
for (let i = 0; i < data.length; i++) {
|
|
const row = data[i];
|
|
|
|
// 위도/경도 추출 (다양한 컬럼명 지원)
|
|
const lat = this.extractCoordinate(row, latColumn);
|
|
const lng = this.extractCoordinate(row, lngColumn);
|
|
|
|
// 유효한 좌표인지 확인
|
|
if (lat === null || lng === null || isNaN(lat) || isNaN(lng)) {
|
|
logger.warn(`⚠️ 유효하지 않은 좌표 스킵: row ${i}`, { lat, lng });
|
|
continue;
|
|
}
|
|
|
|
// 위도 범위 체크 (-90 ~ 90)
|
|
if (lat < -90 || lat > 90) {
|
|
logger.warn(`⚠️ 위도 범위 초과: ${lat}`);
|
|
continue;
|
|
}
|
|
|
|
// 경도 범위 체크 (-180 ~ 180)
|
|
if (lng < -180 || lng > 180) {
|
|
logger.warn(`⚠️ 경도 범위 초과: ${lng}`);
|
|
continue;
|
|
}
|
|
|
|
// 추가 정보 수집
|
|
const additionalInfo: Record<string, any> = {};
|
|
if (additionalColumns) {
|
|
for (const col of additionalColumns) {
|
|
if (col && row[col] !== undefined) {
|
|
additionalInfo[col] = row[col];
|
|
}
|
|
}
|
|
}
|
|
|
|
// 마커 생성
|
|
markers.push({
|
|
id: row.id || row.ID || `marker-${i}`,
|
|
latitude: lat,
|
|
longitude: lng,
|
|
label: labelColumn ? row[labelColumn] : undefined,
|
|
status: statusColumn ? row[statusColumn] : undefined,
|
|
additionalInfo: Object.keys(additionalInfo).length > 0 ? additionalInfo : undefined,
|
|
});
|
|
}
|
|
|
|
return markers;
|
|
}
|
|
|
|
/**
|
|
* 다양한 형식의 좌표 추출
|
|
*/
|
|
private extractCoordinate(row: any, columnName: string): number | null {
|
|
const value = row[columnName];
|
|
|
|
if (value === null || value === undefined) {
|
|
return null;
|
|
}
|
|
|
|
// 이미 숫자인 경우
|
|
if (typeof value === "number") {
|
|
return value;
|
|
}
|
|
|
|
// 문자열인 경우 파싱
|
|
if (typeof value === "string") {
|
|
const parsed = parseFloat(value);
|
|
return isNaN(parsed) ? null : parsed;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|