시험을 `resources/tester` 로 옮긴 뒤 **한 건이 실패**하고 있었음 — `test_preview_cost` 가 짝 파일을 `tmp.tests.…` 로 불러 못 찾았음(옮긴 폴더에 없음). - `from resources.tester.test_preview_assets import …` 로 고침 - 실행 안내 문구 셋도 새 경로로(`diag_structures` · `helper_b06_fill_slope_length` · `test_common_util_crs`) - 정책 시험 설명은 **사실이 바뀌었으므로** 고쳐 적음 — 「git 밖이라」가 아니라 「옮겨서 함께 건너가되, 까닭은 여전히 값 옆(정본)에 두는 것이 옳다」 `resources/tester/` 1183 통과 / 22 건너뜀 / **실패 0**. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
180 lines
7.9 KiB
Python
180 lines
7.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""PRJ 좌표계 판별 검증 — common_util_crs + 수리된 get_epsg_from_prj.
|
|
|
|
실행: ./venv/Scripts/python.exe -m pytest resources/tester/test_common_util_crs.py -q
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from pyproj import CRS, Transformer
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from B03_FileInput.B03_FileInput_Engine_Analyze import analyze_prj_metadata # noqa: E402
|
|
from B04_PreProcess.B04_PreProcess_Engine_VWorld import get_epsg_from_prj # noqa: E402
|
|
from common_util.common_util_crs import ( # noqa: E402
|
|
crs_input_from_prj,
|
|
identify_epsg,
|
|
)
|
|
|
|
UPLOADS = Path(r"C:/Users/umsan/.claude/uploads/ab448e1d-3fa5-4f88-8e02-b26428c8dfc7")
|
|
LAS_PRJ = UPLOADS / "a450e281-__.prj" # Korean 1985 Modified East Belt → 5176
|
|
ROUTE_PRJ = UPLOADS / "105c7734-______.__.2.2.prj" # UTM-K, AUTHORITY 없음 → 5179
|
|
EXISTING_COMPD_PRJ = (
|
|
ROOT / "storage/1/3/2f940d8a-2065-4cf6-8bf8-dc3f0af84e57/B03_FileInput/input/prj/result.prj"
|
|
)
|
|
|
|
needs_uploads = pytest.mark.skipif(not LAS_PRJ.exists(), reason="업로드 PRJ 없음")
|
|
needs_storage = pytest.mark.skipif(not EXISTING_COMPD_PRJ.exists(), reason="저장소 PRJ 없음")
|
|
|
|
|
|
# ── 사다리 라벨 판별 ──────────────────────────────────────────────────────────
|
|
@needs_uploads
|
|
def test_uploaded_las_prj_labels_5176():
|
|
text = LAS_PRJ.read_text(encoding="utf-8")
|
|
assert identify_epsg(CRS.from_wkt(text), text) == 5176
|
|
|
|
|
|
@needs_uploads
|
|
def test_uploaded_route_prj_labels_5179():
|
|
text = ROUTE_PRJ.read_text(encoding="utf-8")
|
|
# AUTHORITY 태그가 아예 없다 — 파라미터 지문(③)으로만 잡힌다.
|
|
assert 'AUTHORITY["EPSG"' not in text
|
|
assert identify_epsg(CRS.from_wkt(text), text) == 5179
|
|
|
|
|
|
def test_standard_codes_round_trip():
|
|
for code in (5185, 5186, 5187, 5188, 5179, 5174, 5176, 32652, 4326):
|
|
wkt = CRS.from_epsg(code).to_wkt("WKT1_GDAL")
|
|
assert identify_epsg(CRS.from_wkt(wkt), wkt) == code, code
|
|
|
|
|
|
@needs_storage
|
|
def test_compound_kngeoid_prj_labels_5187():
|
|
text = EXISTING_COMPD_PRJ.read_text(encoding="utf-8")
|
|
# KNGeoid24 결합(COMPD_CS) — 수평 성분으로 판별한다. 사설 수직 코드는
|
|
# B03 _prepare_prj_wkt가 떼므로 여기서는 pyproj가 그대로 읽는지만 대비한다.
|
|
from B03_FileInput.B03_FileInput_Engine_Analyze import _prepare_prj_wkt
|
|
|
|
parse_text, _ = _prepare_prj_wkt(text)
|
|
assert identify_epsg(CRS.from_wkt(parse_text), parse_text) == 5187
|
|
|
|
|
|
# ── 회귀: 죽은 문자열 분기 ────────────────────────────────────────────────────
|
|
def test_central_belt_no_longer_misread_as_east():
|
|
"""예전 코드는 false_easting의 EAST 때문에 모든 PRJ를 5187로 판정했다."""
|
|
central = CRS.from_epsg(5186).to_wkt("WKT1_GDAL")
|
|
assert get_epsg_from_prj(central) == "EPSG:5186"
|
|
west = CRS.from_epsg(5185).to_wkt("WKT1_GDAL")
|
|
assert get_epsg_from_prj(west) == "EPSG:5185"
|
|
utm = CRS.from_epsg(32652).to_wkt("WKT1_GDAL")
|
|
assert get_epsg_from_prj(utm) == "EPSG:32652"
|
|
|
|
|
|
def test_empty_prj_keeps_legacy_default():
|
|
assert get_epsg_from_prj("") == "EPSG:5186"
|
|
assert get_epsg_from_prj("이것은 WKT가 아니다") == "EPSG:5186"
|
|
|
|
|
|
# ── 변환 정본 = WKT (TOWGS84 보존) ───────────────────────────────────────────
|
|
@needs_uploads
|
|
def test_las_prj_returns_wkt_and_transforms_into_korea():
|
|
text = LAS_PRJ.read_text(encoding="utf-8")
|
|
crs_input = get_epsg_from_prj(text)
|
|
# 비표준 TOWGS84라 pyproj DB 확정이 안 된다 → 원문 WKT 그대로 반환해야 한다.
|
|
assert crs_input.lstrip().startswith("PROJCS"), crs_input[:40]
|
|
lon, lat = Transformer.from_crs(crs_input, "EPSG:4326", always_xy=True).transform(
|
|
209014.2, 367805.3
|
|
)
|
|
assert 128.5 < lon < 129.7 and 36.3 < lat < 37.3, (lon, lat) # 울진 일대
|
|
|
|
|
|
@needs_uploads
|
|
def test_route_prj_transform_matches_utmk():
|
|
text = ROUTE_PRJ.read_text(encoding="utf-8")
|
|
crs_input = get_epsg_from_prj(text)
|
|
lon, lat = Transformer.from_crs(crs_input, "EPSG:4326", always_xy=True).transform(
|
|
1142863.4, 1869357.4
|
|
)
|
|
# 정식 EPSG:5179와 십cm 수준에서 같아야 한다 (ITRF2000≈GRS80 무보정)
|
|
lon2, lat2 = Transformer.from_crs("EPSG:5179", "EPSG:4326", always_xy=True).transform(
|
|
1142863.4, 1869357.4
|
|
)
|
|
assert abs(lon - lon2) < 1e-5 and abs(lat - lat2) < 1e-5
|
|
|
|
|
|
def test_wkt_string_accepted_by_transformer():
|
|
"""반환값이 EPSG든 WKT든 Transformer.from_crs 입력으로 동작해야 한다."""
|
|
wkt = CRS.from_epsg(5187).to_wkt("WKT1_GDAL")
|
|
x, y = Transformer.from_crs(wkt, "EPSG:4326", always_xy=True).transform(208457.1, 467857.4)
|
|
assert 128.0 < x < 130.0 and 36.0 < y < 38.0
|
|
|
|
|
|
# ── B03 메타데이터 라벨 보강 ─────────────────────────────────────────────────
|
|
@needs_uploads
|
|
def test_analyze_prj_metadata_fills_epsg_labels():
|
|
las_meta = analyze_prj_metadata(LAS_PRJ)
|
|
assert las_meta["is_valid"] and las_meta["epsg"] == 5176, las_meta["epsg"]
|
|
route_meta = analyze_prj_metadata(ROUTE_PRJ)
|
|
assert route_meta["is_valid"] and route_meta["epsg"] == 5179, route_meta["epsg"]
|
|
|
|
|
|
@needs_storage
|
|
def test_analyze_existing_compound_prj_still_5187():
|
|
meta = analyze_prj_metadata(EXISTING_COMPD_PRJ)
|
|
assert meta["epsg"] == 5187 and meta["crs_status"] in ("identified", "custom_vertical_crs")
|
|
|
|
|
|
# ── 광역 스윕: 한국 전체 + 해외, WKT 3형식, 익명화 악조건 ────────────────────
|
|
KOREAN_CODES = (5173, 5174, 5175, 5176, 5177, 5178, 5179, 5185, 5186, 5187, 5188, 32651, 32652, 4326)
|
|
FOREIGN_CODES = (3857, 32610, 25832, 2154, 27700, 26910, 2193, 6669, 6677, 3095, 28355, 31370)
|
|
WKT_FORMATS = ("WKT1_GDAL", "WKT2_2019", "WKT1_ESRI")
|
|
|
|
|
|
@pytest.mark.parametrize("fmt", WKT_FORMATS)
|
|
def test_sweep_korean_and_foreign_codes(fmt):
|
|
"""한국 14종 + 해외 12종을 세 가지 WKT 방언으로 직렬화해 전부 재판별한다."""
|
|
for code in KOREAN_CODES + FOREIGN_CODES:
|
|
wkt = CRS.from_epsg(code).to_wkt(fmt)
|
|
assert identify_epsg(CRS.from_wkt(wkt), wkt) == code, (code, fmt)
|
|
assert crs_input_from_prj(wkt) == f"EPSG:{code}", (code, fmt)
|
|
|
|
|
|
def _anonymize(wkt: str) -> str:
|
|
"""AUTHORITY 태그 제거 + 좌표계 이름 익명화 — 원청 노선 PRJ와 같은 악조건."""
|
|
import re
|
|
|
|
wkt = re.sub(r",?AUTHORITY\[[^\]]*\]", "", wkt)
|
|
return re.sub(r'PROJCS\["[^"]+"', 'PROJCS["unknown"', wkt)
|
|
|
|
|
|
def test_sweep_anonymized_wkt_still_identified():
|
|
"""AUTHORITY 없고 이름도 지운 ESRI WKT — 파라미터만으로 판별돼야 한다."""
|
|
for code in (5185, 5186, 5187, 5188, 5179, 5174, 5176, 32652, 32610, 2154, 26910):
|
|
wkt = _anonymize(CRS.from_epsg(code).to_wkt("WKT1_ESRI"))
|
|
got = identify_epsg(CRS.from_wkt(wkt), wkt)
|
|
assert got == code, (code, got)
|
|
|
|
|
|
def test_anonymized_never_mislabels_as_other_code():
|
|
"""익명화 WKT가 엉뚱한 코드로 오판되면 좌표가 통째로 어긋난다 — 정답 아니면 None만 허용."""
|
|
for code in FOREIGN_CODES:
|
|
wkt = _anonymize(CRS.from_epsg(code).to_wkt("WKT1_ESRI"))
|
|
got = identify_epsg(CRS.from_wkt(wkt), wkt)
|
|
assert got in (code, None), (code, got)
|
|
|
|
|
|
@needs_storage
|
|
def test_existing_compound_prj_keeps_legacy_epsg_string():
|
|
"""기존 프로젝트(수평 5187 + KNGeoid24 수직)는 예전처럼 "EPSG:5187"이 나와야 한다.
|
|
|
|
수직 성분의 bound(지오이드)는 2D 변환에 무관하므로 WKT 폴백 사유가 아니다.
|
|
"""
|
|
text = EXISTING_COMPD_PRJ.read_text(encoding="utf-8")
|
|
assert get_epsg_from_prj(text) == "EPSG:5187"
|