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; } /** * 지도 데이터 서비스 * 외부/내부 DB에서 위도/경도 데이터를 조회하여 지도 마커로 변환 */ export class MapDataService { constructor() { // ExternalDbConnectionService는 static 메서드를 사용 } /** * 외부 DB에서 지도 데이터 조회 */ async getMapData(params: MapDataQuery): Promise { 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 ): Promise { 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 = {}; 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; } }