지도 위젯 팝업 수정 및 폴리곤 매핑 추가
This commit is contained in:
parent
ff23aa7d1d
commit
3e9c566834
|
|
@ -26,8 +26,6 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M
|
|||
const [sampleData, setSampleData] = useState<any[]>([]); // 샘플 데이터 (최대 3개)
|
||||
const [columnSearchTerm, setColumnSearchTerm] = useState(""); // 컬럼 검색어
|
||||
|
||||
console.log("🔧 MultiApiConfig - dataSource:", dataSource);
|
||||
|
||||
// 외부 API 커넥션 목록 로드
|
||||
useEffect(() => {
|
||||
const loadApiConnections = async () => {
|
||||
|
|
@ -51,14 +49,12 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M
|
|||
return;
|
||||
}
|
||||
|
||||
console.log("불러온 커넥션:", connection);
|
||||
|
||||
// base_url과 endpoint_path를 조합하여 전체 URL 생성
|
||||
const fullEndpoint = connection.endpoint_path
|
||||
? `${connection.base_url}${connection.endpoint_path}`
|
||||
: connection.base_url;
|
||||
|
||||
console.log("전체 엔드포인트:", fullEndpoint);
|
||||
|
||||
const updates: Partial<ChartDataSource> = {
|
||||
endpoint: fullEndpoint,
|
||||
|
|
@ -76,7 +72,6 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M
|
|||
value,
|
||||
});
|
||||
});
|
||||
console.log("기본 헤더 적용:", headers);
|
||||
}
|
||||
|
||||
// 인증 설정이 있으면 헤더 또는 쿼리 파라미터에 추가
|
||||
|
|
@ -91,7 +86,6 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M
|
|||
key: authConfig.keyName,
|
||||
value: authConfig.keyValue,
|
||||
});
|
||||
console.log("API Key 헤더 추가:", authConfig.keyName);
|
||||
} else if (authConfig.keyLocation === "query" && authConfig.keyName && authConfig.keyValue) {
|
||||
// UTIC API는 'key'를 사용하므로, 'apiKey'를 'key'로 변환
|
||||
const actualKeyName = authConfig.keyName === "apiKey" ? "key" : authConfig.keyName;
|
||||
|
|
@ -100,7 +94,6 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M
|
|||
key: actualKeyName,
|
||||
value: authConfig.keyValue,
|
||||
});
|
||||
console.log("API Key 쿼리 파라미터 추가:", actualKeyName, "(원본:", authConfig.keyName, ")");
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
@ -111,7 +104,6 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M
|
|||
key: "Authorization",
|
||||
value: `Bearer ${authConfig.token}`,
|
||||
});
|
||||
console.log("Bearer Token 헤더 추가");
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
@ -123,7 +115,6 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M
|
|||
key: "Authorization",
|
||||
value: `Basic ${credentials}`,
|
||||
});
|
||||
console.log("Basic Auth 헤더 추가");
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
@ -134,7 +125,6 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M
|
|||
key: "Authorization",
|
||||
value: `Bearer ${authConfig.accessToken}`,
|
||||
});
|
||||
console.log("OAuth2 Token 헤더 추가");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -148,7 +138,6 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M
|
|||
updates.queryParams = queryParams;
|
||||
}
|
||||
|
||||
console.log("최종 업데이트:", updates);
|
||||
onChange(updates);
|
||||
};
|
||||
|
||||
|
|
@ -235,12 +224,12 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M
|
|||
|
||||
const result = await response.json();
|
||||
|
||||
console.log("🌐 [API 테스트 결과]", result.data);
|
||||
|
||||
if (result.success) {
|
||||
// 텍스트 데이터 파싱 함수 (MapTestWidgetV2와 동일)
|
||||
const parseTextData = (text: string): any[] => {
|
||||
try {
|
||||
console.log("🔍 텍스트 파싱 시작 (처음 500자):", text.substring(0, 500));
|
||||
|
||||
const lines = text.split('\n').filter(line => {
|
||||
const trimmed = line.trim();
|
||||
return trimmed &&
|
||||
|
|
@ -249,8 +238,6 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M
|
|||
!trimmed.startsWith('---');
|
||||
});
|
||||
|
||||
console.log(`📝 유효한 라인: ${lines.length}개`);
|
||||
|
||||
if (lines.length === 0) return [];
|
||||
|
||||
const result: any[] = [];
|
||||
|
|
@ -278,7 +265,6 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M
|
|||
}
|
||||
}
|
||||
|
||||
console.log("📊 파싱 결과:", result.length, "개");
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("❌ 텍스트 파싱 오류:", error);
|
||||
|
|
@ -291,10 +277,8 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M
|
|||
|
||||
// 텍스트 데이터 체크 (기상청 API 등)
|
||||
if (data && typeof data === 'object' && data.text && typeof data.text === 'string') {
|
||||
console.log("📄 텍스트 형식 데이터 감지, CSV 파싱 시도");
|
||||
const parsedData = parseTextData(data.text);
|
||||
if (parsedData.length > 0) {
|
||||
console.log(`✅ CSV 파싱 성공: ${parsedData.length}개 행`);
|
||||
data = parsedData;
|
||||
}
|
||||
} else if (dataSource.jsonPath) {
|
||||
|
|
@ -306,6 +290,8 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M
|
|||
|
||||
const rows = Array.isArray(data) ? data : [data];
|
||||
|
||||
console.log("📊 [최종 파싱된 데이터]", rows);
|
||||
|
||||
// 컬럼 목록 및 타입 추출
|
||||
if (rows.length > 0) {
|
||||
const columns = Object.keys(rows[0]);
|
||||
|
|
@ -336,9 +322,6 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M
|
|||
|
||||
// 샘플 데이터 저장 (최대 3개)
|
||||
setSampleData(rows.slice(0, 3));
|
||||
|
||||
console.log("📊 발견된 컬럼:", columns);
|
||||
console.log("📊 컬럼 타입:", types);
|
||||
}
|
||||
|
||||
// 위도/경도 또는 coordinates 필드 또는 지역 코드 체크
|
||||
|
|
@ -422,7 +405,6 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M
|
|||
id={`endpoint-${dataSource.id}`}
|
||||
value={dataSource.endpoint || ""}
|
||||
onChange={(e) => {
|
||||
console.log("📝 API URL 변경:", e.target.value);
|
||||
onChange({ endpoint: e.target.value });
|
||||
}}
|
||||
placeholder="https://api.example.com/data"
|
||||
|
|
|
|||
|
|
@ -96,11 +96,8 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
// 다중 데이터 소스 로딩
|
||||
const loadMultipleDataSources = useCallback(async () => {
|
||||
if (!dataSources || dataSources.length === 0) {
|
||||
// // console.log("⚠️ 데이터 소스가 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
// // console.log(`🔄 ${dataSources.length}개의 데이터 소스 로딩 시작...`);
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
|
|
@ -109,8 +106,6 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
const results = await Promise.allSettled(
|
||||
dataSources.map(async (source) => {
|
||||
try {
|
||||
// // console.log(`📡 데이터 소스 "${source.name || source.id}" 로딩 중...`);
|
||||
|
||||
if (source.type === "api") {
|
||||
return await loadRestApiData(source);
|
||||
} else if (source.type === "database") {
|
||||
|
|
@ -130,21 +125,16 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
const allPolygons: PolygonData[] = [];
|
||||
|
||||
results.forEach((result, index) => {
|
||||
// // console.log(`🔍 결과 ${index}:`, result);
|
||||
|
||||
if (result.status === "fulfilled" && result.value) {
|
||||
const value = result.value as { markers: MarkerData[]; polygons: PolygonData[] };
|
||||
// // console.log(`✅ 데이터 소스 ${index} 성공:`, value);
|
||||
|
||||
// 마커 병합
|
||||
if (value.markers && Array.isArray(value.markers)) {
|
||||
// // console.log(` → 마커 ${value.markers.length}개 추가`);
|
||||
allMarkers.push(...value.markers);
|
||||
}
|
||||
|
||||
// 폴리곤 병합
|
||||
if (value.polygons && Array.isArray(value.polygons)) {
|
||||
// // console.log(` → 폴리곤 ${value.polygons.length}개 추가`);
|
||||
allPolygons.push(...value.polygons);
|
||||
}
|
||||
} else if (result.status === "rejected") {
|
||||
|
|
@ -152,10 +142,6 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
}
|
||||
});
|
||||
|
||||
// // console.log(`✅ 총 ${allMarkers.length}개의 마커, ${allPolygons.length}개의 폴리곤 로딩 완료`);
|
||||
// // console.log("📍 최종 마커 데이터:", allMarkers);
|
||||
// // console.log("🔷 최종 폴리곤 데이터:", allPolygons);
|
||||
|
||||
// 이전 마커 위치와 비교하여 진행 방향 계산
|
||||
const markersWithHeading = allMarkers.map((marker) => {
|
||||
const prevMarker = prevMarkers.find((pm) => pm.id === marker.id);
|
||||
|
|
@ -192,7 +178,6 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
|
||||
// 수동 새로고침 핸들러
|
||||
const handleManualRefresh = useCallback(() => {
|
||||
// // console.log("🔄 수동 새로고침 버튼 클릭");
|
||||
loadMultipleDataSources();
|
||||
}, [loadMultipleDataSources]);
|
||||
|
||||
|
|
@ -200,8 +185,6 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
const loadRestApiData = async (
|
||||
source: ChartDataSource,
|
||||
): Promise<{ markers: MarkerData[]; polygons: PolygonData[] }> => {
|
||||
// // console.log(`🌐 REST API 데이터 로딩 시작:`, source.name, `mapDisplayType:`, source.mapDisplayType);
|
||||
|
||||
if (!source.endpoint) {
|
||||
throw new Error("API endpoint가 없습니다.");
|
||||
}
|
||||
|
|
@ -256,13 +239,12 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
|
||||
// 텍스트 형식 데이터 체크 (기상청 API 등)
|
||||
if (data && typeof data === "object" && data.text && typeof data.text === "string") {
|
||||
// // console.log("📄 텍스트 형식 데이터 감지, CSV 파싱 시도");
|
||||
const parsedData = parseTextData(data.text);
|
||||
if (parsedData.length > 0) {
|
||||
// // console.log(`✅ CSV 파싱 성공: ${parsedData.length}개 행`);
|
||||
// 컬럼 매핑 적용
|
||||
const mappedData = applyColumnMapping(parsedData, source.columnMapping);
|
||||
return convertToMapData(mappedData, source.name || source.id || "API", source.mapDisplayType, source);
|
||||
const result = convertToMapData(mappedData, source.name || source.id || "API", source.mapDisplayType, source);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -280,15 +262,14 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
const mappedRows = applyColumnMapping(rows, source.columnMapping);
|
||||
|
||||
// 마커와 폴리곤으로 변환 (mapDisplayType + dataSource 전달)
|
||||
return convertToMapData(mappedRows, source.name || source.id || "API", source.mapDisplayType, source);
|
||||
const finalResult = convertToMapData(mappedRows, source.name || source.id || "API", source.mapDisplayType, source);
|
||||
return finalResult;
|
||||
};
|
||||
|
||||
// Database 데이터 로딩
|
||||
const loadDatabaseData = async (
|
||||
source: ChartDataSource,
|
||||
): Promise<{ markers: MarkerData[]; polygons: PolygonData[] }> => {
|
||||
// // console.log(`💾 Database 데이터 로딩 시작:`, source.name, `mapDisplayType:`, source.mapDisplayType);
|
||||
|
||||
if (!source.query) {
|
||||
throw new Error("SQL 쿼리가 없습니다.");
|
||||
}
|
||||
|
|
@ -330,7 +311,6 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
// XML 데이터 파싱 (UTIC API 등)
|
||||
const parseXmlData = (xmlText: string): any[] => {
|
||||
try {
|
||||
// // console.log(" 📄 XML 파싱 시작");
|
||||
const parser = new DOMParser();
|
||||
const xmlDoc = parser.parseFromString(xmlText, "text/xml");
|
||||
|
||||
|
|
@ -349,7 +329,6 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
results.push(obj);
|
||||
}
|
||||
|
||||
// // console.log(` ✅ XML 파싱 완료: ${results.length}개 레코드`);
|
||||
return results;
|
||||
} catch (error) {
|
||||
console.error(" ❌ XML 파싱 실패:", error);
|
||||
|
|
@ -360,11 +339,8 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
// 텍스트 데이터 파싱 (CSV, 기상청 형식 등)
|
||||
const parseTextData = (text: string): any[] => {
|
||||
try {
|
||||
// // console.log(" 🔍 원본 텍스트 (처음 500자):", text.substring(0, 500));
|
||||
|
||||
// XML 형식 감지
|
||||
if (text.trim().startsWith("<?xml") || text.trim().startsWith("<result>")) {
|
||||
// // console.log(" 📄 XML 형식 데이터 감지");
|
||||
return parseXmlData(text);
|
||||
}
|
||||
|
||||
|
|
@ -373,8 +349,6 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
return trimmed && !trimmed.startsWith("#") && !trimmed.startsWith("=") && !trimmed.startsWith("---");
|
||||
});
|
||||
|
||||
// // console.log(` 📝 유효한 라인: ${lines.length}개`);
|
||||
|
||||
if (lines.length === 0) return [];
|
||||
|
||||
// CSV 형식으로 파싱
|
||||
|
|
@ -384,8 +358,6 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
const line = lines[i];
|
||||
const values = line.split(",").map((v) => v.trim().replace(/,=$/g, ""));
|
||||
|
||||
// // console.log(` 라인 ${i}:`, values);
|
||||
|
||||
// 기상특보 형식: 지역코드, 지역명, 하위코드, 하위지역명, 발표시각, 특보종류, 등급, 발표상태, 설명
|
||||
if (values.length >= 4) {
|
||||
const obj: any = {
|
||||
|
|
@ -408,7 +380,6 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
}
|
||||
}
|
||||
|
||||
// // console.log(" 📊 최종 파싱 결과:", result.length, "개");
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error(" ❌ 텍스트 파싱 오류:", error);
|
||||
|
|
@ -423,23 +394,15 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
mapDisplayType?: "auto" | "marker" | "polygon",
|
||||
dataSource?: ChartDataSource,
|
||||
): { markers: MarkerData[]; polygons: PolygonData[] } => {
|
||||
// // console.log(`🔄 ${sourceName} 데이터 변환 시작:`, rows.length, "개 행");
|
||||
// // console.log(` 📌 mapDisplayType:`, mapDisplayType, `(타입: ${typeof mapDisplayType})`);
|
||||
// // console.log(` 🎨 마커 색상:`, dataSource?.markerColor, `폴리곤 색상:`, dataSource?.polygonColor);
|
||||
|
||||
if (rows.length === 0) return { markers: [], polygons: [] };
|
||||
|
||||
const markers: MarkerData[] = [];
|
||||
const polygons: PolygonData[] = [];
|
||||
|
||||
rows.forEach((row, index) => {
|
||||
// // console.log(` 행 ${index}:`, row);
|
||||
|
||||
// 텍스트 데이터 체크 (기상청 API 등)
|
||||
if (row && typeof row === "object" && row.text && typeof row.text === "string") {
|
||||
// // console.log(" 📄 텍스트 형식 데이터 감지, CSV 파싱 시도");
|
||||
const parsedData = parseTextData(row.text);
|
||||
// // console.log(` ✅ CSV 파싱 결과: ${parsedData.length}개 행`);
|
||||
|
||||
// 파싱된 데이터를 재귀적으로 변환 (색상 정보 전달)
|
||||
const result = convertToMapData(parsedData, sourceName, mapDisplayType, dataSource);
|
||||
|
|
@ -450,7 +413,6 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
|
||||
// 폴리곤 데이터 체크 (coordinates 필드가 배열인 경우 또는 강제 polygon 모드)
|
||||
if (row.coordinates && Array.isArray(row.coordinates) && row.coordinates.length > 0) {
|
||||
// // console.log(` → coordinates 발견:`, row.coordinates.length, "개");
|
||||
// coordinates가 [lat, lng] 배열의 배열인지 확인
|
||||
const firstCoord = row.coordinates[0];
|
||||
if (Array.isArray(firstCoord) && firstCoord.length === 2) {
|
||||
|
|
@ -460,7 +422,7 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
name: row.name || row.title || `영역 ${index + 1}`,
|
||||
coordinates: row.coordinates as [number, number][],
|
||||
status: row.status || row.level,
|
||||
description: row.description || JSON.stringify(row, null, 2),
|
||||
description: JSON.stringify(row, null, 2), // 항상 전체 row 객체를 JSON으로 저장
|
||||
source: sourceName,
|
||||
color: dataSource?.polygonColor || getColorByStatus(row.status || row.level),
|
||||
});
|
||||
|
|
@ -471,13 +433,12 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
// 지역명으로 해상 구역 확인 (auto 또는 polygon 모드일 때만)
|
||||
const regionName = row.name || row.area || row.region || row.location || row.subRegion;
|
||||
if (regionName && MARITIME_ZONES[regionName] && mapDisplayType !== "marker") {
|
||||
// // console.log(` → 해상 구역 발견: ${regionName}, 폴리곤으로 처리`);
|
||||
polygons.push({
|
||||
id: `${sourceName}-polygon-${index}-${row.code || row.id || Date.now()}`, // 고유 ID 생성
|
||||
name: regionName,
|
||||
coordinates: MARITIME_ZONES[regionName] as [number, number][],
|
||||
status: row.status || row.level,
|
||||
description: row.description || `${row.type || ""} ${row.level || ""}`.trim() || JSON.stringify(row, null, 2),
|
||||
description: JSON.stringify(row, null, 2), // 항상 전체 row 객체를 JSON으로 저장
|
||||
source: sourceName,
|
||||
color: dataSource?.polygonColor || getColorByStatus(row.status || row.level),
|
||||
});
|
||||
|
|
@ -494,7 +455,6 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
(row.code || row.areaCode || row.regionCode || row.tmFc || row.stnId)
|
||||
) {
|
||||
const regionCode = row.code || row.areaCode || row.regionCode || row.tmFc || row.stnId;
|
||||
// // console.log(` → 지역 코드 발견: ${regionCode}, 위도/경도 변환 시도`);
|
||||
const coords = getCoordinatesByRegionCode(regionCode);
|
||||
if (coords) {
|
||||
lat = coords.lat;
|
||||
|
|
@ -506,7 +466,6 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
// 지역명으로도 시도
|
||||
if ((lat === undefined || lng === undefined) && (row.name || row.area || row.region || row.location)) {
|
||||
const regionName = row.name || row.area || row.region || row.location;
|
||||
// // console.log(` → 지역명 발견: ${regionName}, 위도/경도 변환 시도`);
|
||||
const coords = getCoordinatesByRegionName(regionName);
|
||||
if (coords) {
|
||||
lat = coords.lat;
|
||||
|
|
@ -525,7 +484,7 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
name: regionName,
|
||||
coordinates: [], // GeoJSON에서 좌표를 가져올 것
|
||||
status: row.status || row.level,
|
||||
description: row.description || JSON.stringify(row, null, 2),
|
||||
description: JSON.stringify(row, null, 2), // 항상 전체 row 객체를 JSON으로 저장
|
||||
source: sourceName,
|
||||
color: dataSource?.polygonColor || getColorByStatus(row.status || row.level),
|
||||
});
|
||||
|
|
@ -537,7 +496,6 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
|
||||
// 위도/경도가 있고 polygon 모드가 아니면 마커로 처리
|
||||
if (lat !== undefined && lng !== undefined && (mapDisplayType as string) !== "polygon") {
|
||||
// // console.log(` → 마커로 처리: (${lat}, ${lng})`);
|
||||
markers.push({
|
||||
id: `${sourceName}-marker-${index}-${row.code || row.id || Date.now()}`, // 고유 ID 생성
|
||||
lat: Number(lat),
|
||||
|
|
@ -546,7 +504,7 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
longitude: Number(lng),
|
||||
name: row.name || row.title || row.area || row.region || `위치 ${index + 1}`,
|
||||
status: row.status || row.level,
|
||||
description: row.description || JSON.stringify(row, null, 2),
|
||||
description: JSON.stringify(row, null, 2), // 항상 전체 row 객체를 JSON으로 저장
|
||||
source: sourceName,
|
||||
color: dataSource?.markerColor || "#3b82f6", // 사용자 지정 색상 또는 기본 파랑
|
||||
});
|
||||
|
|
@ -560,7 +518,7 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
name: regionName,
|
||||
coordinates: [], // GeoJSON에서 좌표를 가져올 것
|
||||
status: row.status || row.level,
|
||||
description: row.description || JSON.stringify(row, null, 2),
|
||||
description: JSON.stringify(row, null, 2), // 항상 전체 row 객체를 JSON으로 저장
|
||||
source: sourceName,
|
||||
color: dataSource?.polygonColor || getColorByStatus(row.status || row.level),
|
||||
});
|
||||
|
|
@ -571,7 +529,6 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
}
|
||||
});
|
||||
|
||||
// // console.log(`✅ ${sourceName}: 마커 ${markers.length}개, 폴리곤 ${polygons.length}개 변환 완료`);
|
||||
return { markers, polygons };
|
||||
};
|
||||
|
||||
|
|
@ -627,6 +584,97 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
|
||||
// 해상 구역 폴리곤 좌표 (기상청 특보 구역 기준)
|
||||
const MARITIME_ZONES: Record<string, Array<[number, number]>> = {
|
||||
// 서해 해역
|
||||
"인천·경기북부앞바다": [
|
||||
[37.8, 125.8],
|
||||
[37.8, 126.5],
|
||||
[37.3, 126.5],
|
||||
[37.3, 125.8],
|
||||
],
|
||||
"인천·경기남부앞바다": [
|
||||
[37.3, 125.7],
|
||||
[37.3, 126.4],
|
||||
[36.8, 126.4],
|
||||
[36.8, 125.7],
|
||||
],
|
||||
충남북부앞바다: [
|
||||
[36.8, 125.6],
|
||||
[36.8, 126.3],
|
||||
[36.3, 126.3],
|
||||
[36.3, 125.6],
|
||||
],
|
||||
충남남부앞바다: [
|
||||
[36.3, 125.5],
|
||||
[36.3, 126.2],
|
||||
[35.8, 126.2],
|
||||
[35.8, 125.5],
|
||||
],
|
||||
전북북부앞바다: [
|
||||
[35.8, 125.4],
|
||||
[35.8, 126.1],
|
||||
[35.3, 126.1],
|
||||
[35.3, 125.4],
|
||||
],
|
||||
전북남부앞바다: [
|
||||
[35.3, 125.3],
|
||||
[35.3, 126.0],
|
||||
[34.8, 126.0],
|
||||
[34.8, 125.3],
|
||||
],
|
||||
전남북부서해앞바다: [
|
||||
[35.5, 125.2],
|
||||
[35.5, 125.9],
|
||||
[35.0, 125.9],
|
||||
[35.0, 125.2],
|
||||
],
|
||||
전남중부서해앞바다: [
|
||||
[35.0, 125.1],
|
||||
[35.0, 125.8],
|
||||
[34.5, 125.8],
|
||||
[34.5, 125.1],
|
||||
],
|
||||
전남남부서해앞바다: [
|
||||
[34.5, 125.0],
|
||||
[34.5, 125.7],
|
||||
[34.0, 125.7],
|
||||
[34.0, 125.0],
|
||||
],
|
||||
서해중부안쪽먼바다: [
|
||||
[37.5, 124.5],
|
||||
[37.5, 126.0],
|
||||
[36.0, 126.0],
|
||||
[36.0, 124.5],
|
||||
],
|
||||
서해중부바깥먼바다: [
|
||||
[37.5, 123.5],
|
||||
[37.5, 125.0],
|
||||
[36.0, 125.0],
|
||||
[36.0, 123.5],
|
||||
],
|
||||
서해남부북쪽안쪽먼바다: [
|
||||
[36.0, 124.5],
|
||||
[36.0, 126.0],
|
||||
[35.0, 126.0],
|
||||
[35.0, 124.5],
|
||||
],
|
||||
서해남부북쪽바깥먼바다: [
|
||||
[36.0, 123.5],
|
||||
[36.0, 125.0],
|
||||
[35.0, 125.0],
|
||||
[35.0, 123.5],
|
||||
],
|
||||
서해남부남쪽안쪽먼바다: [
|
||||
[35.0, 124.0],
|
||||
[35.0, 125.5],
|
||||
[34.0, 125.5],
|
||||
[34.0, 124.0],
|
||||
],
|
||||
서해남부남쪽바깥먼바다: [
|
||||
[35.0, 123.0],
|
||||
[35.0, 124.5],
|
||||
[33.5, 124.5],
|
||||
[33.5, 123.0],
|
||||
],
|
||||
// 제주도 해역
|
||||
제주도남부앞바다: [
|
||||
[33.25, 126.0],
|
||||
|
|
@ -896,7 +944,6 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
try {
|
||||
const response = await fetch("/geojson/korea-municipalities.json");
|
||||
const data = await response.json();
|
||||
// // console.log("🗺️ GeoJSON 로드 완료:", data.features?.length, "개 시/군/구");
|
||||
setGeoJsonData(data);
|
||||
} catch (err) {
|
||||
console.error("❌ GeoJSON 로드 실패:", err);
|
||||
|
|
@ -982,7 +1029,13 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
<p className="text-destructive text-sm">{error}</p>
|
||||
</div>
|
||||
) : (
|
||||
<MapContainer center={center} zoom={13} style={{ width: "100%", height: "100%" }} className="z-0">
|
||||
<MapContainer
|
||||
key={`map-widget-${element.id}`}
|
||||
center={center}
|
||||
zoom={13}
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
className="z-0"
|
||||
>
|
||||
<TileLayer url={tileMapUrl} attribution="© VWorld" maxZoom={19} />
|
||||
|
||||
{/* 폴리곤 렌더링 */}
|
||||
|
|
@ -1069,14 +1122,57 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
});
|
||||
|
||||
if (matchingPolygon) {
|
||||
layer.bindPopup(`
|
||||
<div class="min-w-[200px]">
|
||||
<div class="mb-2 font-semibold">${matchingPolygon.name}</div>
|
||||
${matchingPolygon.source ? `<div class="mb-1 text-xs text-muted-foreground">출처: ${matchingPolygon.source}</div>` : ""}
|
||||
${matchingPolygon.status ? `<div class="mb-1 text-xs">상태: ${matchingPolygon.status}</div>` : ""}
|
||||
${matchingPolygon.description ? `<div class="mt-2 max-h-[200px] overflow-auto text-xs"><pre class="whitespace-pre-wrap">${matchingPolygon.description}</pre></div>` : ""}
|
||||
</div>
|
||||
`);
|
||||
// 폴리곤의 데이터 소스 찾기
|
||||
const polygonDataSource = dataSources?.find((ds) => ds.name === matchingPolygon.source);
|
||||
const popupFields = polygonDataSource?.popupFields;
|
||||
|
||||
let popupContent = "";
|
||||
|
||||
// popupFields가 설정되어 있으면 설정된 필드만 표시
|
||||
if (popupFields && popupFields.length > 0 && matchingPolygon.description) {
|
||||
try {
|
||||
const parsed = JSON.parse(matchingPolygon.description);
|
||||
popupContent = `
|
||||
<div class="min-w-[200px]">
|
||||
${matchingPolygon.source ? `<div class="mb-2 border-b pb-2"><div class="text-gray-500 text-xs">📡 ${matchingPolygon.source}</div></div>` : ""}
|
||||
<div class="bg-gray-100 rounded p-2">
|
||||
<div class="text-gray-900 mb-1 text-xs font-semibold">상세 정보</div>
|
||||
<div class="space-y-2">
|
||||
${popupFields
|
||||
.map((field) => {
|
||||
const value = parsed[field.fieldName];
|
||||
if (value === undefined || value === null) return "";
|
||||
return `<div class="text-xs"><span class="text-gray-600 font-medium">${field.label}:</span> <span class="text-gray-900">${value}</span></div>`;
|
||||
})
|
||||
.join("")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} catch (error) {
|
||||
// JSON 파싱 실패 시 기본 표시
|
||||
popupContent = `
|
||||
<div class="min-w-[200px]">
|
||||
<div class="mb-2 font-semibold">${matchingPolygon.name}</div>
|
||||
${matchingPolygon.source ? `<div class="mb-1 text-xs text-muted-foreground">출처: ${matchingPolygon.source}</div>` : ""}
|
||||
${matchingPolygon.status ? `<div class="mb-1 text-xs">상태: ${matchingPolygon.status}</div>` : ""}
|
||||
${matchingPolygon.description ? `<div class="mt-2 max-h-[200px] overflow-auto text-xs"><pre class="whitespace-pre-wrap">${matchingPolygon.description}</pre></div>` : ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
} else {
|
||||
// popupFields가 없으면 전체 데이터 표시
|
||||
popupContent = `
|
||||
<div class="min-w-[200px]">
|
||||
<div class="mb-2 font-semibold">${matchingPolygon.name}</div>
|
||||
${matchingPolygon.source ? `<div class="mb-1 text-xs text-muted-foreground">출처: ${matchingPolygon.source}</div>` : ""}
|
||||
${matchingPolygon.status ? `<div class="mb-1 text-xs">상태: ${matchingPolygon.status}</div>` : ""}
|
||||
${matchingPolygon.description ? `<div class="mt-2 max-h-[200px] overflow-auto text-xs"><pre class="whitespace-pre-wrap">${matchingPolygon.description}</pre></div>` : ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
layer.bindPopup(popupContent);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
|
@ -1089,33 +1185,91 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
{/* 폴리곤 렌더링 (해상 구역만) */}
|
||||
{polygons
|
||||
.filter((p) => MARITIME_ZONES[p.name])
|
||||
.map((polygon) => (
|
||||
<Polygon
|
||||
key={polygon.id}
|
||||
positions={polygon.coordinates}
|
||||
pathOptions={{
|
||||
color: polygon.color || "#3b82f6",
|
||||
fillColor: polygon.color || "#3b82f6",
|
||||
fillOpacity: 0.3,
|
||||
weight: 2,
|
||||
}}
|
||||
>
|
||||
<Popup>
|
||||
<div className="min-w-[200px]">
|
||||
<div className="mb-2 font-semibold">{polygon.name}</div>
|
||||
{polygon.source && (
|
||||
<div className="text-muted-foreground mb-1 text-xs">출처: {polygon.source}</div>
|
||||
)}
|
||||
{polygon.status && <div className="mb-1 text-xs">상태: {polygon.status}</div>}
|
||||
{polygon.description && (
|
||||
<div className="mt-2 max-h-[200px] overflow-auto text-xs">
|
||||
<pre className="whitespace-pre-wrap">{polygon.description}</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Popup>
|
||||
</Polygon>
|
||||
))}
|
||||
.map((polygon) => {
|
||||
// 폴리곤의 데이터 소스 찾기
|
||||
const polygonDataSource = dataSources?.find((ds) => ds.name === polygon.source);
|
||||
const popupFields = polygonDataSource?.popupFields;
|
||||
|
||||
return (
|
||||
<Polygon
|
||||
key={polygon.id}
|
||||
positions={polygon.coordinates}
|
||||
pathOptions={{
|
||||
color: polygon.color || "#3b82f6",
|
||||
fillColor: polygon.color || "#3b82f6",
|
||||
fillOpacity: 0.3,
|
||||
weight: 2,
|
||||
}}
|
||||
>
|
||||
<Popup>
|
||||
<div className="min-w-[200px]">
|
||||
{/* popupFields가 설정되어 있으면 설정된 필드만 표시 */}
|
||||
{popupFields && popupFields.length > 0 && polygon.description ? (
|
||||
(() => {
|
||||
try {
|
||||
const parsed = JSON.parse(polygon.description);
|
||||
return (
|
||||
<>
|
||||
{polygon.source && (
|
||||
<div className="mb-2 border-b pb-2">
|
||||
<div className="text-muted-foreground text-xs">📡 {polygon.source}</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="bg-muted rounded p-2">
|
||||
<div className="text-foreground mb-1 text-xs font-semibold">상세 정보</div>
|
||||
<div className="space-y-2">
|
||||
{popupFields.map((field, idx) => {
|
||||
const value = parsed[field.fieldName];
|
||||
if (value === undefined || value === null) return null;
|
||||
return (
|
||||
<div key={idx} className="text-xs">
|
||||
<span className="text-muted-foreground font-medium">{field.label}:</span>{" "}
|
||||
<span className="text-foreground">{String(value)}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
} catch (error) {
|
||||
// JSON 파싱 실패 시 기본 표시
|
||||
return (
|
||||
<>
|
||||
<div className="mb-2 font-semibold">{polygon.name}</div>
|
||||
{polygon.source && (
|
||||
<div className="text-muted-foreground mb-1 text-xs">출처: {polygon.source}</div>
|
||||
)}
|
||||
{polygon.status && <div className="mb-1 text-xs">상태: {polygon.status}</div>}
|
||||
{polygon.description && (
|
||||
<div className="mt-2 max-h-[200px] overflow-auto text-xs">
|
||||
<pre className="whitespace-pre-wrap">{polygon.description}</pre>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
})()
|
||||
) : (
|
||||
// popupFields가 없으면 전체 데이터 표시
|
||||
<>
|
||||
<div className="mb-2 font-semibold">{polygon.name}</div>
|
||||
{polygon.source && (
|
||||
<div className="text-muted-foreground mb-1 text-xs">출처: {polygon.source}</div>
|
||||
)}
|
||||
{polygon.status && <div className="mb-1 text-xs">상태: {polygon.status}</div>}
|
||||
{polygon.description && (
|
||||
<div className="mt-2 max-h-[200px] overflow-auto text-xs">
|
||||
<pre className="whitespace-pre-wrap">{polygon.description}</pre>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Popup>
|
||||
</Polygon>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* 마커 렌더링 */}
|
||||
{markers.map((marker) => {
|
||||
|
|
@ -1227,8 +1381,9 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
<div className="space-y-2">
|
||||
{marker.description &&
|
||||
(() => {
|
||||
const firstDataSource = dataSources?.[0];
|
||||
const popupFields = firstDataSource?.popupFields;
|
||||
// 마커의 소스에 해당하는 데이터 소스 찾기
|
||||
const sourceDataSource = dataSources?.find((ds) => ds.name === marker.source);
|
||||
const popupFields = sourceDataSource?.popupFields;
|
||||
|
||||
// popupFields가 설정되어 있으면 설정된 필드만 표시
|
||||
if (popupFields && popupFields.length > 0) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue