285 lines
18 KiB
Python
285 lines
18 KiB
Python
"""
|
|
config_system.py
|
|
백엔드 작동 제어 및 알고리즘 파라미터
|
|
|
|
모든 제어 변수는 여기서 관리. 코드 내 하드코딩 금지.
|
|
"""
|
|
|
|
import os
|
|
|
|
from dotenv import load_dotenv
|
|
|
|
load_dotenv()
|
|
os.environ["PYTHONUTF8"] = "1"
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
# 1. FastAPI 서버
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
SERVER_HOST = os.getenv("SERVER_HOST", "0.0.0.0")
|
|
SERVER_PORT = int(os.getenv("SERVER_PORT", "8000"))
|
|
DEBUG = os.getenv("DEBUG", "False").lower() == "true"
|
|
|
|
# 정적 파일 서빙 (프론트엔드 빌드 결과)
|
|
STATIC_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "node_modules", ".build")
|
|
STATIC_URL = "/static"
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
# 2. 로깅
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
# 3. 데이터베이스 (MariaDB)
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
DB_HOST = os.getenv("DB_HOST", "localhost")
|
|
DB_PORT = int(os.getenv("DB_PORT", "3306"))
|
|
DB_NAME = os.getenv("DB_NAME", "aislo_db")
|
|
DB_USER = os.getenv("DB_USER", "aislo")
|
|
DB_PASSWORD = os.getenv("DB_PASSWORD", "aislo")
|
|
|
|
# asyncmy 연결 풀 설정
|
|
DB_POOL_MIN = int(os.getenv("DB_POOL_MIN", "5"))
|
|
DB_POOL_MAX = int(os.getenv("DB_POOL_MAX", "20"))
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
# 4. 파일 업로드 제한
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
UPLOAD_MAX_MB = int(os.getenv("UPLOAD_MAX_MB", "500"))
|
|
UPLOAD_MAX_FILES = int(os.getenv("UPLOAD_MAX_FILES", "20"))
|
|
UPLOAD_CHUNK_SIZE_BYTES = int(os.getenv("UPLOAD_CHUNK_SIZE_BYTES", str(1024 * 1024)))
|
|
UPLOAD_ALLOWED_EXT = [".las", ".laz", ".dem", ".tif", ".tiff", ".tfw", ".prj", ".dxf", ".dwg"]
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
# 5. 지형 분석 알고리즘 파라미터
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
# Trimesh 메쉬 생성
|
|
MESH_GRID_SIZE = float(os.getenv("MESH_GRID_SIZE", "1.0")) # 미터 단위
|
|
MESH_SMOOTHING_ITERATIONS = int(os.getenv("MESH_SMOOTHING_ITERATIONS", "0"))
|
|
SURFACE_LAS_CHUNK_SIZE = int(os.getenv("SURFACE_LAS_CHUNK_SIZE", "500000"))
|
|
SURFACE_DEFAULT_RGB_VALUE = int(os.getenv("SURFACE_DEFAULT_RGB_VALUE", "128"))
|
|
SURFACE_GRID_CELL_SIZE_M = float(os.getenv("SURFACE_GRID_CELL_SIZE_M", "2.0"))
|
|
SURFACE_GRID_HEIGHT_THRESHOLD_M = float(os.getenv("SURFACE_GRID_HEIGHT_THRESHOLD_M", "1.5"))
|
|
|
|
# CSF (Cloth Simulation Filter) 지면 분류 파라미터
|
|
SURFACE_CSF_CLOTH_RESOLUTION_M = float(os.getenv("SURFACE_CSF_CLOTH_RESOLUTION_M", "1.5"))
|
|
SURFACE_CSF_RIGIDNESS = int(os.getenv("SURFACE_CSF_RIGIDNESS", "1"))
|
|
SURFACE_CSF_TIME_STEP = float(os.getenv("SURFACE_CSF_TIME_STEP", "0.65"))
|
|
SURFACE_CSF_CLASS_THRESHOLD_M = float(os.getenv("SURFACE_CSF_CLASS_THRESHOLD_M", "0.5"))
|
|
SURFACE_CSF_ITERATIONS = int(os.getenv("SURFACE_CSF_ITERATIONS", "150"))
|
|
SURFACE_CSF_SLOPE_SMOOTH = os.getenv("SURFACE_CSF_SLOPE_SMOOTH", "True").lower() == "true"
|
|
SURFACE_CSF_SLOPE_SMOOTH_THRESHOLD_M = float(
|
|
os.getenv("SURFACE_CSF_SLOPE_SMOOTH_THRESHOLD_M", "1.8")
|
|
)
|
|
|
|
# PMF (Progressive Morphological Filter) 지면 분류 파라미터
|
|
SURFACE_PMF_CELL_SIZE_M = float(os.getenv("SURFACE_PMF_CELL_SIZE_M", "2.0"))
|
|
SURFACE_PMF_MAX_WINDOW_SIZE = int(os.getenv("SURFACE_PMF_MAX_WINDOW_SIZE", "40"))
|
|
SURFACE_PMF_INITIAL_WINDOW_SIZE = int(os.getenv("SURFACE_PMF_INITIAL_WINDOW_SIZE", "3"))
|
|
SURFACE_PMF_SLOPE = float(os.getenv("SURFACE_PMF_SLOPE", "1.0"))
|
|
SURFACE_PMF_MAX_DISTANCE_M = float(os.getenv("SURFACE_PMF_MAX_DISTANCE_M", "2.5"))
|
|
|
|
# RANSAC (Local plane fitting) 지면 분류 파라미터
|
|
SURFACE_RANSAC_DISTANCE_THRESHOLD_M = float(os.getenv("SURFACE_RANSAC_DISTANCE_THRESHOLD_M", "0.3"))
|
|
SURFACE_RANSAC_N = int(os.getenv("SURFACE_RANSAC_N", "3"))
|
|
SURFACE_RANSAC_ITERATIONS = int(os.getenv("SURFACE_RANSAC_ITERATIONS", "100"))
|
|
SURFACE_RANSAC_LOCAL_GRID_SIZE_M = float(os.getenv("SURFACE_RANSAC_LOCAL_GRID_SIZE_M", "10.0"))
|
|
SURFACE_RANSAC_SEED = int(os.getenv("SURFACE_RANSAC_SEED", "42"))
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
# 5-2. 지표면 모델 생성 파라미터 (TIN/DTM/NURBS/implicit/meshfree)
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
# 빌드 대상 지면 필터·표현 방식
|
|
SURFACE_MODEL_SOURCE_FILTERS = tuple(
|
|
os.getenv("SURFACE_MODEL_SOURCE_FILTERS", "grid_min_z,csf,pmf").split(",")
|
|
)
|
|
SURFACE_MODEL_PRECOMPUTE = tuple(
|
|
os.getenv("SURFACE_MODEL_PRECOMPUTE", "tin,dtm,nurbs,implicit,meshfree").split(",")
|
|
)
|
|
SURFACE_MODEL_SMOOTHING_METHODS = tuple(
|
|
os.getenv("SURFACE_MODEL_SMOOTHING_METHODS", "dtm,tin").split(",")
|
|
)
|
|
SURFACE_MODEL_SYNC_TIMEOUT_SECONDS = int(os.getenv("SURFACE_MODEL_SYNC_TIMEOUT_SECONDS", "0"))
|
|
|
|
# footprint(외곽) 산출
|
|
SURFACE_FOOTPRINT_RESOLUTION_M = float(os.getenv("SURFACE_FOOTPRINT_RESOLUTION_M", "1.0"))
|
|
SURFACE_FOOTPRINT_GAP_CLOSE_M = float(os.getenv("SURFACE_FOOTPRINT_GAP_CLOSE_M", "1.0"))
|
|
SURFACE_BOUNDARY_INSET_M = float(os.getenv("SURFACE_BOUNDARY_INSET_M", "1.0"))
|
|
SURFACE_KEEP_LARGEST_FOOTPRINT = (
|
|
os.getenv("SURFACE_KEEP_LARGEST_FOOTPRINT", "True").lower() == "true"
|
|
)
|
|
SURFACE_TILE_SIZE_M = float(os.getenv("SURFACE_TILE_SIZE_M", "50.0"))
|
|
SURFACE_MAX_PREVIEW_VERTICES = int(os.getenv("SURFACE_MAX_PREVIEW_VERTICES", "120000"))
|
|
|
|
# 표현별 파라미터
|
|
SURFACE_TIN_MAX_INPUT_POINTS = int(os.getenv("SURFACE_TIN_MAX_INPUT_POINTS", "200000"))
|
|
SURFACE_DTM_GRID_RESOLUTION_M = float(os.getenv("SURFACE_DTM_GRID_RESOLUTION_M", "1.0"))
|
|
SURFACE_NURBS_DEGREE = int(os.getenv("SURFACE_NURBS_DEGREE", "3"))
|
|
SURFACE_NURBS_PATCH_SIZE_M = float(os.getenv("SURFACE_NURBS_PATCH_SIZE_M", "50.0"))
|
|
SURFACE_NURBS_CONTROL_POINTS_PER_AXIS = int(
|
|
os.getenv("SURFACE_NURBS_CONTROL_POINTS_PER_AXIS", "16")
|
|
)
|
|
SURFACE_IMPLICIT_MAX_POINTS_PER_TILE = int(
|
|
os.getenv("SURFACE_IMPLICIT_MAX_POINTS_PER_TILE", "20000")
|
|
)
|
|
SURFACE_IMPLICIT_SMOOTHING = float(os.getenv("SURFACE_IMPLICIT_SMOOTHING", "0.5"))
|
|
SURFACE_MESHFREE_MAX_MODEL_POINTS = int(os.getenv("SURFACE_MESHFREE_MAX_MODEL_POINTS", "300000"))
|
|
SURFACE_MESHFREE_POINT_RADIUS_M = float(os.getenv("SURFACE_MESHFREE_POINT_RADIUS_M", "0.5"))
|
|
|
|
# 스무딩 파라미터
|
|
SURFACE_SMOOTHING_DTM_SIGMA_M = float(os.getenv("SURFACE_SMOOTHING_DTM_SIGMA_M", "0.5"))
|
|
SURFACE_SMOOTHING_DTM_SPLINE_SMOOTH = float(os.getenv("SURFACE_SMOOTHING_DTM_SPLINE_SMOOTH", "0.0"))
|
|
SURFACE_SMOOTHING_DTM_PREVIEW_RESOLUTION_M = float(
|
|
os.getenv("SURFACE_SMOOTHING_DTM_PREVIEW_RESOLUTION_M", "0.5")
|
|
)
|
|
SURFACE_SMOOTHING_TIN_TAUBIN_ITERATIONS = int(
|
|
os.getenv("SURFACE_SMOOTHING_TIN_TAUBIN_ITERATIONS", "10")
|
|
)
|
|
SURFACE_SMOOTHING_TIN_TAUBIN_LAMBDA = float(os.getenv("SURFACE_SMOOTHING_TIN_TAUBIN_LAMBDA", "0.5"))
|
|
SURFACE_SMOOTHING_TIN_TAUBIN_MU = float(os.getenv("SURFACE_SMOOTHING_TIN_TAUBIN_MU", "-0.53"))
|
|
|
|
# 등고선 파라미터
|
|
SURFACE_CONTOUR_INTERVAL_M = float(os.getenv("SURFACE_CONTOUR_INTERVAL_M", "5.0"))
|
|
SURFACE_CONTOUR_GRID_RESOLUTION_M = float(os.getenv("SURFACE_CONTOUR_GRID_RESOLUTION_M", "1.0"))
|
|
|
|
|
|
def build_surface_model_config() -> dict:
|
|
"""지표면 모델 파이프라인이 사용하는 config dict를 조립한다."""
|
|
return {
|
|
"source_filters": list(SURFACE_MODEL_SOURCE_FILTERS),
|
|
"precompute": list(SURFACE_MODEL_PRECOMPUTE),
|
|
"smoothing_methods": tuple(SURFACE_MODEL_SMOOTHING_METHODS),
|
|
"sync_timeout_seconds": SURFACE_MODEL_SYNC_TIMEOUT_SECONDS,
|
|
"footprint_resolution_meters": SURFACE_FOOTPRINT_RESOLUTION_M,
|
|
"footprint_gap_close_meters": SURFACE_FOOTPRINT_GAP_CLOSE_M,
|
|
"boundary_inset_meters": SURFACE_BOUNDARY_INSET_M,
|
|
"keep_largest_footprint": SURFACE_KEEP_LARGEST_FOOTPRINT,
|
|
"tile_size_meters": SURFACE_TILE_SIZE_M,
|
|
"max_preview_vertices": SURFACE_MAX_PREVIEW_VERTICES,
|
|
"tin_max_input_points": SURFACE_TIN_MAX_INPUT_POINTS,
|
|
"dtm_grid_resolution_meters": SURFACE_DTM_GRID_RESOLUTION_M,
|
|
"nurbs_degree": SURFACE_NURBS_DEGREE,
|
|
"nurbs_patch_size_meters": SURFACE_NURBS_PATCH_SIZE_M,
|
|
"nurbs_control_points_per_axis": SURFACE_NURBS_CONTROL_POINTS_PER_AXIS,
|
|
"implicit_max_points_per_tile": SURFACE_IMPLICIT_MAX_POINTS_PER_TILE,
|
|
"implicit_smoothing": SURFACE_IMPLICIT_SMOOTHING,
|
|
"meshfree_max_model_points": SURFACE_MESHFREE_MAX_MODEL_POINTS,
|
|
"meshfree_point_radius_meters": SURFACE_MESHFREE_POINT_RADIUS_M,
|
|
"smoothing_dtm_sigma_meters": SURFACE_SMOOTHING_DTM_SIGMA_M,
|
|
"smoothing_dtm_spline_smooth": SURFACE_SMOOTHING_DTM_SPLINE_SMOOTH,
|
|
"smoothing_dtm_preview_resolution_meters": SURFACE_SMOOTHING_DTM_PREVIEW_RESOLUTION_M,
|
|
"smoothing_tin_taubin_iterations": SURFACE_SMOOTHING_TIN_TAUBIN_ITERATIONS,
|
|
"smoothing_tin_taubin_lambda": SURFACE_SMOOTHING_TIN_TAUBIN_LAMBDA,
|
|
"smoothing_tin_taubin_mu": SURFACE_SMOOTHING_TIN_TAUBIN_MU,
|
|
"contour_interval_meters": SURFACE_CONTOUR_INTERVAL_M,
|
|
"contour_grid_resolution_meters": SURFACE_CONTOUR_GRID_RESOLUTION_M,
|
|
}
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
# 5-3. 경로 설계 파라미터 (B05 WF2)
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
# 비용 함수 가중치
|
|
ROUTE_W_DIST = float(os.getenv("ROUTE_W_DIST", "1.0"))
|
|
ROUTE_W_GRADE = float(os.getenv("ROUTE_W_GRADE", "2.0"))
|
|
ROUTE_W_SIDE = float(os.getenv("ROUTE_W_SIDE", "1.5"))
|
|
ROUTE_W_CURVE = float(os.getenv("ROUTE_W_CURVE", "0.5"))
|
|
ROUTE_W_AVOID = float(os.getenv("ROUTE_W_AVOID", "10.0"))
|
|
ROUTE_WEIGHT_MAX = float(os.getenv("ROUTE_WEIGHT_MAX", "1000.0"))
|
|
|
|
# 격자·경사·비용 제약
|
|
ROUTE_MAX_GRADE = float(os.getenv("ROUTE_MAX_GRADE", "0.14"))
|
|
ROUTE_MAX_GRADE_PAVED = float(os.getenv("ROUTE_MAX_GRADE_PAVED", "0.18"))
|
|
ROUTE_GRID_RES_M = float(os.getenv("ROUTE_GRID_RES_M", "2.0"))
|
|
ROUTE_AVOID_DEFAULT_RADIUS_M = float(os.getenv("ROUTE_AVOID_DEFAULT_RADIUS_M", "25.0"))
|
|
ROUTE_DEFAULT_GRADE_CLASS = os.getenv("ROUTE_DEFAULT_GRADE_CLASS", "trunk")
|
|
ROUTE_MAX_COST_CELLS = int(os.getenv("ROUTE_MAX_COST_CELLS", "4000000"))
|
|
ROUTE_REQUIRED_POINT_TOLERANCE_M = float(os.getenv("ROUTE_REQUIRED_POINT_TOLERANCE_M", "1.0"))
|
|
ROUTE_GRADE_CLASSES = ("trunk", "branch", "work")
|
|
|
|
# 임도 등급별 종단경사 한계 및 최소 곡선반지름
|
|
FOREST_ROAD_MAX_GRADE = {"trunk": 0.26, "branch": 0.28, "work": 0.40}
|
|
FOREST_ROAD_MIN_CURVE_R_M = {"trunk": 12.0, "branch": 10.0, "work": 6.0}
|
|
|
|
# 대안(정속경사) 파라미터
|
|
ROUTE_ALT_MIN_GRADE = float(os.getenv("ROUTE_ALT_MIN_GRADE", "0.08"))
|
|
ROUTE_ALT_MAX_GRADE = float(os.getenv("ROUTE_ALT_MAX_GRADE", "0.14"))
|
|
ROUTE_ALT_GRADE_TOLERANCE = float(os.getenv("ROUTE_ALT_GRADE_TOLERANCE", "0.005"))
|
|
|
|
# 지형 skeleton (능선/계곡) 파라미터
|
|
SKELETON_VALLEY_ACC_THRESHOLD_CELLS = int(os.getenv("SKELETON_VALLEY_ACC_THRESHOLD_CELLS", "500"))
|
|
SKELETON_MAIN_VALLEY_ACC_THRESHOLD_CELLS = int(
|
|
os.getenv("SKELETON_MAIN_VALLEY_ACC_THRESHOLD_CELLS", "5000")
|
|
)
|
|
SKELETON_RIDGE_ACC_THRESHOLD_CELLS = int(os.getenv("SKELETON_RIDGE_ACC_THRESHOLD_CELLS", "500"))
|
|
SKELETON_MAIN_RIDGE_ACC_THRESHOLD_CELLS = int(
|
|
os.getenv("SKELETON_MAIN_RIDGE_ACC_THRESHOLD_CELLS", "5000")
|
|
)
|
|
SKELETON_NODE_SPACING_M = float(os.getenv("SKELETON_NODE_SPACING_M", "10.0"))
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
# 5-4. 종횡단 생성 파라미터 (B06 WF3)
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
SECTION_STATION_INTERVAL_M = float(os.getenv("SECTION_STATION_INTERVAL_M", "20.0"))
|
|
SECTION_CROSS_HALF_WIDTH_M = float(os.getenv("SECTION_CROSS_HALF_WIDTH_M", "15.0"))
|
|
SECTION_CROSS_SAMPLE_INTERVAL_M = float(os.getenv("SECTION_CROSS_SAMPLE_INTERVAL_M", "0.5"))
|
|
SECTION_LONG_SAMPLE_INTERVAL_M = float(os.getenv("SECTION_LONG_SAMPLE_INTERVAL_M", "1.0"))
|
|
SECTION_INCLUDE_ENDPOINT = os.getenv("SECTION_INCLUDE_ENDPOINT", "True").lower() == "true"
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
# 6. 저장소 경로
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
STORAGE_BASE_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "storage")
|
|
PROJECT_STORAGE_STAGE_DIRS = frozenset(
|
|
{
|
|
"B03_FileInput",
|
|
"B04_wf1_Surface",
|
|
"B05_wf2_Route",
|
|
"B06_wf3_ProfileCross",
|
|
"B07_wf4_DesignDetail",
|
|
"B08_wf5_Quantity",
|
|
"B09_wf6_Estimation",
|
|
}
|
|
)
|
|
|
|
|
|
# 프로젝트별 저장 구조: storage/[고객사]/[사용자]/[프로젝트ID]/
|
|
def get_project_storage_path(company: str, user: str, project_id: str) -> str:
|
|
"""검증된 식별자로 프로젝트 저장 경로를 생성한다."""
|
|
segments = (company, user, project_id)
|
|
for segment in segments:
|
|
if (
|
|
not segment
|
|
or segment in {".", ".."}
|
|
or os.path.isabs(segment)
|
|
or "/" in segment
|
|
or "\\" in segment
|
|
):
|
|
raise ValueError("저장 경로 식별자는 비어 있거나 경로 구분자를 포함할 수 없습니다.")
|
|
|
|
storage_root = os.path.abspath(STORAGE_BASE_DIR)
|
|
path = os.path.abspath(os.path.join(storage_root, *segments))
|
|
if os.path.commonpath((storage_root, path)) != storage_root:
|
|
raise ValueError("프로젝트 저장 경로가 저장소 루트를 벗어났습니다.")
|
|
|
|
os.makedirs(path, exist_ok=True)
|
|
return path
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
# 7. 인증 (JWT)
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
JWT_SECRET_KEY = os.getenv("JWT_SECRET_KEY", "your-secret-key-change-in-production")
|
|
JWT_ALGORITHM = "HS256"
|
|
JWT_EXPIRE_MINUTES = int(os.getenv("JWT_EXPIRE_MINUTES", "1440")) # 24시간
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
# 8. CORS (프론트엔드 도메인)
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
CORS_ORIGINS = os.getenv("CORS_ORIGINS", "http://localhost:5173,http://localhost:8000").split(",")
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
# 9. 환경 정보
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
ENVIRONMENT = os.getenv("ENVIRONMENT", "development") # development, staging, production
|