[예측] - OpenDrift Python API 서버 및 스크립트 추가 (prediction/opendrift/) - 시뮬레이션 상태 폴링 훅(useSimulationStatus), 로딩 오버레이 추가 - HydrParticleOverlay: deck.gl 기반 입자 궤적 시각화 레이어 - OilSpillView/LeftPanel/RightPanel: 시뮬레이션 실행·결과 표시 UI 개편 - predictionService/predictionRouter: 시뮬레이션 CRUD 및 상태 관리 API - simulation.ts: OpenDrift 연동 엔드포인트 확장 - docs/PREDICTION-GUIDE.md: 예측 기능 개발 가이드 추가 [CCTV/항공방제] - CCTV 오일 감지 GPU 추론 연동 (OilDetectionOverlay, useOilDetection) - CCTV 안전관리 감지 기능 추가 (선박 출입, 침입 감지) - oil_inference_server.py: Python GPU 추론 서버 [관리자] - 관리자 화면 고도화 (사용자/권한/게시판/선박신호 패널) - AdminSidebar, BoardMgmtPanel, VesselSignalPanel 신규 컴포넌트 [기타] - DB: 시뮬레이션 결과, 선박보험 시드(1391건), 역할 정리 마이그레이션 - 팀 워크플로우 v1.6.1 동기화 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
165 lines
5.1 KiB
Python
165 lines
5.1 KiB
Python
import numpy as np
|
|
import xarray as xr
|
|
from datetime import datetime
|
|
import pandas as pd
|
|
|
|
from logger import get_logger
|
|
from utils import haversine_distance, find_time_index, convert_and_round
|
|
|
|
logger = get_logger("extractUvWithinBox")
|
|
|
|
|
|
def _compute_hydr_region(ocean_ds: xr.Dataset, center_lon: float, center_lat: float,
|
|
box_size_km: float = 25) -> dict:
|
|
"""
|
|
해양 데이터셋에서 중심점 기준 공간 영역을 계산합니다. 시뮬레이션당 1회 호출.
|
|
|
|
Parameters
|
|
----------
|
|
ocean_ds : xr.Dataset
|
|
이미 열린 해양 NetCDF 데이터셋
|
|
center_lon, center_lat : float
|
|
중심점 경도, 위도
|
|
box_size_km : float
|
|
상하좌우 범위 (km)
|
|
|
|
Returns
|
|
-------
|
|
dict
|
|
min_row, max_row, min_col, max_col, lon_region, lat_region,
|
|
lon_intervals, lat_intervals, bound_lon_lat, u_ndim
|
|
"""
|
|
lon = ocean_ds['lon'].values
|
|
lat = ocean_ds['lat'].values
|
|
|
|
if lon.ndim == 1 and lat.ndim == 1:
|
|
lon_2d, lat_2d = np.meshgrid(lon, lat)
|
|
else:
|
|
lon_2d = lon
|
|
lat_2d = lat
|
|
|
|
# 동서/남북 거리 계산
|
|
dx = haversine_distance(center_lon, center_lat, lon_2d, center_lat, return_km=True)
|
|
dx = dx * np.sign(lon_2d - center_lon)
|
|
dy = haversine_distance(center_lon, center_lat, center_lon, lat_2d, return_km=True)
|
|
dy = dy * np.sign(lat_2d - center_lat)
|
|
|
|
mask = (np.abs(dx) <= box_size_km) & (np.abs(dy) <= box_size_km)
|
|
|
|
rows, cols = np.where(mask)
|
|
if len(rows) == 0:
|
|
raise ValueError("No data within specified range")
|
|
|
|
min_row, max_row = int(rows.min()), int(rows.max())
|
|
min_col, max_col = int(cols.min()), int(cols.max())
|
|
|
|
lon_region = lon_2d[min_row:max_row + 1, min_col:max_col + 1]
|
|
lat_region = lat_2d[min_row:max_row + 1, min_col:max_col + 1]
|
|
|
|
lon_intervals = [float(lon_region[0, i + 1] - lon_region[0, i])
|
|
for i in range(lon_region.shape[1] - 1)]
|
|
lat_intervals = [float(lat_region[i + 1, 0] - lat_region[i, 0])
|
|
for i in range(lat_region.shape[0] - 1)]
|
|
|
|
bound_lon_lat = {
|
|
"top": float(lat_region.max()),
|
|
"bottom": float(lat_region.min()),
|
|
"left": float(lon_region.min()),
|
|
"right": float(lon_region.max()),
|
|
}
|
|
|
|
# u 변수 차원 수 미리 파악
|
|
u_data = ocean_ds.get('u', ocean_ds.get('ssu'))
|
|
u_ndim = u_data.ndim
|
|
|
|
return {
|
|
"min_row": min_row, "max_row": max_row,
|
|
"min_col": min_col, "max_col": max_col,
|
|
"rows": int(max_row - min_row + 1),
|
|
"cols": int(max_col - min_col + 1),
|
|
"lon_intervals": lon_intervals,
|
|
"lat_intervals": lat_intervals,
|
|
"bound_lon_lat": bound_lon_lat,
|
|
"u_ndim": u_ndim,
|
|
}
|
|
|
|
|
|
def _extract_uv_at_time(ocean_ds: xr.Dataset, time_idx: int, region: dict) -> dict:
|
|
"""
|
|
사전 계산된 공간 영역에서 특정 timestep의 u/v 데이터를 추출합니다.
|
|
|
|
Parameters
|
|
----------
|
|
ocean_ds : xr.Dataset
|
|
이미 열린 해양 NetCDF 데이터셋
|
|
time_idx : int
|
|
시간 인덱스
|
|
region : dict
|
|
_compute_hydr_region()이 반환한 영역 정보
|
|
|
|
Returns
|
|
-------
|
|
dict
|
|
{"value": [u_list, v_list], "grid": {...}}
|
|
"""
|
|
u_data = ocean_ds.get('u', ocean_ds.get('ssu'))
|
|
v_data = ocean_ds.get('v', ocean_ds.get('ssv'))
|
|
|
|
u_ndim = region["u_ndim"]
|
|
if u_ndim == 3:
|
|
u_2d = u_data[time_idx].values
|
|
v_2d = v_data[time_idx].values
|
|
elif u_ndim == 4:
|
|
u_2d = u_data[time_idx, 0].values
|
|
v_2d = v_data[time_idx, 0].values
|
|
else:
|
|
u_2d = u_data.values
|
|
v_2d = v_data.values
|
|
|
|
r = region
|
|
u_region = u_2d[r["min_row"]:r["max_row"] + 1, r["min_col"]:r["max_col"] + 1]
|
|
v_region = v_2d[r["min_row"]:r["max_row"] + 1, r["min_col"]:r["max_col"] + 1]
|
|
|
|
land_mask = (u_region == 0) & (v_region == 0)
|
|
u_list = convert_and_round(u_region, land_mask)
|
|
v_list = convert_and_round(v_region, land_mask)
|
|
|
|
return {
|
|
"value": [u_list, v_list],
|
|
"grid": {
|
|
"lonInterval": r["lon_intervals"],
|
|
"boundLonLat": r["bound_lon_lat"],
|
|
"rows": r["rows"],
|
|
"cols": r["cols"],
|
|
"latInterval": r["lat_intervals"],
|
|
},
|
|
}
|
|
|
|
|
|
def extract_uv_within_box(nc_file, center_lon, center_lat, target_time, box_size_km=25):
|
|
"""
|
|
선택한 포인트와 시간으로부터 상하좌우 정사각형 범위 내의 u, v 데이터 추출
|
|
|
|
Parameters
|
|
----------
|
|
nc_file : str
|
|
NetCDF 파일 경로
|
|
center_lon : float
|
|
중심점 경도
|
|
center_lat : float
|
|
중심점 위도
|
|
target_time : str or datetime
|
|
목표 시간 (예: '2024-01-15 12:00:00')
|
|
box_size_km : float
|
|
상하좌우 범위 (km), 기본값 25km
|
|
|
|
Returns
|
|
-------
|
|
result : dict
|
|
추출된 데이터를 담은 딕셔너리
|
|
"""
|
|
with xr.open_dataset(nc_file) as ds:
|
|
region = _compute_hydr_region(ds, center_lon, center_lat, box_size_km)
|
|
time_idx, _ = find_time_index(ds, target_time)
|
|
return _extract_uv_at_time(ds, time_idx, region)
|