444 lines
14 KiB
Python
444 lines
14 KiB
Python
import io
|
|
import base64
|
|
import matplotlib
|
|
matplotlib.use('Agg')
|
|
import matplotlib.pyplot as plt
|
|
import numpy as np
|
|
from flask import Blueprint, jsonify
|
|
|
|
bp = Blueprint('flood', __name__, url_prefix='/api/flood')
|
|
|
|
|
|
def generate_plot():
|
|
"""침수 예측 샘플 플롯 생성"""
|
|
fig, ax = plt.subplots(figsize=(10, 6))
|
|
|
|
# 샘플 데이터 (실제 데이터로 교체 필요)
|
|
np.random.seed(123)
|
|
data = np.random.rand(10, 10)
|
|
|
|
im = ax.imshow(data, cmap='Blues', interpolation='nearest')
|
|
ax.set_title('침수 예측 결과', fontsize=14)
|
|
ax.set_xlabel('X 좌표')
|
|
ax.set_ylabel('Y 좌표')
|
|
fig.colorbar(im, ax=ax, label='침수 확률')
|
|
|
|
# 이미지를 base64로 변환
|
|
buf = io.BytesIO()
|
|
fig.savefig(buf, format='png', dpi=100, bbox_inches='tight')
|
|
buf.seek(0)
|
|
image_base64 = base64.b64encode(buf.getvalue()).decode('utf-8')
|
|
plt.close(fig)
|
|
|
|
return image_base64
|
|
|
|
|
|
@bp.route('/predict', methods=['GET'])
|
|
def predict():
|
|
"""침수 예측 결과 반환"""
|
|
try:
|
|
image = generate_plot()
|
|
return jsonify({
|
|
'status': 'success',
|
|
'message': '침수 예측 완료',
|
|
'image': image
|
|
})
|
|
except Exception as e:
|
|
return jsonify({
|
|
'status': 'error',
|
|
'message': str(e),
|
|
'image': None
|
|
})
|
|
|
|
|
|
def generate_simulation_plot():
|
|
"""침수 시뮬레이션 플롯 생성"""
|
|
fig, ax = plt.subplots(figsize=(10, 6))
|
|
|
|
np.random.seed(111)
|
|
time = np.arange(0, 48, 1)
|
|
flood_level = np.sin(time / 6) * 2 + np.random.normal(0, 0.3, 48) + 3
|
|
|
|
ax.plot(time, flood_level, 'b-', linewidth=2, label='침수 수위')
|
|
ax.fill_between(time, 0, flood_level, alpha=0.3, color='blue')
|
|
ax.axhline(y=4, color='red', linestyle='--', label='위험 수위')
|
|
ax.set_title('침수 시뮬레이션 결과', fontsize=14)
|
|
ax.set_xlabel('시간 (시)')
|
|
ax.set_ylabel('수위 (m)')
|
|
ax.legend()
|
|
ax.grid(True, alpha=0.3)
|
|
|
|
buf = io.BytesIO()
|
|
fig.savefig(buf, format='png', dpi=100, bbox_inches='tight')
|
|
buf.seek(0)
|
|
image_base64 = base64.b64encode(buf.getvalue()).decode('utf-8')
|
|
plt.close(fig)
|
|
|
|
return image_base64
|
|
|
|
|
|
def generate_waterlevel_plot():
|
|
"""수위 곡선 플롯 생성"""
|
|
fig, ax = plt.subplots(figsize=(10, 6))
|
|
|
|
np.random.seed(222)
|
|
days = np.arange(1, 31)
|
|
water_level = np.cumsum(np.random.randn(30)) + 50
|
|
|
|
ax.plot(days, water_level, 'g-', linewidth=2, marker='o', markersize=4)
|
|
ax.axhline(y=55, color='orange', linestyle='--', label='경고 수위')
|
|
ax.axhline(y=60, color='red', linestyle='--', label='위험 수위')
|
|
ax.set_title('월간 수위 곡선', fontsize=14)
|
|
ax.set_xlabel('일')
|
|
ax.set_ylabel('수위 (m)')
|
|
ax.legend()
|
|
ax.grid(True, alpha=0.3)
|
|
|
|
buf = io.BytesIO()
|
|
fig.savefig(buf, format='png', dpi=100, bbox_inches='tight')
|
|
buf.seek(0)
|
|
image_base64 = base64.b64encode(buf.getvalue()).decode('utf-8')
|
|
plt.close(fig)
|
|
|
|
return image_base64
|
|
|
|
|
|
@bp.route('/simulation', methods=['GET'])
|
|
def simulation():
|
|
"""침수 시뮬레이션 결과 반환"""
|
|
try:
|
|
image = generate_simulation_plot()
|
|
return jsonify({
|
|
'status': 'success',
|
|
'message': '침수 시뮬레이션 완료',
|
|
'image': image
|
|
})
|
|
except Exception as e:
|
|
return jsonify({
|
|
'status': 'error',
|
|
'message': str(e),
|
|
'image': None
|
|
})
|
|
|
|
|
|
@bp.route('/waterlevel', methods=['GET'])
|
|
def waterlevel():
|
|
"""수위 곡선 결과 반환"""
|
|
try:
|
|
image = generate_waterlevel_plot()
|
|
return jsonify({
|
|
'status': 'success',
|
|
'message': '수위 곡선 조회 완료',
|
|
'image': image
|
|
})
|
|
except Exception as e:
|
|
return jsonify({
|
|
'status': 'error',
|
|
'message': str(e),
|
|
'image': None
|
|
})
|
|
|
|
|
|
def generate_flood_heatmap_image(size=(400, 300)):
|
|
"""침수 히트맵 이미지 생성"""
|
|
fig, ax = plt.subplots(figsize=(size[0]/100, size[1]/100))
|
|
|
|
np.random.seed(82)
|
|
|
|
# 히트맵 데이터 생성 (침수 위험도)
|
|
x = np.linspace(0, 10, 100)
|
|
y = np.linspace(0, 8, 80)
|
|
X, Y = np.meshgrid(x, y)
|
|
|
|
# 다중 가우시안으로 히트맵 생성
|
|
Z = np.zeros_like(X)
|
|
# 중심 핫스팟 (심각 지역)
|
|
Z += 0.85 * np.exp(-((X-4)**2 + (Y-3)**2) / 2.5)
|
|
# 주변 핫스팟들
|
|
Z += 0.65 * np.exp(-((X-7)**2 + (Y-5)**2) / 2)
|
|
Z += 0.55 * np.exp(-((X-2)**2 + (Y-6)**2) / 1.8)
|
|
Z += 0.45 * np.exp(-((X-8)**2 + (Y-2)**2) / 1.5)
|
|
|
|
# 노이즈 추가
|
|
Z += np.random.rand(80, 100) * 0.1
|
|
|
|
# 커스텀 컬러맵 (녹색 -> 노랑 -> 주황 -> 파랑)
|
|
from matplotlib.colors import LinearSegmentedColormap
|
|
colors = ['#27ae60', '#f1c40f', '#3498db', '#2980b9']
|
|
cmap = LinearSegmentedColormap.from_list('flood', colors)
|
|
|
|
ax.imshow(Z, cmap=cmap, aspect='auto', alpha=0.8)
|
|
ax.axis('off')
|
|
|
|
plt.tight_layout(pad=0)
|
|
buf = io.BytesIO()
|
|
fig.savefig(buf, format='png', dpi=100, bbox_inches='tight', pad_inches=0, transparent=True)
|
|
buf.seek(0)
|
|
image_base64 = base64.b64encode(buf.getvalue()).decode('utf-8')
|
|
plt.close(fig)
|
|
|
|
return image_base64
|
|
|
|
|
|
def generate_flood_mini_heatmap_image():
|
|
"""미니 침수 히트맵 이미지 생성"""
|
|
fig, ax = plt.subplots(figsize=(2.6, 1.2))
|
|
|
|
np.random.seed(82)
|
|
|
|
x = np.linspace(0, 5, 50)
|
|
y = np.linspace(0, 3, 30)
|
|
X, Y = np.meshgrid(x, y)
|
|
|
|
Z = 0.75 * np.exp(-((X-2.5)**2 + (Y-1.5)**2) / 1.5)
|
|
Z += np.random.rand(30, 50) * 0.15
|
|
|
|
from matplotlib.colors import LinearSegmentedColormap
|
|
colors = ['#27ae60', '#f1c40f', '#3498db', '#2980b9']
|
|
cmap = LinearSegmentedColormap.from_list('flood', colors)
|
|
|
|
ax.imshow(Z, cmap=cmap, aspect='auto', alpha=0.85)
|
|
ax.axis('off')
|
|
|
|
plt.tight_layout(pad=0)
|
|
buf = io.BytesIO()
|
|
fig.savefig(buf, format='png', dpi=100, bbox_inches='tight', pad_inches=0, transparent=True)
|
|
buf.seek(0)
|
|
image_base64 = base64.b64encode(buf.getvalue()).decode('utf-8')
|
|
plt.close(fig)
|
|
|
|
return image_base64
|
|
|
|
|
|
def generate_monthly_rainfall_chart():
|
|
"""월별 강우량 차트 생성"""
|
|
fig, ax = plt.subplots(figsize=(4, 1.2))
|
|
|
|
np.random.seed(2025)
|
|
months = np.arange(1, 21)
|
|
|
|
# 2025년 데이터
|
|
rainfall_2025 = 45 + np.sin(months / 3) * 15 + np.random.rand(20) * 10
|
|
# 2024년 데이터
|
|
rainfall_2024 = 40 + np.sin(months / 3 + 0.5) * 12 + np.random.rand(20) * 8
|
|
|
|
ax.plot(months, rainfall_2025, 'b-', linewidth=1.5, label='2025')
|
|
ax.plot(months, rainfall_2024, color='#3498db', linewidth=1.5, linestyle='--', label='2024')
|
|
|
|
ax.fill_between(months, rainfall_2025, alpha=0.2, color='blue')
|
|
|
|
ax.set_xlim(1, 20)
|
|
ax.set_ylim(20, 80)
|
|
ax.set_xticks([1, 8, 9, 12, 15, 20])
|
|
ax.set_xticklabels(['1', '8', '9', '12', '15', '20'], fontsize=7)
|
|
ax.tick_params(axis='y', labelsize=7)
|
|
ax.grid(True, alpha=0.3)
|
|
|
|
plt.tight_layout(pad=0.5)
|
|
buf = io.BytesIO()
|
|
fig.savefig(buf, format='png', dpi=100, bbox_inches='tight', transparent=True)
|
|
buf.seek(0)
|
|
image_base64 = base64.b64encode(buf.getvalue()).decode('utf-8')
|
|
plt.close(fig)
|
|
|
|
return image_base64
|
|
|
|
|
|
@bp.route('/monitoring-heatmap', methods=['GET'])
|
|
def monitoring_heatmap():
|
|
"""침수 모니터링 히트맵 이미지 반환"""
|
|
try:
|
|
heatmap_image = generate_flood_heatmap_image()
|
|
mini_heatmap_image = generate_flood_mini_heatmap_image()
|
|
|
|
return jsonify({
|
|
'status': 'success',
|
|
'heatmap_image': heatmap_image,
|
|
'mini_heatmap_image': mini_heatmap_image,
|
|
'risk_score': 65,
|
|
'risk_level': '경고'
|
|
})
|
|
except Exception as e:
|
|
return jsonify({
|
|
'status': 'error',
|
|
'message': str(e)
|
|
})
|
|
|
|
|
|
@bp.route('/monthly-chart', methods=['GET'])
|
|
def monthly_chart():
|
|
"""월별 강우량 차트 반환"""
|
|
try:
|
|
image = generate_monthly_rainfall_chart()
|
|
return jsonify({
|
|
'status': 'success',
|
|
'image': image
|
|
})
|
|
except Exception as e:
|
|
return jsonify({
|
|
'status': 'error',
|
|
'message': str(e),
|
|
'image': None
|
|
})
|
|
|
|
|
|
def generate_hourly_waterlevel_chart(period='day'):
|
|
"""시간별 수위 변화 차트 생성"""
|
|
fig, ax = plt.subplots(figsize=(12, 5))
|
|
|
|
np.random.seed(2025)
|
|
|
|
if period == 'day':
|
|
x = np.arange(0, 24)
|
|
x_labels = [f'{i}시' for i in range(24)]
|
|
elif period == 'week':
|
|
x = np.arange(0, 7*24, 6)
|
|
x_labels = ['월', '화', '수', '목', '금', '토', '일']
|
|
else:
|
|
x = np.arange(0, 30)
|
|
x_labels = [f'{i+1}일' for i in range(30)]
|
|
|
|
base_level = 2.0 + np.sin(x / 6) * 0.5
|
|
water_level = base_level + np.random.normal(0, 0.2, len(x))
|
|
water_level = np.maximum(water_level, 0.5)
|
|
|
|
ax.plot(x, water_level, 'b-', linewidth=2, marker='o', markersize=4, label='실측 수위')
|
|
ax.fill_between(x, 0, water_level, alpha=0.3, color='blue')
|
|
ax.axhline(y=3.5, color='orange', linestyle='--', linewidth=2, label='주의 수위')
|
|
ax.axhline(y=4.0, color='red', linestyle='--', linewidth=2, label='경고 수위')
|
|
|
|
ax.set_xlabel('시간' if period == 'day' else '기간', fontsize=11)
|
|
ax.set_ylabel('수위 (m)', fontsize=11)
|
|
ax.set_title('시간별 수위 변화', fontsize=14, fontweight='bold')
|
|
ax.legend(loc='upper right')
|
|
ax.grid(True, alpha=0.3)
|
|
ax.set_ylim(0, 5)
|
|
|
|
plt.tight_layout()
|
|
buf = io.BytesIO()
|
|
fig.savefig(buf, format='png', dpi=100, bbox_inches='tight')
|
|
buf.seek(0)
|
|
image_base64 = base64.b64encode(buf.getvalue()).decode('utf-8')
|
|
plt.close(fig)
|
|
|
|
return image_base64, {
|
|
'current': f'{water_level[-1]:.1f}m',
|
|
'avg': f'{np.mean(water_level):.1f}m',
|
|
'max': f'{np.max(water_level):.1f}m'
|
|
}
|
|
|
|
|
|
def generate_reservoir_chart(period='day'):
|
|
"""저수율 변화 차트 생성"""
|
|
fig, ax = plt.subplots(figsize=(12, 5))
|
|
|
|
np.random.seed(2026)
|
|
|
|
if period == 'day':
|
|
x = np.arange(0, 24)
|
|
elif period == 'week':
|
|
x = np.arange(0, 7)
|
|
else:
|
|
x = np.arange(0, 30)
|
|
|
|
base_rate = 65 - np.cumsum(np.random.uniform(0.1, 0.5, len(x)))
|
|
reservoir_rate = np.maximum(base_rate, 30)
|
|
|
|
ax.plot(x, reservoir_rate, 'g-', linewidth=2, marker='s', markersize=5, label='저수율')
|
|
ax.fill_between(x, 0, reservoir_rate, alpha=0.3, color='green')
|
|
ax.axhline(y=50, color='orange', linestyle='--', linewidth=2, label='주의 수준')
|
|
ax.axhline(y=30, color='red', linestyle='--', linewidth=2, label='위험 수준')
|
|
|
|
ax.set_xlabel('시간' if period == 'day' else '기간', fontsize=11)
|
|
ax.set_ylabel('저수율 (%)', fontsize=11)
|
|
ax.set_title('저수율 변화 추이', fontsize=14, fontweight='bold')
|
|
ax.legend(loc='upper right')
|
|
ax.grid(True, alpha=0.3)
|
|
ax.set_ylim(0, 100)
|
|
|
|
plt.tight_layout()
|
|
buf = io.BytesIO()
|
|
fig.savefig(buf, format='png', dpi=100, bbox_inches='tight')
|
|
buf.seek(0)
|
|
image_base64 = base64.b64encode(buf.getvalue()).decode('utf-8')
|
|
plt.close(fig)
|
|
|
|
return image_base64, {
|
|
'current': f'{reservoir_rate[-1]:.1f}%',
|
|
'avg': f'{np.mean(reservoir_rate):.1f}%',
|
|
'max': f'{np.max(reservoir_rate):.1f}%'
|
|
}
|
|
|
|
|
|
def generate_river_level_chart(period='day'):
|
|
"""하천 수위 곡선 차트 생성"""
|
|
fig, ax = plt.subplots(figsize=(12, 5))
|
|
|
|
np.random.seed(2027)
|
|
|
|
if period == 'day':
|
|
x = np.arange(0, 24)
|
|
elif period == 'week':
|
|
x = np.arange(0, 7*24, 6)
|
|
else:
|
|
x = np.arange(0, 30)
|
|
|
|
river_level = 1.5 + np.sin(x / 8) * 0.8 + np.random.normal(0, 0.15, len(x))
|
|
river_level = np.maximum(river_level, 0.3)
|
|
|
|
ax.plot(x, river_level, 'c-', linewidth=2, marker='d', markersize=4, label='하천 수위')
|
|
ax.fill_between(x, 0, river_level, alpha=0.3, color='cyan')
|
|
ax.axhline(y=2.5, color='orange', linestyle='--', linewidth=2, label='범람 주의')
|
|
ax.axhline(y=3.0, color='red', linestyle='--', linewidth=2, label='범람 경고')
|
|
|
|
ax.set_xlabel('시간' if period == 'day' else '기간', fontsize=11)
|
|
ax.set_ylabel('수위 (m)', fontsize=11)
|
|
ax.set_title('하천 수위 변화', fontsize=14, fontweight='bold')
|
|
ax.legend(loc='upper right')
|
|
ax.grid(True, alpha=0.3)
|
|
ax.set_ylim(0, 4)
|
|
|
|
plt.tight_layout()
|
|
buf = io.BytesIO()
|
|
fig.savefig(buf, format='png', dpi=100, bbox_inches='tight')
|
|
buf.seek(0)
|
|
image_base64 = base64.b64encode(buf.getvalue()).decode('utf-8')
|
|
plt.close(fig)
|
|
|
|
return image_base64, {
|
|
'current': f'{river_level[-1]:.1f}m',
|
|
'avg': f'{np.mean(river_level):.1f}m',
|
|
'max': f'{np.max(river_level):.1f}m'
|
|
}
|
|
|
|
|
|
@bp.route('/graph', methods=['GET'])
|
|
def graph():
|
|
"""수위 곡선 그래프 반환"""
|
|
try:
|
|
from flask import request
|
|
graph_type = request.args.get('type', 'hourly')
|
|
period = request.args.get('period', 'day')
|
|
|
|
if graph_type == 'hourly':
|
|
image, stats = generate_hourly_waterlevel_chart(period)
|
|
elif graph_type == 'reservoir':
|
|
image, stats = generate_reservoir_chart(period)
|
|
elif graph_type == 'river':
|
|
image, stats = generate_river_level_chart(period)
|
|
else:
|
|
image, stats = generate_hourly_waterlevel_chart(period)
|
|
|
|
return jsonify({
|
|
'status': 'success',
|
|
'image': image,
|
|
'stats': stats,
|
|
'warning_level': '4.0m'
|
|
})
|
|
except Exception as e:
|
|
return jsonify({
|
|
'status': 'error',
|
|
'message': str(e),
|
|
'image': None
|
|
})
|