1037 lines
41 KiB
JavaScript
1037 lines
41 KiB
JavaScript
// 메인 JavaScript 파일
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
console.log('Flask 앱이 로드되었습니다.');
|
|
|
|
// 고흥 지역 좌표
|
|
const GOHEUNG_LAT = 34.6047;
|
|
const GOHEUNG_LNG = 127.2855;
|
|
|
|
// 지도 객체 저장
|
|
var maps = {};
|
|
|
|
// 지도 초기화 함수
|
|
function initMap(containerId, lat, lng, zoom) {
|
|
if (maps[containerId]) {
|
|
maps[containerId].invalidateSize();
|
|
return maps[containerId];
|
|
}
|
|
|
|
var container = document.getElementById(containerId);
|
|
if (!container) return null;
|
|
|
|
var map = L.map(containerId).setView([lat, lng], zoom);
|
|
|
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
attribution: '© OpenStreetMap contributors'
|
|
}).addTo(map);
|
|
|
|
// 고흥 마커 추가
|
|
L.marker([lat, lng]).addTo(map)
|
|
.bindPopup('고흥')
|
|
.openPopup();
|
|
|
|
maps[containerId] = map;
|
|
return map;
|
|
}
|
|
|
|
// 메뉴 아이템과 페이지 콘텐츠 요소 가져오기
|
|
const menuItems = document.querySelectorAll('.menu-item');
|
|
const submenuItems = document.querySelectorAll('.submenu-item');
|
|
const pageContents = document.querySelectorAll('.page-content');
|
|
const hasSubmenuItems = document.querySelectorAll('.has-submenu');
|
|
|
|
// 페이지 전환 함수
|
|
function switchPage(pageId) {
|
|
// 모든 페이지 콘텐츠 숨기기
|
|
pageContents.forEach(function(content) {
|
|
content.classList.remove('active');
|
|
});
|
|
|
|
// 모든 메뉴 아이템 비활성화
|
|
menuItems.forEach(function(item) {
|
|
item.classList.remove('active');
|
|
});
|
|
|
|
// 모든 서브메뉴 아이템 비활성화
|
|
submenuItems.forEach(function(item) {
|
|
item.classList.remove('active');
|
|
});
|
|
|
|
// 선택된 페이지 콘텐츠 표시
|
|
const targetPage = document.getElementById(pageId);
|
|
if (targetPage) {
|
|
targetPage.classList.add('active');
|
|
}
|
|
|
|
// 선택된 메뉴 아이템 활성화
|
|
const activeMenuItem = document.querySelector('.menu-item[data-page="' + pageId + '"]');
|
|
if (activeMenuItem) {
|
|
activeMenuItem.classList.add('active');
|
|
}
|
|
|
|
// 선택된 서브메뉴 아이템 활성화
|
|
const activeSubmenuItem = document.querySelector('.submenu-item[data-page="' + pageId + '"]');
|
|
if (activeSubmenuItem) {
|
|
activeSubmenuItem.classList.add('active');
|
|
// 부모 메뉴도 활성화
|
|
const parentMenu = activeSubmenuItem.closest('.has-submenu');
|
|
if (parentMenu) {
|
|
parentMenu.querySelector('.menu-item').classList.add('active');
|
|
}
|
|
}
|
|
|
|
// 페이지별 지도 초기화
|
|
setTimeout(function() {
|
|
if (pageId === 'intro') {
|
|
initMap('map-intro', GOHEUNG_LAT, GOHEUNG_LNG, 11);
|
|
} else if (pageId === 'flood-simulation') {
|
|
Terrain3D.init('terrain-flood-3d');
|
|
loadFlood3DChart();
|
|
} else if (pageId === 'flood-monitoring') {
|
|
initMap('flood-heatmap', GOHEUNG_LAT, GOHEUNG_LNG, 12);
|
|
initMap('flood-mini-map', GOHEUNG_LAT, GOHEUNG_LNG, 13);
|
|
loadFloodMonitoringData();
|
|
} else if (pageId === 'drought-simulation') {
|
|
Terrain3D.init('terrain-drought-3d');
|
|
loadDrought3DChart();
|
|
} else if (pageId === 'drought-monitoring') {
|
|
initGridMap('drought-heatmap');
|
|
initMap('drought-mini-map', GOHEUNG_LAT, GOHEUNG_LNG, 13);
|
|
loadDroughtMonitoringData();
|
|
} else if (pageId === 'kma-test') {
|
|
loadKmaForecast();
|
|
}
|
|
}, 100);
|
|
}
|
|
|
|
// 침수 3D 시뮬레이션 차트 로드
|
|
function loadFlood3DChart() {
|
|
fetch('/api/terrain/simulation-chart')
|
|
.then(function(response) { return response.json(); })
|
|
.then(function(data) {
|
|
if (data.status === 'success' && data.image) {
|
|
var chartImg = document.getElementById('flood-simulation-chart');
|
|
if (chartImg) {
|
|
chartImg.src = 'data:image/png;base64,' + data.image;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// 가뭄 3D 시뮬레이션 차트 로드
|
|
function loadDrought3DChart() {
|
|
fetch('/api/terrain/simulation-chart')
|
|
.then(function(response) { return response.json(); })
|
|
.then(function(data) {
|
|
if (data.status === 'success' && data.image) {
|
|
var chartImg = document.getElementById('drought-simulation-chart');
|
|
if (chartImg) {
|
|
chartImg.src = 'data:image/png;base64,' + data.image;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// 침수 모니터링 데이터 로드
|
|
function loadFloodMonitoringData() {
|
|
// 히트맵 이미지 로드
|
|
fetch('/api/flood/monitoring-heatmap')
|
|
.then(function(response) { return response.json(); })
|
|
.then(function(data) {
|
|
if (data.status === 'success') {
|
|
var heatmapOverlay = document.getElementById('flood-heatmap-overlay');
|
|
if (heatmapOverlay && data.heatmap_image) {
|
|
heatmapOverlay.src = 'data:image/png;base64,' + data.heatmap_image;
|
|
}
|
|
var miniHeatmap = document.getElementById('flood-mini-heatmap');
|
|
if (miniHeatmap && data.mini_heatmap_image) {
|
|
miniHeatmap.src = 'data:image/png;base64,' + data.mini_heatmap_image;
|
|
}
|
|
// Risk Score 업데이트
|
|
var riskScoreEl = document.getElementById('flood-risk-score');
|
|
if (riskScoreEl && data.risk_score) {
|
|
riskScoreEl.textContent = data.risk_score;
|
|
}
|
|
}
|
|
});
|
|
|
|
// 월별 강수량 차트 로드
|
|
fetch('/api/flood/monthly-chart')
|
|
.then(function(response) { return response.json(); })
|
|
.then(function(data) {
|
|
if (data.status === 'success' && data.image) {
|
|
var chartImg = document.getElementById('flood-monthly-chart');
|
|
if (chartImg) {
|
|
chartImg.src = 'data:image/png;base64,' + data.image;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// 침수 모니터링 필터 변경 이벤트
|
|
var floodMapType = document.getElementById('flood-map-type');
|
|
var floodPeriod = document.getElementById('flood-period');
|
|
var floodViewType = document.getElementById('flood-view-type');
|
|
|
|
[floodMapType, floodPeriod, floodViewType].forEach(function(select) {
|
|
if (select) {
|
|
select.addEventListener('change', function() {
|
|
loadFloodMonitoringData();
|
|
});
|
|
}
|
|
});
|
|
|
|
// 침수 모니터링 버튼 이벤트
|
|
var floodDetailBtn = document.getElementById('btn-flood-detail');
|
|
if (floodDetailBtn) {
|
|
floodDetailBtn.addEventListener('click', function() {
|
|
// 시뮬레이션 페이지로 이동
|
|
switchPage('flood-simulation');
|
|
// 하위 메뉴 열기
|
|
var floodSubmenu = document.querySelector('.has-submenu:nth-child(3)');
|
|
if (floodSubmenu) {
|
|
floodSubmenu.classList.add('open');
|
|
}
|
|
});
|
|
}
|
|
|
|
var floodReportBtn = document.getElementById('btn-flood-report');
|
|
if (floodReportBtn) {
|
|
floodReportBtn.addEventListener('click', function() {
|
|
alert('보고서 출력 기능은 추후 업데이트 예정입니다.');
|
|
});
|
|
}
|
|
|
|
// 침수 수위 곡선 그래프 로드
|
|
function loadFloodGraph() {
|
|
var graphType = document.getElementById('flood-graph-type');
|
|
var graphPeriod = document.getElementById('flood-graph-period');
|
|
var graphImage = document.getElementById('flood-graph-image');
|
|
var graphPlaceholder = document.getElementById('flood-graph-placeholder');
|
|
var graphLoading = document.getElementById('flood-graph-loading');
|
|
|
|
if (!graphType || !graphPeriod) return;
|
|
|
|
var type = graphType.value;
|
|
var period = graphPeriod.value;
|
|
|
|
// 로딩 표시
|
|
if (graphLoading) graphLoading.style.display = 'block';
|
|
if (graphPlaceholder) graphPlaceholder.style.display = 'none';
|
|
|
|
fetch('/api/flood/graph?type=' + type + '&period=' + period)
|
|
.then(function(response) { return response.json(); })
|
|
.then(function(data) {
|
|
if (graphLoading) graphLoading.style.display = 'none';
|
|
|
|
if (data.status === 'success' && data.image) {
|
|
if (graphImage) {
|
|
graphImage.src = 'data:image/png;base64,' + data.image;
|
|
graphImage.style.display = 'block';
|
|
}
|
|
if (graphPlaceholder) graphPlaceholder.style.display = 'none';
|
|
|
|
// 통계 정보 업데이트
|
|
if (data.stats) {
|
|
var currentLevel = document.getElementById('flood-current-level');
|
|
var avgLevel = document.getElementById('flood-avg-level');
|
|
var maxLevel = document.getElementById('flood-max-level');
|
|
|
|
if (currentLevel) currentLevel.textContent = data.stats.current;
|
|
if (avgLevel) avgLevel.textContent = data.stats.avg;
|
|
if (maxLevel) maxLevel.textContent = data.stats.max;
|
|
}
|
|
}
|
|
})
|
|
.catch(function(error) {
|
|
if (graphLoading) graphLoading.style.display = 'none';
|
|
if (graphPlaceholder) {
|
|
graphPlaceholder.style.display = 'block';
|
|
graphPlaceholder.querySelector('span').textContent = '그래프 로드 실패';
|
|
}
|
|
});
|
|
}
|
|
|
|
// 침수 수위 곡선 버튼 이벤트
|
|
var floodGraphLoadBtn = document.getElementById('btn-flood-graph-load');
|
|
if (floodGraphLoadBtn) {
|
|
floodGraphLoadBtn.addEventListener('click', loadFloodGraph);
|
|
}
|
|
|
|
// 가뭄 시계열 변화 그래프 로드
|
|
function loadDroughtGraph() {
|
|
var graphType = document.getElementById('drought-graph-type');
|
|
var graphPeriod = document.getElementById('drought-graph-period');
|
|
var graphImage = document.getElementById('drought-graph-image');
|
|
var graphPlaceholder = document.getElementById('drought-graph-placeholder');
|
|
var graphLoading = document.getElementById('drought-graph-loading');
|
|
|
|
if (!graphType || !graphPeriod) return;
|
|
|
|
var type = graphType.value;
|
|
var period = graphPeriod.value;
|
|
|
|
// 로딩 표시
|
|
if (graphLoading) graphLoading.style.display = 'block';
|
|
if (graphPlaceholder) graphPlaceholder.style.display = 'none';
|
|
|
|
fetch('/api/drought/graph?type=' + type + '&period=' + period)
|
|
.then(function(response) { return response.json(); })
|
|
.then(function(data) {
|
|
if (graphLoading) graphLoading.style.display = 'none';
|
|
|
|
if (data.status === 'success' && data.image) {
|
|
if (graphImage) {
|
|
graphImage.src = 'data:image/png;base64,' + data.image;
|
|
graphImage.style.display = 'block';
|
|
}
|
|
if (graphPlaceholder) graphPlaceholder.style.display = 'none';
|
|
|
|
// 통계 정보 업데이트
|
|
if (data.stats) {
|
|
var currentValue = document.getElementById('drought-current-value');
|
|
var avgValue = document.getElementById('drought-avg-value');
|
|
var minValue = document.getElementById('drought-min-value');
|
|
var gradeValue = document.getElementById('drought-grade');
|
|
|
|
if (currentValue) currentValue.textContent = data.stats.current;
|
|
if (avgValue) avgValue.textContent = data.stats.avg;
|
|
if (minValue) minValue.textContent = data.stats.min;
|
|
if (gradeValue) gradeValue.textContent = data.stats.grade;
|
|
}
|
|
}
|
|
})
|
|
.catch(function(error) {
|
|
if (graphLoading) graphLoading.style.display = 'none';
|
|
if (graphPlaceholder) {
|
|
graphPlaceholder.style.display = 'block';
|
|
graphPlaceholder.querySelector('span').textContent = '그래프 로드 실패';
|
|
}
|
|
});
|
|
}
|
|
|
|
// 가뭄 시계열 변화 그래프 버튼 이벤트
|
|
var droughtGraphLoadBtn = document.getElementById('btn-drought-graph-load');
|
|
if (droughtGraphLoadBtn) {
|
|
droughtGraphLoadBtn.addEventListener('click', loadDroughtGraph);
|
|
}
|
|
|
|
// SHP 파일을 JPG로 렌더링하여 지도 영역에 표시
|
|
function initGridMap(containerId, gridSize) {
|
|
gridSize = gridSize || 500;
|
|
|
|
var container = document.getElementById(containerId);
|
|
if (!container) return;
|
|
|
|
// 기존 Leaflet 지도 제거 (이미지로 대체)
|
|
if (maps[containerId]) {
|
|
maps[containerId].remove();
|
|
delete maps[containerId];
|
|
}
|
|
|
|
// 로딩 표시
|
|
container.innerHTML = '<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#aaa;">SHP 렌더링 중...</div>';
|
|
|
|
fetch('/api/drought/shp-render?grid=' + gridSize)
|
|
.then(function(response) { return response.json(); })
|
|
.then(function(data) {
|
|
if (data.status === 'success' && data.image) {
|
|
container.innerHTML = '';
|
|
var img = document.createElement('img');
|
|
img.src = 'data:image/jpeg;base64,' + data.image;
|
|
img.style.width = '100%';
|
|
img.style.height = '100%';
|
|
img.style.objectFit = 'contain';
|
|
img.alt = '가뭄 SHP 렌더링';
|
|
container.appendChild(img);
|
|
} else {
|
|
container.innerHTML = '<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#e74c3c;">' + (data.message || '렌더링 실패') + '</div>';
|
|
}
|
|
})
|
|
.catch(function(error) {
|
|
container.innerHTML = '<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#e74c3c;">오류: ' + error.message + '</div>';
|
|
});
|
|
}
|
|
|
|
// 격자 단위 버튼 이벤트
|
|
document.querySelectorAll('.grid-btn').forEach(function(btn) {
|
|
btn.addEventListener('click', function() {
|
|
document.querySelectorAll('.grid-btn').forEach(function(b) { b.classList.remove('active'); });
|
|
btn.classList.add('active');
|
|
initGridMap('drought-heatmap', parseInt(btn.dataset.grid));
|
|
});
|
|
});
|
|
|
|
// 가뭄 모니터링 데이터 로드
|
|
function loadDroughtMonitoringData() {
|
|
// 히트맵 이미지 로드
|
|
fetch('/api/drought/monitoring-heatmap')
|
|
.then(function(response) { return response.json(); })
|
|
.then(function(data) {
|
|
if (data.status === 'success') {
|
|
var heatmapOverlay = document.getElementById('drought-heatmap-overlay');
|
|
if (heatmapOverlay && data.heatmap_image) {
|
|
heatmapOverlay.src = 'data:image/png;base64,' + data.heatmap_image;
|
|
}
|
|
var miniHeatmap = document.getElementById('drought-mini-heatmap');
|
|
if (miniHeatmap && data.mini_heatmap_image) {
|
|
miniHeatmap.src = 'data:image/png;base64,' + data.mini_heatmap_image;
|
|
}
|
|
// Risk Score 업데이트
|
|
var riskScoreEl = document.getElementById('drought-risk-score');
|
|
if (riskScoreEl && data.risk_score) {
|
|
riskScoreEl.textContent = data.risk_score;
|
|
}
|
|
}
|
|
});
|
|
|
|
// 월별 가뭄 지수 차트 로드
|
|
fetch('/api/drought/monthly-chart')
|
|
.then(function(response) { return response.json(); })
|
|
.then(function(data) {
|
|
if (data.status === 'success' && data.image) {
|
|
var chartImg = document.getElementById('drought-monthly-chart');
|
|
if (chartImg) {
|
|
chartImg.src = 'data:image/png;base64,' + data.image;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// 가뭄 모니터링 필터 변경 이벤트
|
|
var droughtMapType = document.getElementById('drought-map-type');
|
|
var droughtPeriod = document.getElementById('drought-period');
|
|
var droughtViewType = document.getElementById('drought-view-type');
|
|
|
|
[droughtMapType, droughtPeriod, droughtViewType].forEach(function(select) {
|
|
if (select) {
|
|
select.addEventListener('change', function() {
|
|
loadDroughtMonitoringData();
|
|
});
|
|
}
|
|
});
|
|
|
|
// 가뭄 모니터링 버튼 이벤트
|
|
var droughtDetailBtn = document.getElementById('btn-drought-detail');
|
|
if (droughtDetailBtn) {
|
|
droughtDetailBtn.addEventListener('click', function() {
|
|
// 시뮬레이션 페이지로 이동
|
|
switchPage('drought-simulation');
|
|
// 하위 메뉴 열기
|
|
var droughtSubmenu = document.querySelector('.has-submenu:nth-child(4)');
|
|
if (droughtSubmenu) {
|
|
droughtSubmenu.classList.add('open');
|
|
}
|
|
});
|
|
}
|
|
|
|
var droughtReportBtn = document.getElementById('btn-drought-report');
|
|
if (droughtReportBtn) {
|
|
droughtReportBtn.addEventListener('click', function() {
|
|
alert('보고서 출력 기능은 추후 업데이트 예정입니다.');
|
|
});
|
|
}
|
|
|
|
// 가뭄 탭 버튼 이벤트 (탭 전환만, 페이지 이동 없음)
|
|
document.querySelectorAll('.drought-tab-btn').forEach(function(btn) {
|
|
btn.addEventListener('click', function() {
|
|
document.querySelectorAll('.drought-tab-btn').forEach(function(b) { b.classList.remove('active'); });
|
|
btn.classList.add('active');
|
|
});
|
|
});
|
|
|
|
// 메뉴 클릭 이벤트 리스너 등록
|
|
menuItems.forEach(function(item) {
|
|
item.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
const parentLi = this.parentElement;
|
|
|
|
// 하위 메뉴가 있는 경우 토글
|
|
if (parentLi.classList.contains('has-submenu')) {
|
|
parentLi.classList.toggle('open');
|
|
}
|
|
|
|
const pageId = this.getAttribute('data-page');
|
|
switchPage(pageId);
|
|
});
|
|
});
|
|
|
|
// 서브메뉴 클릭 이벤트 리스너 등록
|
|
submenuItems.forEach(function(item) {
|
|
item.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
const pageId = this.getAttribute('data-page');
|
|
switchPage(pageId);
|
|
});
|
|
});
|
|
|
|
// API 호출 함수
|
|
function callApi(apiUrl, container) {
|
|
const loading = container.querySelector('.loading');
|
|
const plotImage = container.querySelector('.plot-image');
|
|
const plotMessage = container.querySelector('.plot-message');
|
|
|
|
// 로딩 표시
|
|
loading.style.display = 'block';
|
|
plotImage.style.display = 'none';
|
|
plotMessage.style.display = 'none';
|
|
|
|
fetch(apiUrl)
|
|
.then(function(response) {
|
|
return response.json();
|
|
})
|
|
.then(function(data) {
|
|
loading.style.display = 'none';
|
|
if (data.status === 'success' && data.image) {
|
|
plotImage.src = 'data:image/png;base64,' + data.image;
|
|
plotImage.style.display = 'block';
|
|
} else {
|
|
plotMessage.textContent = data.message || '결과를 불러올 수 없습니다.';
|
|
plotMessage.style.display = 'block';
|
|
}
|
|
})
|
|
.catch(function(error) {
|
|
loading.style.display = 'none';
|
|
plotMessage.textContent = '오류가 발생했습니다: ' + error.message;
|
|
plotMessage.style.display = 'block';
|
|
});
|
|
}
|
|
|
|
// API 버튼 클릭 이벤트 리스너 등록
|
|
const apiBtns = document.querySelectorAll('.api-btn');
|
|
apiBtns.forEach(function(btn) {
|
|
btn.addEventListener('click', function() {
|
|
const apiUrl = this.getAttribute('data-api');
|
|
const container = this.parentElement.querySelector('.plot-container');
|
|
callApi(apiUrl, container);
|
|
});
|
|
});
|
|
|
|
// 침수 3D 시뮬레이션 버튼 클릭 이벤트
|
|
var floodSimBtn = document.getElementById('btn-flood-simulation');
|
|
if (floodSimBtn) {
|
|
floodSimBtn.addEventListener('click', function() {
|
|
loadFlood3DChart();
|
|
// 통계 업데이트 (랜덤 시뮬레이션)
|
|
var areaEl = document.querySelector('.flood-stat-area');
|
|
var buildingEl = document.querySelector('.flood-stat-building');
|
|
var costEl = document.querySelector('.flood-stat-cost');
|
|
|
|
if (areaEl) {
|
|
var area = Math.floor(Math.random() * 3000) + 3000;
|
|
areaEl.innerHTML = area.toLocaleString() + ' <small>㎡</small>';
|
|
}
|
|
if (buildingEl) {
|
|
var building = Math.floor(Math.random() * 100) + 80;
|
|
buildingEl.innerHTML = building.toLocaleString() + ' <small>동</small>';
|
|
}
|
|
if (costEl) {
|
|
var cost = (Math.random() * 15 + 10).toFixed(1);
|
|
costEl.innerHTML = cost + ' <small>억원</small>';
|
|
}
|
|
});
|
|
}
|
|
|
|
// 가뭄 3D 시뮬레이션 버튼 클릭 이벤트
|
|
var droughtSimBtn = document.getElementById('btn-drought-simulation');
|
|
if (droughtSimBtn) {
|
|
droughtSimBtn.addEventListener('click', function() {
|
|
loadDrought3DChart();
|
|
// 통계 업데이트 (랜덤 시뮬레이션)
|
|
var areaEl = document.querySelector('.drought-stat-area');
|
|
var farmEl = document.querySelector('.drought-stat-farm');
|
|
var costEl = document.querySelector('.drought-stat-cost');
|
|
|
|
if (areaEl) {
|
|
var area = Math.floor(Math.random() * 5000) + 5000;
|
|
areaEl.innerHTML = area.toLocaleString() + ' <small>㎡</small>';
|
|
}
|
|
if (farmEl) {
|
|
var farm = Math.floor(Math.random() * 2000) + 2000;
|
|
farmEl.innerHTML = farm.toLocaleString() + ' <small>㎡</small>';
|
|
}
|
|
if (costEl) {
|
|
var cost = (Math.random() * 10 + 8).toFixed(1);
|
|
costEl.innerHTML = cost + ' <small>억원</small>';
|
|
}
|
|
});
|
|
}
|
|
|
|
// ========== Water Body 추출 기능 (Drag & Drop) ==========
|
|
var wbDropzone = document.getElementById('wb-dropzone');
|
|
var wbFileInput = document.getElementById('wb-file-input');
|
|
var wbBrowseBtn = document.getElementById('btn-wb-browse');
|
|
var wbRemoveBtn = document.getElementById('btn-wb-remove');
|
|
var wbExtractBtn = document.getElementById('btn-waterbody-extract');
|
|
var wbDropzoneContent = document.getElementById('wb-dropzone-content');
|
|
var wbPreview = document.getElementById('wb-preview');
|
|
var wbInputImage = document.getElementById('wb-input-image');
|
|
var wbOutputImage = document.getElementById('wb-output-image');
|
|
var wbResultPlaceholder = document.getElementById('wb-result-placeholder');
|
|
var wbResultContainer = document.getElementById('wb-result-container');
|
|
var wbExtractLoading = document.getElementById('wb-extract-loading');
|
|
var wbResultInfo = document.getElementById('wb-result-info');
|
|
|
|
// 업로드된 파일 저장
|
|
var uploadedFile = null;
|
|
|
|
// 파일 선택 버튼 클릭
|
|
if (wbBrowseBtn) {
|
|
wbBrowseBtn.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
wbFileInput.click();
|
|
});
|
|
}
|
|
|
|
// 파일 선택 이벤트
|
|
if (wbFileInput) {
|
|
wbFileInput.addEventListener('change', function(e) {
|
|
var file = e.target.files[0];
|
|
if (file) {
|
|
handleFileUpload(file);
|
|
}
|
|
});
|
|
}
|
|
|
|
// 드래그 이벤트
|
|
if (wbDropzone) {
|
|
// 드래그 오버
|
|
wbDropzone.addEventListener('dragover', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
wbDropzone.classList.add('dragover');
|
|
});
|
|
|
|
// 드래그 리브
|
|
wbDropzone.addEventListener('dragleave', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
wbDropzone.classList.remove('dragover');
|
|
});
|
|
|
|
// 드롭
|
|
wbDropzone.addEventListener('drop', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
wbDropzone.classList.remove('dragover');
|
|
|
|
var file = e.dataTransfer.files[0];
|
|
if (file && file.type.startsWith('image/')) {
|
|
handleFileUpload(file);
|
|
} else {
|
|
alert('이미지 파일만 업로드 가능합니다.');
|
|
}
|
|
});
|
|
}
|
|
|
|
// 파일 업로드 처리
|
|
function handleFileUpload(file) {
|
|
uploadedFile = file;
|
|
|
|
// 이미지 미리보기
|
|
var reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
wbInputImage.src = e.target.result;
|
|
wbDropzoneContent.style.display = 'none';
|
|
wbPreview.style.display = 'block';
|
|
|
|
// 추출 버튼 활성화
|
|
if (wbExtractBtn) {
|
|
wbExtractBtn.disabled = false;
|
|
}
|
|
};
|
|
reader.readAsDataURL(file);
|
|
|
|
// 결과 영역 초기화
|
|
resetOutputArea();
|
|
}
|
|
|
|
// 이미지 제거 버튼
|
|
if (wbRemoveBtn) {
|
|
wbRemoveBtn.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
resetInputArea();
|
|
resetOutputArea();
|
|
});
|
|
}
|
|
|
|
// 입력 영역 초기화
|
|
function resetInputArea() {
|
|
uploadedFile = null;
|
|
wbInputImage.src = '';
|
|
wbDropzoneContent.style.display = 'flex';
|
|
wbPreview.style.display = 'none';
|
|
wbFileInput.value = '';
|
|
|
|
if (wbExtractBtn) {
|
|
wbExtractBtn.disabled = true;
|
|
}
|
|
}
|
|
|
|
// 출력 영역 초기화
|
|
function resetOutputArea() {
|
|
wbOutputImage.src = '';
|
|
wbOutputImage.style.display = 'none';
|
|
wbResultPlaceholder.style.display = 'flex';
|
|
wbResultInfo.style.display = 'none';
|
|
}
|
|
|
|
// 추출 버튼 클릭
|
|
if (wbExtractBtn) {
|
|
wbExtractBtn.addEventListener('click', function() {
|
|
if (!uploadedFile) {
|
|
alert('이미지를 먼저 업로드해주세요.');
|
|
return;
|
|
}
|
|
|
|
// 로딩 표시
|
|
wbExtractBtn.style.display = 'none';
|
|
wbExtractLoading.style.display = 'flex';
|
|
wbResultPlaceholder.style.display = 'none';
|
|
|
|
// FormData로 이미지 전송
|
|
var formData = new FormData();
|
|
formData.append('image', uploadedFile);
|
|
|
|
fetch('/api/waterbody/predict', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(function(response) { return response.json(); })
|
|
.then(function(data) {
|
|
wbExtractBtn.style.display = 'flex';
|
|
wbExtractLoading.style.display = 'none';
|
|
|
|
if (data.status === 'success') {
|
|
// 결과 이미지 표시
|
|
wbOutputImage.src = 'data:image/png;base64,' + data.output_image;
|
|
wbOutputImage.style.display = 'block';
|
|
wbResultPlaceholder.style.display = 'none';
|
|
|
|
// 결과 정보 표시
|
|
if (data.data) {
|
|
document.getElementById('wb-area').textContent = data.data.area || '-';
|
|
document.getElementById('wb-ratio').textContent = data.data.ratio || '-';
|
|
document.getElementById('wb-count').textContent = data.data.count ? data.data.count + '개' : '-';
|
|
document.getElementById('wb-confidence').textContent = data.data.confidence || '-';
|
|
}
|
|
wbResultInfo.style.display = 'flex';
|
|
} else {
|
|
alert('추출 실패: ' + (data.message || '알 수 없는 오류'));
|
|
wbResultPlaceholder.style.display = 'flex';
|
|
}
|
|
})
|
|
.catch(function(error) {
|
|
wbExtractBtn.style.display = 'flex';
|
|
wbExtractLoading.style.display = 'none';
|
|
wbResultPlaceholder.style.display = 'flex';
|
|
alert('오류가 발생했습니다: ' + error.message);
|
|
});
|
|
});
|
|
}
|
|
|
|
// ========== Intro 대시보드 기능 ==========
|
|
|
|
// 현재 날짜/시간 표시
|
|
function updateCurrentDateTime() {
|
|
var now = new Date();
|
|
var days = ['일', '월', '화', '수', '목', '금', '토'];
|
|
var dateStr = now.getFullYear() + '.' +
|
|
String(now.getMonth() + 1).padStart(2, '0') + '.' +
|
|
String(now.getDate()).padStart(2, '0') + ' (' + days[now.getDay()] + ')';
|
|
|
|
var dateTimeEl = document.getElementById('current-datetime');
|
|
if (dateTimeEl) {
|
|
dateTimeEl.textContent = dateStr;
|
|
}
|
|
}
|
|
|
|
// 날짜 셀렉터 초기화
|
|
function initDateSelector() {
|
|
var now = new Date();
|
|
var yearSelect = document.getElementById('select-year');
|
|
var monthSelect = document.getElementById('select-month');
|
|
var daySelect = document.getElementById('select-day');
|
|
|
|
if (!yearSelect || !monthSelect || !daySelect) return;
|
|
|
|
// 현재 날짜로 설정
|
|
yearSelect.value = now.getFullYear();
|
|
monthSelect.value = now.getMonth() + 1;
|
|
|
|
// 일 옵션 업데이트
|
|
updateDayOptions();
|
|
daySelect.value = now.getDate();
|
|
|
|
// 월 변경 시 일 옵션 업데이트
|
|
yearSelect.addEventListener('change', updateDayOptions);
|
|
monthSelect.addEventListener('change', updateDayOptions);
|
|
}
|
|
|
|
// 일 옵션 업데이트
|
|
function updateDayOptions() {
|
|
var yearSelect = document.getElementById('select-year');
|
|
var monthSelect = document.getElementById('select-month');
|
|
var daySelect = document.getElementById('select-day');
|
|
|
|
if (!yearSelect || !monthSelect || !daySelect) return;
|
|
|
|
var year = parseInt(yearSelect.value);
|
|
var month = parseInt(monthSelect.value);
|
|
var daysInMonth = new Date(year, month, 0).getDate();
|
|
|
|
var currentDay = parseInt(daySelect.value) || 1;
|
|
|
|
daySelect.innerHTML = '';
|
|
for (var i = 1; i <= daysInMonth; i++) {
|
|
var option = document.createElement('option');
|
|
option.value = i;
|
|
option.textContent = i + '일';
|
|
daySelect.appendChild(option);
|
|
}
|
|
|
|
daySelect.value = Math.min(currentDay, daysInMonth);
|
|
}
|
|
|
|
// 모니터링 데이터 업데이트 (시뮬레이션)
|
|
function updateMonitoringData() {
|
|
// 침수 예측 확률 업데이트
|
|
var floodProb = Math.floor(Math.random() * 40) + 10;
|
|
var floodProbEl = document.getElementById('flood-prob');
|
|
var floodFill = document.querySelector('.flood-fill');
|
|
var floodStatus = document.querySelector('.monitoring-card.flood .prob-status');
|
|
|
|
if (floodProbEl) floodProbEl.textContent = floodProb;
|
|
if (floodFill) floodFill.style.width = floodProb + '%';
|
|
if (floodStatus) {
|
|
if (floodProb < 30) {
|
|
floodStatus.className = 'prob-status safe';
|
|
floodStatus.textContent = '낮음';
|
|
} else if (floodProb < 60) {
|
|
floodStatus.className = 'prob-status warning';
|
|
floodStatus.textContent = '주의';
|
|
} else {
|
|
floodStatus.className = 'prob-status danger';
|
|
floodStatus.textContent = '위험';
|
|
}
|
|
}
|
|
|
|
// 가뭄 예측 확률 업데이트
|
|
var droughtProb = Math.floor(Math.random() * 50) + 30;
|
|
var droughtProbEl = document.getElementById('drought-prob');
|
|
var droughtFill = document.querySelector('.drought-fill');
|
|
var droughtStatus = document.querySelector('.monitoring-card.drought .prob-status');
|
|
|
|
if (droughtProbEl) droughtProbEl.textContent = droughtProb;
|
|
if (droughtFill) droughtFill.style.width = droughtProb + '%';
|
|
if (droughtStatus) {
|
|
if (droughtProb < 30) {
|
|
droughtStatus.className = 'prob-status safe';
|
|
droughtStatus.textContent = '낮음';
|
|
} else if (droughtProb < 60) {
|
|
droughtStatus.className = 'prob-status warning';
|
|
droughtStatus.textContent = '주의';
|
|
} else {
|
|
droughtStatus.className = 'prob-status danger';
|
|
droughtStatus.textContent = '위험';
|
|
}
|
|
}
|
|
|
|
// 종합 위험도 업데이트
|
|
var overallRisk = Math.floor((floodProb + droughtProb) / 2);
|
|
var gaugeValue = document.querySelector('.gauge-value');
|
|
var gaugeLabel = document.querySelector('.gauge-label');
|
|
var gaugeFill = document.querySelector('.gauge-fill');
|
|
|
|
if (gaugeValue) gaugeValue.textContent = overallRisk;
|
|
if (gaugeFill) {
|
|
gaugeFill.setAttribute('stroke-dasharray', overallRisk + ', 100');
|
|
if (overallRisk < 30) {
|
|
gaugeFill.className.baseVal = 'gauge-fill safe';
|
|
if (gaugeLabel) {
|
|
gaugeLabel.textContent = '안전';
|
|
gaugeLabel.style.color = '#27ae60';
|
|
}
|
|
} else if (overallRisk < 60) {
|
|
gaugeFill.className.baseVal = 'gauge-fill warning';
|
|
if (gaugeLabel) {
|
|
gaugeLabel.textContent = '보통';
|
|
gaugeLabel.style.color = '#f39c12';
|
|
}
|
|
} else {
|
|
gaugeFill.className.baseVal = 'gauge-fill danger';
|
|
if (gaugeLabel) {
|
|
gaugeLabel.textContent = '위험';
|
|
gaugeLabel.style.color = '#e74c3c';
|
|
}
|
|
}
|
|
}
|
|
|
|
// 예보 미니 업데이트
|
|
var floodForecasts = document.querySelectorAll('.monitoring-card.flood .fc-value');
|
|
floodForecasts.forEach(function(el) {
|
|
el.textContent = (floodProb + Math.floor(Math.random() * 10) - 5) + '%';
|
|
});
|
|
|
|
var droughtForecasts = document.querySelectorAll('.monitoring-card.drought .fc-value');
|
|
droughtForecasts.forEach(function(el) {
|
|
el.textContent = (droughtProb + Math.floor(Math.random() * 10) - 5) + '%';
|
|
});
|
|
}
|
|
|
|
// 날짜 조회 버튼 클릭
|
|
var dateSearchBtn = document.getElementById('btn-date-search');
|
|
if (dateSearchBtn) {
|
|
dateSearchBtn.addEventListener('click', function() {
|
|
updateMonitoringData();
|
|
|
|
var year = document.getElementById('select-year').value;
|
|
var month = document.getElementById('select-month').value;
|
|
var day = document.getElementById('select-day').value;
|
|
|
|
var dateTimeEl = document.getElementById('current-datetime');
|
|
var selectedDate = new Date(year, month - 1, day);
|
|
var days = ['일', '월', '화', '수', '목', '금', '토'];
|
|
var dateStr = year + '.' +
|
|
String(month).padStart(2, '0') + '.' +
|
|
String(day).padStart(2, '0') + ' (' + days[selectedDate.getDay()] + ')';
|
|
|
|
if (dateTimeEl) {
|
|
dateTimeEl.textContent = dateStr;
|
|
}
|
|
});
|
|
}
|
|
|
|
// ========== 테스트용 화면 (기상청 단기예보 - 고흥) ==========
|
|
var KMA_DISPLAY_COLS = [
|
|
{ key: 'numEf', label: '예보차수' },
|
|
{ key: 'wf', label: '날씨' },
|
|
{ key: 'ta', label: '예상기온(℃)' },
|
|
{ key: 'rnSt', label: '강수확률(%)' },
|
|
{ key: 'rnYn', label: '강수형태' },
|
|
{ key: 'wd1', label: '풍향(1)' },
|
|
{ key: 'wd2', label: '풍향(2)' },
|
|
{ key: 'wsIt', label: '풍속강도' },
|
|
{ key: 'wfCd', label: '하늘상태' }
|
|
];
|
|
|
|
var KMA_RNYN_NAMES = {
|
|
'0': '없음', '1': '비', '2': '비/눈', '3': '눈', '4': '소나기'
|
|
};
|
|
|
|
var KMA_WSLT_NAMES = {
|
|
'1': '약', '2': '약간강', '3': '강', '4': '매우강'
|
|
};
|
|
|
|
var kmaCache = null;
|
|
|
|
function loadKmaForecast() {
|
|
var placeholder = document.getElementById('kma-placeholder');
|
|
var loading = document.getElementById('kma-loading');
|
|
var dataContainer = document.getElementById('kma-data-container');
|
|
var summary = document.getElementById('kma-summary');
|
|
|
|
// 캐시가 있으면 재사용
|
|
if (kmaCache) {
|
|
renderKmaTable(kmaCache);
|
|
return;
|
|
}
|
|
|
|
if (placeholder) placeholder.style.display = 'none';
|
|
if (summary) summary.style.display = 'none';
|
|
loading.style.display = 'flex';
|
|
dataContainer.style.display = 'none';
|
|
|
|
fetch('/api/kma/forecast')
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(result) {
|
|
loading.style.display = 'none';
|
|
if (!result.success) {
|
|
if (placeholder) placeholder.style.display = 'block';
|
|
return;
|
|
}
|
|
kmaCache = result;
|
|
renderKmaTable(result);
|
|
})
|
|
.catch(function(err) {
|
|
loading.style.display = 'none';
|
|
if (placeholder) placeholder.style.display = 'block';
|
|
});
|
|
}
|
|
|
|
function renderKmaTable(result) {
|
|
var summary = document.getElementById('kma-summary');
|
|
var dataContainer = document.getElementById('kma-data-container');
|
|
|
|
document.getElementById('kma-count').textContent = result.count + '건';
|
|
|
|
// 발표시간 표시
|
|
var rows = result.rows || [];
|
|
if (rows.length > 0 && rows[0].announceTime) {
|
|
var s = String(rows[0].announceTime);
|
|
var formatted = s.length >= 10
|
|
? s.substring(0,4) + '.' + s.substring(4,6) + '.' + s.substring(6,8) + ' ' + s.substring(8,10) + ':00'
|
|
: s;
|
|
document.getElementById('kma-announce-time').textContent = formatted;
|
|
}
|
|
|
|
if (summary) summary.style.display = 'flex';
|
|
|
|
var rows = result.rows || [];
|
|
var tableHead = document.getElementById('kma-table-head');
|
|
var tableBody = document.getElementById('kma-table-body');
|
|
tableHead.innerHTML = '';
|
|
tableBody.innerHTML = '';
|
|
|
|
if (rows.length > 0) {
|
|
var headRow = '<tr>';
|
|
KMA_DISPLAY_COLS.forEach(function(col) {
|
|
headRow += '<th>' + col.label + '</th>';
|
|
});
|
|
headRow += '</tr>';
|
|
tableHead.innerHTML = headRow;
|
|
|
|
rows.forEach(function(row) {
|
|
var tr = '<tr>';
|
|
KMA_DISPLAY_COLS.forEach(function(col) {
|
|
var val = row[col.key];
|
|
if (col.key === 'rnYn' && KMA_RNYN_NAMES[String(val)] !== undefined) {
|
|
val = KMA_RNYN_NAMES[String(val)];
|
|
}
|
|
if (col.key === 'wsIt' && KMA_WSLT_NAMES[String(val)] !== undefined) {
|
|
val = KMA_WSLT_NAMES[String(val)];
|
|
}
|
|
if (col.key === 'numEf') {
|
|
var efNames = {0:'오늘밤', 1:'내일오전', 2:'내일오후', 3:'모레오전', 4:'모레오후', 5:'+3일오전', 6:'+3일오후', 7:'+4일오전', 8:'+4일오후'};
|
|
if (efNames[val] !== undefined) val = efNames[val];
|
|
}
|
|
tr += '<td>' + (val !== null && val !== undefined && val !== '' ? val : '-') + '</td>';
|
|
});
|
|
tr += '</tr>';
|
|
tableBody.innerHTML += tr;
|
|
});
|
|
|
|
dataContainer.style.display = 'block';
|
|
}
|
|
}
|
|
|
|
var btnKmaFetch = document.getElementById('btn-kma-fetch');
|
|
if (btnKmaFetch) {
|
|
btnKmaFetch.addEventListener('click', function() {
|
|
kmaCache = null; // 수동 조회 시 캐시 초기화
|
|
loadKmaForecast();
|
|
});
|
|
}
|
|
|
|
// 초기화
|
|
updateCurrentDateTime();
|
|
initDateSelector();
|
|
|
|
// 초기 페이지 지도 로드
|
|
setTimeout(function() {
|
|
initMap('map-intro', GOHEUNG_LAT, GOHEUNG_LNG, 11);
|
|
}, 200);
|
|
});
|