- GL 겹 스물: 위성 조각 · 음영 · 급경사 · 산사태위험(image) · 산림입지 · 보호구역 · 띠 · 물 면 · 건물(fill + 테두리) · 지적 · 등고선 · 도로 · 임도 짐작 · 올린 임도망도 · 세류 · 단층 · 지질구조선 · 0차곡 · 산사태취약 필지(line · 점선 px 그대로) · 산사태취약 점 · 지명 점(circle) — 색 · 굵기 · 투명도 · 차례 = 옛 캔버스 그대로 - 음영 = 위성 위 「곱하기 0.75」와 같은 셈의 검은 반투명 그림(밝기 g → 투명도 0.75·(1−g)) · 위성 끔 = 그대로 - 등고 수치 · 지명 = symbol(겹치면 뺌 · 등고 글은 선 따라) · 글꼴 = 화면 Noto Sans KR(OFL · 자체 보관)을 그 자리에서 그림 (꼴에 글꼴 주소 없음 = 글꼴 조각 파일 없음 · 받기 전에 그 글자 글꼴 미리 받음) - 카메라 주인 = D03 화면 view · 돌림 하나 — 휠 · 가운데 끌기 · 돌림 · 맞춤 · 단축키 모두 draw() → glCamera - 처음 = GL(aislo:pref:d03.canvas = "1" 이면 옛 캔버스) · 겹 단추줄 끝 단추로 바꿈 · WebGL 못 열거나 잃음 = 저절로 옛 캔버스 + 알림 - GL 이면 캔버스 바탕 겹은 쉼(등고 주곡선 간격 셈만) · GL 이면 위성 한 장 그림 안 받음 - 산사태위험 그림도 경위도 귀퉁이(lonlat_corners 한 곳) · 묶음 꼴 표시 없는 합친 자료도 GL 이 받게 다시 쌈 - 캔버스 그리기 목록 = 새 D03_PlanAnalysis_UI_LayerPaint.ts(700줄 규칙) · 검증 창구 layers().gl = GL 겹별 그린 지물 수 - 잼(headless Edge 실 GPU · 검증_sub2_D · 겹 18 켬 = 자료 있는 겹 전부): 줌아웃 화면 간격 중앙 58.4 → 4.2 ms · 끊김 27 → 0/30 · 끌기 62.5 → 4.2 ms · 끊김 37 → 0/40 · 마우스 4.2 ms 끊김 0 그대로 · 그은 선 대 GL 0.019 px(돌림 30° 0.019 · 확대 0.077) - 시험: 캔버스 그리기를 옮긴 자리로 지킴 시험 넷 고침(581-2 · 582 · 584-1 · 584-3 — 그리기 차례 · 색 값이 LayerPaint 로) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
100 lines
4.6 KiB
Python
100 lines
4.6 KiB
Python
"""D 노선분석 GIS 겹 「다음」 묶음(PLAN 581-2) — 산림입지도 잘라 넓이 · 토양형 코드 차례
|
||
이름 없으면 글자 그대로 · 실패 = 오류(파일 안 씀) · 덮으면 다시 안 받음 · 화면 겹 정의 · 색 값.
|
||
|
||
./venv/Scripts/python.exe -m pytest resources/tester/test_581_2_plan_soils.py -q
|
||
"""
|
||
|
||
import json
|
||
import re
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
|
||
from common_util.common_util_crs import tm_to_lonlat
|
||
from D03_PlanAnalysis import D03_PlanAnalysis_Layers as layers
|
||
|
||
EPSG = 5186
|
||
BOX = (230000.0, 450000.0, 232000.0, 451000.0) # 2 × 1 km
|
||
ROOT = Path(__file__).resolve().parents[2]
|
||
|
||
|
||
def _square(x0: float, y0: float, side: float) -> dict:
|
||
ring = [(x0, y0), (x0 + side, y0), (x0 + side, y0 + side), (x0, y0 + side), (x0, y0)]
|
||
return {"type": "Polygon", "coordinates": [[list(tm_to_lonlat(x, y, EPSG)) for x, y in ring]]}
|
||
|
||
|
||
def _soil(gid: str, geometry: dict, cd: str, code: str, name: str | None) -> dict:
|
||
props = {"sltp_cd": cd, "toyanghyun": code, **({"name": name} if name else {})}
|
||
return {"id": gid, "geometry": geometry, "properties": props}
|
||
|
||
|
||
def test_clip_soils_area_order_names() -> None:
|
||
found = [
|
||
_soil("a", _square(230100.0, 450100.0, 100.0), "03", "B₃", "갈색적윤산림토양"),
|
||
_soil("b", _square(231800.0, 450800.0, 400.0), "01", "B₁", "갈색건조산림토양"), # 귀퉁이
|
||
_soil("b", _square(231800.0, 450800.0, 400.0), "01", "B₁", "갈색건조산림토양"), # 겹침
|
||
_soil("c", _square(230500.0, 450500.0, 100.0), "03", "B₃", "갈색적윤산림토양"),
|
||
_soil("d", _square(230800.0, 450100.0, 50.0), "99", "Xx", None), # 모르는 값
|
||
_soil("e", _square(240000.0, 460000.0, 100.0), "02", "B₂", "갈색약건산림토양"), # 밖
|
||
]
|
||
features, summary = layers.clip_soils(found, BOX, EPSG)
|
||
assert [row["code"] for row in summary] == ["B₁", "B₃", "Xx"]
|
||
assert summary[0]["area_m2"] == pytest.approx(200 * 200, rel=0.01)
|
||
assert summary[1] == {
|
||
"code": "B₃",
|
||
"name": "갈색적윤산림토양",
|
||
"count": 2,
|
||
"area_m2": pytest.approx(2 * 100 * 100, rel=0.01),
|
||
}
|
||
assert summary[2]["name"] == "Xx" # 이름 없으면 글자 그대로
|
||
assert len(features) == 4
|
||
assert {f["properties"]["code"] for f in features} == {"B₁", "B₃", "Xx"}
|
||
|
||
|
||
class FakeWfs:
|
||
def __init__(self, fail: bool = False) -> None:
|
||
self.fail, self.calls = fail, []
|
||
|
||
def __call__(self, typename, filename, bounds, output_dir) -> None:
|
||
self.calls.append(typename)
|
||
if self.fail:
|
||
return
|
||
feats = [_soil("a", _square(230500.0, 450200.0, 300.0), "01", "B₁", "갈색건조산림토양")]
|
||
body = {"type": "FeatureCollection", "features": feats}
|
||
(Path(output_dir) / filename).write_text(json.dumps(body), "utf-8")
|
||
|
||
|
||
def test_soils_fetch_once(tmp_path, monkeypatch) -> None:
|
||
fake = FakeWfs()
|
||
monkeypatch.setattr(layers, "request_vworld_wfs", fake)
|
||
got = layers.ensure_soils(tmp_path, BOX, EPSG)
|
||
assert got["fetched"] and fake.calls == [layers.SOIL_TYPENAME]
|
||
saved = json.loads((tmp_path / layers.SOIL_FILE).read_text("utf-8"))
|
||
assert saved["summary"][0]["count"] == 1 and len(saved["features"]) == 1
|
||
inner = (230500.0, 450100.0, 231000.0, 450900.0)
|
||
assert not layers.ensure_soils(tmp_path, inner, EPSG)["fetched"] # 덮으면 다시 안 받음
|
||
assert len(fake.calls) == 1
|
||
assert layers.ensure_soils(tmp_path, (229000.0, *BOX[1:]), EPSG)["fetched"] # 넓어지면 다시
|
||
|
||
|
||
def test_soils_fail_raises(tmp_path, monkeypatch) -> None:
|
||
monkeypatch.setattr(layers, "request_vworld_wfs", FakeWfs(fail=True))
|
||
with pytest.raises(RuntimeError):
|
||
layers.ensure_soils(tmp_path, BOX, EPSG)
|
||
assert not (tmp_path / layers.SOIL_FILE).exists()
|
||
|
||
|
||
def test_screen_def_and_colors() -> None:
|
||
defs = (ROOT / "D03_PlanAnalysis" / "D03_PlanAnalysis_UI_LayerDefs.ts").read_text("utf-8")
|
||
entry = re.search(r'key: "soils",\s*icon: "(\w+)",[^}]*on: (\w+)', defs)
|
||
assert entry and entry.group(2) == "false" # 처음 값 끔
|
||
icons = (ROOT / "ui_template" / "ui_template_icons.ts").read_text("utf-8")
|
||
assert re.search(rf"^\s+{entry.group(1)}:", icons, re.M)
|
||
theme = (ROOT / "ui_template" / "ui_template_theme.css").read_text("utf-8")
|
||
assert all(f"--draw-map-soil-{n}:" in theme for n in range(1, 7))
|
||
page = (ROOT / "D03_PlanAnalysis" / "D03_PlanAnalysis_UI_LayerPaint.ts").read_text(
|
||
"utf-8"
|
||
) # 586-2
|
||
assert "if (!on.soils || !data.soils) return;" in page
|
||
assert page.count("base: true,") == page.count("draw: (context, map) =>") # 겹 모두 바탕(582)
|