Files
Aislo/resources/tester/test_prj_identify.py
eomsangdonandClaude Opus 5 0ef32b5279 chore(tester): 시험을 resources/tester/ 로 옮김 — 창끼리 건너가게
⚠ **뿌리** — `tmp/` 는 창 사이에 안 건너감(실측 확인: 상대 창이 놓은 `tmp/_sync_probe.txt`
가 시간을 두고 두 번 봐도 안 보임). 그래서 **정본(등록부 스키마)만 건너가고 그것을 읽는
시험은 안 건너가** 오늘 두 번, 같은 시험이 **연 창은 통과·받은 창은 실패**가 됐음.

- `tmp/tests/*` 를 `resources/tester/` 로 **복사**(127 파일). 내용은 **한 줄도 안 고침**
- `tmp/tests` 는 **남겨 둠** — 되돌릴 자리(사용자 지시)
- 실행: `./venv/Scripts/python.exe -m pytest resources/tester/ -q`
  옮기기 전과 **같은 수**: 617 통과 / 22 건너뜀 / 실패 0

⇒ 이제 시험·예외·까닭이 **정본과 함께** 움직임. 오늘 세운 「예외는 정본 스키마에」와 짝임.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 17:12:30 +09:00

117 lines
4.1 KiB
Python

# -*- coding: utf-8 -*-
"""PRJ 좌표계 판별 시제 — 문자열 매칭 대신 투영 파라미터로 EPSG를 고른다.
현행 `B04_PreProcess_Engine_VWorld.get_epsg_from_prj()`는 WKT에 항상 들어 있는
`false_easting` 때문에 "EAST" 분기가 먼저 걸려 **모든 PRJ에 EPSG:5187을 준다**.
"""
from __future__ import annotations
import re
import pytest
from pyproj import CRS
# 한국에서 실제로 들어오는 좌표계만 후보로 둔다.
CANDIDATES = (
5185, 5186, 5187, 5188, # Korea 2000 (GRS80) 서·중부·동부·동해
5179, 5178, # UTM-K (GRS80 / Bessel)
5173, 5174, 5176, 5177, # Korean 1985 (Bessel) 구 좌표계
32652, 4326,
)
_AUTHORITY = re.compile(r'AUTHORITY\["EPSG","(\d+)"\]')
def _projected(crs: CRS) -> CRS:
"""COMPD_CS(수직 CRS 결합)면 수평 성분만 꺼낸다."""
return crs.sub_crs_list[0] if crs.is_compound else crs
def _fingerprint(crs: CRS) -> tuple | None:
"""투영 지문 — (중앙자오선, 원점위도, 축척, FE, FN, 타원체 장반경)."""
crs = _projected(crs)
op = crs.coordinate_operation
if op is None:
return None
p = {q.name.lower(): q.value for q in op.params}
def get(*keys):
for k in keys:
for name, value in p.items():
if k in name:
return value
return None
return (
round(get("longitude of natural origin", "central meridian") or 0.0, 6),
round(get("latitude of natural origin") or 0.0, 6),
round(get("scale factor") or 1.0, 6),
round(get("false easting") or 0.0, 3),
round(get("false northing") or 0.0, 3),
round(crs.ellipsoid.semi_major_metre, 3),
)
def epsg_from_prj(text: str) -> int | None:
"""PRJ WKT에서 EPSG를 판별한다. 못 고르면 None (기본값으로 때우지 않는다)."""
crs = CRS.from_wkt(text)
epsg = _projected(crs).to_epsg()
if epsg:
return epsg
codes = [int(c) for c in _AUTHORITY.findall(text)]
for code in reversed(codes): # WKT 끝의 PROJCS AUTHORITY가 그 좌표계 자신이다
if code in CANDIDATES:
return code
mine = _fingerprint(crs)
if mine is None:
return None
for code in CANDIDATES:
try:
if _fingerprint(CRS.from_epsg(code)) == mine:
return code
except Exception:
continue
return None
def test_uploaded_prj_files() -> None:
upload = r"C:/Users/umsan/.claude/uploads/ab448e1d-3fa5-4f88-8e02-b26428c8dfc7"
try:
las = open(upload + "/a450e281-__.prj", encoding="utf-8").read()
route = open(upload + "/105c7734-______.__.2.2.prj", encoding="utf-8").read()
except FileNotFoundError as e:
pytest.skip(f"laptop 업로드 픽스처 없음 (환경 의존): {e.filename}")
assert epsg_from_prj(las) == 5176, epsg_from_prj(las) # AUTHORITY 태그로 판별
assert epsg_from_prj(route) == 5179, epsg_from_prj(route) # 파라미터 지문으로 판별
def test_standard_codes_round_trip() -> None:
for code in (5185, 5186, 5187, 5188, 5179, 5174, 5176, 32652):
wkt = CRS.from_epsg(code).to_wkt("WKT1_GDAL")
assert epsg_from_prj(wkt) == code, (code, epsg_from_prj(wkt))
def test_compound_vertical_prj() -> None:
"""KNGeoid24 결합 PRJ(기존 프로젝트 자료)도 수평 성분으로 판별한다."""
import pathlib
path = (
pathlib.Path(__file__).resolve().parents[2]
/ "storage/1/3/2f940d8a-2065-4cf6-8bf8-dc3f0af84e57/B03_FileInput/input/prj/result.prj"
)
# 그 프로젝트가 지워진 창에서는 늘 실패한다 — 같은 파일의 다른 검사와 같은 태도로
# 건너뛴다(2026-09-08). 자료가 있는 창에서는 그대로 판별을 검사한다.
if not path.is_file():
pytest.skip(f"프로젝트 자료 없음 (환경 의존): {path.name}")
assert epsg_from_prj(path.read_text(encoding="utf-8")) == 5187
if __name__ == "__main__":
test_uploaded_prj_files()
test_standard_codes_round_trip()
test_compound_vertical_prj()
print("OK — 3개 검사 통과")