Files
Aislo/resources/tester/test_760_1_geology.py
T
eomsangdonandClaude Opus 5.5 f6cfaf446c feat(760-1): 암상 지질도 겹 · 노선 따라 바위 종류 구간 · 밖 겹 출처 줄
- 서버: 지오빅데이터(KIGAM) 수치지질도 1:50,000 — WFS 암상 면(깨진 한글 칸은 기호별 점 조회로 채움) · 받은 범위 ∪ 노선으로 자름 · 저장본 · 노선 따라 1 m 표본 구간 · 키는 서버만(.env GEOBIGDATA_API_KEY · 쿼리 key)
- 그림 조각 대리에 geology(키 쿼리 이름 칸 key_param · 옛 겹 serviceKey 그대로)
- 화면(D03 · B05): 「제한·재해」에 「암상 지질도」 — 그림 조각 + 갈래 색 면 · 「기호 · 암상」 글 · 범례 · 노선 구간 표(참고 — 토사 · 암 판정 아님)
- 켠 밖 겹마다 범례 맨 아래 출처 줄(브이월드 · 국토지리정보원 · 산림청 · 환경부 · 생활안전지도 공공누리 · 한국지질자원연구원 CC BY-NC)
- 범례 칸 높이 한도 · 밀기(낮은 화면)
- 시험: test_760_1 새로 · test_634_2 · test_553_4 겹 차례 표에 geology 한 줄 더함(새 겹 · 낡은 표)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 08:32:53 +09:00

173 lines
7.4 KiB
Python

"""760-1 암상 지질도 겹 — 바위 갈래 · WFS 깨진 칸 읽기 · 받은 범위 자름 · 노선 구간 · 키 숨김 · 출처 줄."""
import json
import re
from pathlib import Path
import pytest
from common_util import common_util_wms_tiles as wms_tiles
from D03_PlanAnalysis import D03_PlanAnalysis_Geology as geology
ROOT = Path(__file__).resolve().parents[2]
MAP = ROOT / "A00_Common" / "map"
EPSG = 5179
FAKE_KEY = "TESTKEY-760-SECRET"
def _square(x0, y0, size):
from pyproj import Transformer
to_ll = Transformer.from_crs(f"EPSG:{EPSG}", "EPSG:4326", always_xy=True).transform
ring = [(x0, y0), (x0 + size, y0), (x0 + size, y0 + size), (x0, y0 + size), (x0, y0)]
return {"type": "Polygon", "coordinates": [[list(to_ll(x, y)) for x, y in ring]]}
def test_rock_group():
assert geology.rock_group("알코스사암 역질 사암 셰일 역암") == 2
assert geology.rock_group("흑운모 화강암") == 0
assert geology.rock_group("우백질 화강편마암") == geology.META
assert geology.rock_group("결정질 석회암 및 각섬암") == geology.META
assert geology.rock_group("안산암질 응회암") == 1
assert geology.rock_group("충적층") == 4
assert geology.rock_group("") == geology.OTHER
assert len(geology.ROCK_GROUP_NAMES) == 6
def test_parse_litho_broken_korean_keys():
# 서버 흠 — 한글 칸 이름 · 값이 「??」로 겹침 · 기호만 영문
body = (
'{"type":"FeatureCollection","features":[{"type":"Feature","geometry":'
'{"type":"Polygon","coordinates":[[[0,0],[1,0],[1,1],[0,0]]]},'
'"properties":{"??":"???? ???","??":"PCEp","????":"?? ??","symnum":"010106",'
'"mapname":"???","mapidx":"IF04"}}]}'
).encode()
got = geology.parse_litho(body)
assert got == [
{
"symnum": "010106",
"symbol": "PCEp",
"geometry": {"type": "Polygon", "coordinates": [[[0, 0], [1, 0], [1, 1], [0, 0]]]},
}
]
with pytest.raises(RuntimeError):
geology.parse_litho(b"<html>500</html>")
def _layer():
raw = [
{"symnum": "A1", "symbol": "Kd", "geometry": _square(1000000, 2000000, 100)},
{"symnum": "B2", "symbol": "Kg", "geometry": _square(1000100, 2000000, 100)},
{"symnum": "A1", "symbol": "Kd", "geometry": _square(1000900, 2000900, 50)}, # 범위 밖
]
names = {"A1": "알코스사암 셰일", "B2": "흑운모 화강암"}
calls = []
def fake_names(symnum, symbol, lon, lat):
calls.append(symnum)
return {
"symbol": symbol,
"litho": names[symnum],
"strata": "지층",
"age": "백악기",
"sheet": "도계동",
}
box = (1000000.0, 2000000.0, 1000200.0, 2000100.0)
return geology.build_layer(raw, box, EPSG, fake_names), calls
def test_build_layer_clips_and_names_once_per_symbol():
value, calls = _layer()
assert sorted(calls) == ["A1", "B2"] # 기호마다 점 조회 한 번
assert [(s["symbol"], s["group"], s["count"]) for s in value["summary"]] == [
("Kd", 2, 1),
("Kg", 0, 1),
]
assert value["summary"][0]["area_m2"] == pytest.approx(10000, rel=1e-3)
assert len(value["features"]) == 2 and len(value["parts_m"]) == 2
lon, lat = value["features"][0]["geometry"]["coordinates"][0][0]
assert 120 < lon < 135 and 30 < lat < 45 # 화면 지물 = 경위도
def test_route_segments():
value, _ = _layer()
# 서 → 동 200 m 직선 — 0~100 m Kd · 100~200 m Kg
segs = geology.route_segments(value["parts_m"], [[1000000, 2000050], [1000200, 2000050]])
assert [(s["from_m"], s["to_m"], s["i"]) for s in segs] == [(0.0, 100.0, 0), (100.0, 200.0, 1)]
# 범위 밖으로 50 m 더 = 자료 없음(-1)
segs = geology.route_segments(value["parts_m"], [[1000000, 2000050], [1000250.5, 2000050]])
assert segs[-1] == {"from_m": 200.0, "to_m": 250.5, "i": -1}
assert geology.route_segments(value["parts_m"], [[0, 0]]) == []
out = geology.layer_response(
value, [{"name": "계획노선", "points": [[1000000, 2000050], [1000200, 2000050]]}]
)
assert "parts_m" not in out and out["routes"][0]["segments"][1]["i"] == 1
assert "CC BY-NC" in out["source"]
def test_cover_box_takes_route():
box = (0.0, 0.0, 100.0, 100.0)
assert geology.cover_box(box, []) == box
got = geology.cover_box(box, [{"name": "a", "points": [[50, 50], [300, 40]]}], pad=10)
assert got == (0.0, 0.0, 310.0, 100.0)
def test_key_only_on_server(monkeypatch):
"""키 = 쿼리 `key` 로만 · 오류 글에 주소 · 키 없음 · 없으면 MissingKeyError."""
seen = []
def fake_urlopen(request, timeout):
seen.append(request.full_url)
raise OSError("boom")
monkeypatch.setattr("urllib.request.urlopen", fake_urlopen)
monkeypatch.setenv(geology.KEY_ENV, FAKE_KEY)
with pytest.raises(RuntimeError) as error:
geology.fetch_litho({"min_lon": 129, "min_lat": 36, "max_lon": 129.1, "max_lat": 36.1})
assert FAKE_KEY not in str(error.value)
assert f"key={FAKE_KEY}" in seen[0] and "typeName=geoOpen" in seen[0]
# 그림 조각 대리 — 같은 키 · 쿼리 이름 key · 오류 글에 키 없음
source = wms_tiles.SOURCES["geology"]
assert source.key_param == "key" and source.key_env == geology.KEY_ENV
with pytest.raises(RuntimeError) as error:
wms_tiles.fetch("geology", 14, 14000, 6400)
assert FAKE_KEY not in str(error.value)
assert seen[1].startswith(f"{geology.WMS_URL}?key={FAKE_KEY}&") and "GetMap" in seen[1]
assert "serviceKey" in wms_tiles.SOURCES["landslide"].key_param # 옛 겹 그대로
monkeypatch.setenv(geology.KEY_ENV, "")
with pytest.raises(geology.MissingKeyError):
geology.fetch_litho({"min_lon": 129, "min_lat": 36, "max_lon": 129.1, "max_lat": 36.1})
def test_screen_layer_and_sources():
lst = (MAP / "map_layer_list.ts").read_text(encoding="utf-8")
assert 'row("geology", "strata", "암상 지질도", "limit")' in lst
src = (MAP / "map_layer_source.ts").read_text(encoding="utf-8")
assert re.search(r'\["브이월드", \[[^\]]*"faults", "lineaments"\]\]', src)
assert re.search(r'CC BY-NC\)", \["geology"\]\]', src)
assert '공공누리 제4유형)", ["landslide"]' in src
forest = (MAP / "map_forest_layers.ts").read_text(encoding="utf-8")
assert 'geology_map: wmt("geology")' in forest
geo = (MAP / "map_geology.ts").read_text(encoding="utf-8")
assert "참고 — 토사 · 암 판정 아님" in geo and "/plan/layers/geology" in geo
assert "data.kigam.re.kr" not in geo # 브라우저는 KIGAM 을 부르지 않음
theme = (ROOT / "ui_template" / "ui_template_theme.css").read_text(encoding="utf-8")
assert all(f"--draw-map-geology-{i}:" in theme for i in range(1, 7))
def test_no_key_in_code():
env = (ROOT / ".env").read_text(encoding="utf-8") if (ROOT / ".env").is_file() else ""
got = re.search(r"^GEOBIGDATA_API_KEY=(.+)$", env, re.M)
if not got or not got.group(1).strip():
pytest.skip("키 없는 PC")
key = got.group(1).strip()
paths = [
ROOT / "D03_PlanAnalysis" / "D03_PlanAnalysis_Geology.py",
MAP / "map_geology.ts",
MAP / "map_layer_source.ts",
]
leaked = [p.name for p in paths if key in p.read_text(encoding="utf-8")] # 실패 글에 키 안 냄
assert leaked == []
assert json.dumps(geology.SOURCE) # 출처 글은 있음