- 폴더·내부 파일 51개 접두사 개명 (git mv, 이력 보존) - 저장소 전체 참조 치환 67파일: import 경로, 라우트 슬러그(b04-preprocess), 라우트 키(B04_PREPROCESS), storage 경로 상수, locale, SQL 주석 - 로직 변경 없음 (기계적 치환). typecheck·백엔드 import 검증 통과 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
124 lines
5.0 KiB
Python
124 lines
5.0 KiB
Python
# B04_PreProcess_Engine_MapSheet.py
|
|
# 1:5,000 수치지형도 도엽번호 산출 엔진.
|
|
# 국토지리정보원 도엽번호 체계:
|
|
# - 경위도 1°x1° 구획: 위도 2자리 + 경도 끝 1자리 (예: 369 = 위도 36~37°, 경도 129~130°)
|
|
# - 1:50,000 = 1° 구획을 4x4(15'x15') 분할, NW부터 행 우선 01~16 (예: 36906)
|
|
# - 1:5,000 = 1:50,000 도엽을 10x10(1.5'x1.5') 분할, NW부터 행 우선 001~100
|
|
# (예: 36906042 = 36906 도엽의 042 구획)
|
|
|
|
from __future__ import annotations
|
|
|
|
# 1:5,000 도엽 한 변 크기 (도 단위): 15' / 10 = 1.5'
|
|
SHEET5K_SIZE_DEG = 0.025
|
|
# 1:50,000 도엽 한 변 크기 (도 단위): 15'
|
|
SHEET50K_SIZE_DEG = 0.25
|
|
|
|
|
|
def _lon_digit_to_degree(digit: int) -> int:
|
|
"""경도 끝자리 → 경도 정수부 복원. 한반도 범위(124~132°E)에서 유일."""
|
|
if digit >= 4:
|
|
return 120 + digit # 4~9 → 124~129
|
|
return 130 + digit # 0~2 → 130~132
|
|
|
|
|
|
def latlon_to_sheet5k(lat: float, lon: float) -> str:
|
|
"""WGS84 위경도 → 1:5,000 도엽번호(8자리 문자열)."""
|
|
lat_deg = int(lat)
|
|
lon_deg = int(lon)
|
|
cell = f"{lat_deg:02d}{lon_deg % 10}"
|
|
|
|
# 1° 구획 내 오프셋 (북서 원점 기준: 북쪽에서 남쪽으로 행 증가)
|
|
lat_frac = (lat_deg + 1) - lat # 구획 상단으로부터 남쪽 거리
|
|
lon_frac = lon - lon_deg
|
|
|
|
row50 = min(int(lat_frac / SHEET50K_SIZE_DEG), 3)
|
|
col50 = min(int(lon_frac / SHEET50K_SIZE_DEG), 3)
|
|
idx50 = row50 * 4 + col50 + 1 # 01~16
|
|
|
|
# 1:50,000 도엽 내 오프셋
|
|
lat_in_50 = lat_frac - row50 * SHEET50K_SIZE_DEG
|
|
lon_in_50 = lon_frac - col50 * SHEET50K_SIZE_DEG
|
|
row5 = min(int(lat_in_50 / SHEET5K_SIZE_DEG), 9)
|
|
col5 = min(int(lon_in_50 / SHEET5K_SIZE_DEG), 9)
|
|
idx5 = row5 * 10 + col5 + 1 # 001~100
|
|
|
|
return f"{cell}{idx50:02d}{idx5:03d}"
|
|
|
|
|
|
def sheet5k_to_bounds(sheet_no: str) -> tuple[float, float, float, float]:
|
|
"""1:5,000 도엽번호 → WGS84 도곽 범위 (lon_min, lat_min, lon_max, lat_max)."""
|
|
s = str(sheet_no).strip()
|
|
if len(s) != 8 or not s.isdigit():
|
|
raise ValueError(f"1:5,000 도엽번호는 8자리 숫자여야 함: {sheet_no!r}")
|
|
|
|
lat_deg = int(s[0:2])
|
|
lon_deg = _lon_digit_to_degree(int(s[2]))
|
|
idx50 = int(s[3:5])
|
|
idx5 = int(s[5:8])
|
|
if not (1 <= idx50 <= 16):
|
|
raise ValueError(f"1:50,000 구획번호(01~16) 범위 밖: {sheet_no!r}")
|
|
if not (1 <= idx5 <= 100):
|
|
raise ValueError(f"1:5,000 구획번호(001~100) 범위 밖: {sheet_no!r}")
|
|
|
|
row50, col50 = divmod(idx50 - 1, 4)
|
|
row5, col5 = divmod(idx5 - 1, 10)
|
|
|
|
lat_max = (lat_deg + 1) - row50 * SHEET50K_SIZE_DEG - row5 * SHEET5K_SIZE_DEG
|
|
lon_min = lon_deg + col50 * SHEET50K_SIZE_DEG + col5 * SHEET5K_SIZE_DEG
|
|
return (lon_min, lat_max - SHEET5K_SIZE_DEG, lon_min + SHEET5K_SIZE_DEG, lat_max)
|
|
|
|
|
|
def sheet5k_center(sheet_no: str) -> tuple[float, float]:
|
|
"""도엽 중심 위경도 (lat, lon)."""
|
|
lon_min, lat_min, lon_max, lat_max = sheet5k_to_bounds(sheet_no)
|
|
return ((lat_min + lat_max) / 2.0, (lon_min + lon_max) / 2.0)
|
|
|
|
|
|
def neighbors_3x3(sheet_no: str) -> list[str]:
|
|
"""중심 도엽 + 인접 8매 = 3x3 도엽번호 목록 (NW부터 행 우선 9매).
|
|
|
|
구획(1:50,000, 1°) 경계를 넘어가는 인접 도엽은 중심 좌표에
|
|
도엽 크기만큼 오프셋을 더해 재산출하므로 번호 이월이 자동 처리된다.
|
|
"""
|
|
lat_c, lon_c = sheet5k_center(sheet_no)
|
|
result = []
|
|
for dr in (1, 0, -1): # 북 → 남
|
|
for dc in (-1, 0, 1): # 서 → 동
|
|
result.append(
|
|
latlon_to_sheet5k(lat_c + dr * SHEET5K_SIZE_DEG, lon_c + dc * SHEET5K_SIZE_DEG)
|
|
)
|
|
return result
|
|
|
|
|
|
def sheets_for_points(points: list[tuple[float, float]]) -> list[str]:
|
|
"""기준 좌표들이 속한 도엽번호만(주변 도엽 없음, 중복 제거).
|
|
|
|
계획노선 시점·종점이 같은 도엽이면 1매, 걸치면 2~3매가 된다.
|
|
배경 지도(위성사진)는 이 도엽 범위만 확보한다(2026-08-01 사용자 지시).
|
|
"""
|
|
ordered: list[str] = []
|
|
seen: set[str] = set()
|
|
for lat, lon in points:
|
|
sheet_no = latlon_to_sheet5k(lat, lon)
|
|
if sheet_no not in seen:
|
|
seen.add(sheet_no)
|
|
ordered.append(sheet_no)
|
|
return ordered
|
|
|
|
|
|
def neighbors_for_points(points: list[tuple[float, float]]) -> list[str]:
|
|
"""기준 좌표들이 속한 도엽 + 각각의 주변 8매를 합친 목록(중복 제거, 순서 유지).
|
|
|
|
계획노선 시점·종점이 같은 도엽이면 9매, 이웃한 두 도엽에 걸치면 12매가 된다
|
|
(3×3 두 벌이 한 줄을 공유하므로 3×4). 도엽 하나가 늘 때마다 병합 산출물도 늘어나므로
|
|
기준 좌표는 노선의 양 끝만 쓴다(2026-08-01 사용자 지시).
|
|
"""
|
|
ordered: list[str] = []
|
|
seen: set[str] = set()
|
|
for lat, lon in points:
|
|
for sheet_no in neighbors_3x3(latlon_to_sheet5k(lat, lon)):
|
|
if sheet_no not in seen:
|
|
seen.add(sheet_no)
|
|
ordered.append(sheet_no)
|
|
return ordered
|