Files
Aislo/resources/tester/test_343_3_browser_cache.py
T
eomsangdonandClaude Opus 5.5 11c75f022a refactor(767-1): 안 쓰는 지면 필터 · 서피스 지움 — 필터 넷(CSF · 격자 최저 · PMF · RANSAC) · 표현 셋(TIN + Taubin 평활 · NURBS · meshfree) · 수치지형도 비교 방식 다섯 · 안 부르는 길 정리
- 필터 = 업체 분류 · 점진 TIN · 둘의 다듬기 판만 · 분석 허용 목록 = Ground 등록부(available_filters) 한 벌
- 표현 = DTM · implicit 만 · 평활 = DTM 만 · config 기본 목록 · PRECOMPUTE · 설정 사전 맞춤(설정 지문 바뀜 — 768 에서 다시 지음)
- 등고선 = 격자 · 굳힌 격자만(삼각망 굽기 · 외곽 맞춤 · implicit RBF 폴백 걷음) · 표본기 · 경로 찾기 tin · nurbs · meshfree 갈래 걷음
- 수치지형도 = 라플라스만(TIN 둘 · TPS · ANUDEM형 · 다중해상도 걷음) · B04 단추 하나
- 안 부르는 길 — B04 JSON 점군 GET · ground-stats GET + TS · B07 옛 계획평면도(라이다) 짓기 · lidar_source · LIDAR_ID · plan_lidar 종류 이름 · SOURCE_FILTER_LABELS · MODEL_METHODS(py) · MESH_* 설정
- 옛 manifest 의 지운 갈래 줄은 DB 로 되살리지 않음 · 옛 csf 행은 미리보기 · 등고선 그대로 열림 · 지운 이름 요청 = 400
- 낡은 글 — Ground · Pipeline 머리 · WF1 「없으면 csf」 · B07 음영 기본 「csf」 → 빈 값 · 등고선 폴백 「csf」 → 404 · B04 3D 보기 처음값 · B05 Schema · B06 Prebuild · 씨닝 설명
- 시험 지움 2 — test_b04_contour_raster · test_b04_contour_equivalence(지운 삼각망 굽기 · 없는 프로젝트를 봐 건너뛰던 것)
- 시험 덜어냄 — test_b04_ground_filters CSF · 격자 최저 네 경우(지운 필터)
- 시험 고침 — 허용 목록 · 형 검사에 걸린 csf · tin · grid_min_z · meshfree · tin_sheet 견본을 ptd · implicit · laplace 로(54_22 · 238_1 · 443 · 342_2 · 346_3 · 351_1 · 442_2 · 271 + helper · 344_1 · 133_1 · 343_2 · 637 · 754_1 · 763_1 깨진 tin → 깨진 implicit · 763_3 · b05 둘 · b06 · sheet_surface TIN(도엽선) 경우 지움) · 이름만 바꿈(273_2 · 343_1 · 343_3 · 624_1)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 16:35:12 +09:00

198 lines
8.1 KiB
Python

"""343-3 B04 다시 열기 받는 양 — 보관함 저장 실패 고침 · 점군 고정 표본 이진 · 켤 때 받기 · ETag.
`helper_343_3_asset_cache.cjs` = 진짜 TS(b_asset_cache · decodePointPreview) + 가짜 IndexedDB.
"""
from __future__ import annotations
import json
import subprocess
from pathlib import Path
import numpy as np
import pytest
import B04_PreProcess.B04_PreProcess_Engine_PointPreview as preview
ROOT = Path(__file__).resolve().parents[2]
HERE = Path(__file__).resolve().parent
def _npz(path: Path, count: int, rgb: bool = True) -> tuple[np.ndarray, np.ndarray]:
rng = np.random.default_rng(3)
xyz = np.column_stack(
(
rng.uniform(200000, 200100, count),
rng.uniform(500000, 500100, count),
rng.uniform(90, 120, count),
)
).astype(np.float32)
colors = rng.integers(0, 65535, (count, 3)).astype(np.uint16)
bounds = np.array([[200000, 200100], [500000, 500100], [90, 120]], dtype=np.float64)
arrays = {"xyz": xyz, "bounds": bounds}
if rgb:
arrays["rgb"] = colors
np.savez_compressed(path, **arrays)
return xyz, colors
def _decode(path: Path) -> tuple[dict, np.ndarray, np.ndarray | None]:
raw = path.read_bytes()
length = int.from_bytes(raw[:4], "little")
head = json.loads(raw[4 : 4 + length])
offset = -(-(4 + length) // 4) * 4
count = head["sampled_count"]
xyz = np.frombuffer(raw, "<f4", count * 3, offset).reshape(-1, 3)
rgb = None
if head["has_rgb"]:
rgb = np.frombuffer(raw, "<u2", count * 3, offset + count * 12).reshape(-1, 3)
return head, xyz, rgb
def test_고정_표본은_JSON_길과_같은_점(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(preview, "POINT_CLOUD_SAMPLE_LIMIT", 50)
source = tmp_path / "structured.npz"
xyz, colors = _npz(source, 200)
head, sample, rgb = _decode(preview.ensure_point_preview(source, None))
# 옛 JSON 길과 같은 규칙(같은 씨앗 · 같은 float32)
indexes = np.random.default_rng(20260710).choice(200, 50, replace=False)
assert head["point_count"] == 200 and head["sampled_count"] == 50
assert np.array_equal(sample, xyz[indexes]) and np.array_equal(rgb, colors[indexes])
assert head["bounds"]["x_min"] == 200000 and head["bounds"]["z_max"] == 120
def test_원천이_그대로면_다시_안_만듦(tmp_path: Path) -> None:
source = tmp_path / "ground_points_ptd.npz"
_npz(source, 30, rgb=False)
target = preview.ensure_point_preview(source, "ptd")
assert target.name == "preview_points_ptd.bin"
stamp = target.stat().st_mtime_ns
assert preview.ensure_point_preview(source, "ptd").stat().st_mtime_ns == stamp
_npz(source, 40, rgb=False)
head, _, rgb = _decode(preview.ensure_point_preview(source, "ptd"))
assert head["sampled_count"] == 40 and rgb is None
@pytest.fixture(scope="module")
def out(tmp_path_factory: pytest.TempPathFactory) -> dict:
folder = tmp_path_factory.mktemp("p343")
source = folder / "structured.npz"
xyz, colors = _npz(source, 25)
target = preview.ensure_point_preview(source, None)
proc = subprocess.run(
["node", str(HERE / "helper_343_3_asset_cache.cjs"), str(target)],
capture_output=True,
text=True,
encoding="utf-8",
timeout=60,
)
assert proc.returncode == 0, proc.stderr
data = json.loads(proc.stdout)
data["expected"] = {
"first": xyz[0].tolist(),
"last": xyz[-1].tolist(),
"rgb": colors[0].tolist(),
}
return data
def test_보관함_저장_뒤_두번째는_보관본(out: dict) -> None:
store = out["store"]
assert store["stored"] and store["first"] == store["second"] == "body:http://x/a"
assert store["warnings"] == []
def test_용량_초과면_한_줄_경고_다음엔_다시_받음(out: dict) -> None:
quota = out["quota"]
assert quota["stored"] == 0 and quota["refetched"] == 1
assert len(quota["warnings"]) == 1 # 같은 까닭은 한 번만
assert "저장" in quota["warnings"][0] and "QuotaExceededError" in quota["warnings"][0]
def test_열기_실패를_끝까지_기억하지_않음(out: dict) -> None:
retry = out["retry"]
assert retry["missed"] is None and retry["hit"] == "v" and retry["opens"] == 2
assert any("열기" in line for line in retry["warnings"])
def test_다른_탭이_막아도_멈추지_않음(out: dict) -> None:
blocked = out["blocked"]
assert blocked["body"] == "body:http://x/a"
assert any("막음" in line for line in blocked["warnings"])
def test_지우기는_색인_열쇠만(out: dict) -> None:
purge = out["purge"]
assert purge["keys"] == ["P|http://x/keep"] and purge["valueReads"] == 0
def test_등고선_주소_한_벌(out: dict) -> None:
assert out["contour"] == [
"http://x/api/projects/P/surface/models/7/contour?interval=1&smooth=false&recalculate=false",
"http://x/api/projects/P/surface/models/7/contour?interval=2&smooth=true&recalculate=true",
]
source = (ROOT / "B04_PreProcess/B04_PreProcess_UI_TerrainViewer_Contours.ts").read_text(
encoding="utf-8"
)
assert "contourAssetUrl(" in source and "/contour?interval" not in source
def test_TS_가_파이썬_이진을_같은_값으로_풂(out: dict) -> None:
decoded, expected = out["preview"], out["expected"]
assert decoded["point_count"] == decoded["sampled_count"] == 25
assert decoded["first"] == expected["first"] and decoded["last"] == expected["last"]
assert decoded["rgbFirst"] == expected["rgb"]
assert decoded["bounds"]["y_max"] == 500100
def test_꺼진_층은_켤_때_받음() -> None:
viewer = (ROOT / "B04_PreProcess/B04_PreProcess_UI_MapViewer.ts").read_text(encoding="utf-8")
assert "GIS_LAYERS.filter((layer) => activeGisLayers.has(layer))" in viewer
assert "activeGisLayers.forEach((layer) => void loadLayerOnDemand(layer))" in viewer
assert len(viewer.splitlines()) <= 700
def test_남은_층은_파일_그대로_ETag() -> None:
source = (ROOT / "B04_PreProcess/B04_PreProcess_Router_GIS.py").read_text(encoding="utf-8")
start = source.index("async def get_project_geojson")
body = source[start : source.index("@tiles_router", start)]
assert "json.loads(filepath" not in body and "json.loads(simplified" not in body
assert body.rstrip().count("cached_file_response(") >= 2
def test_화면은_이진_길로_점군을_받음() -> None:
api = (ROOT / "B04_PreProcess/B04_PreProcess_Api_Fetch.ts").read_text(encoding="utf-8")
assert "/surface/point-cloud/binary" in api and "fetchCachedBytes(" in api
def test_전처리가_미리보기_이진을_한_번_만듦(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
import laspy
import B04_PreProcess.B04_PreProcess_Engine as engine
import B04_PreProcess.B04_PreProcess_Engine_SheetSurface as sheet
rng = np.random.default_rng(1)
header = laspy.LasHeader(point_format=3, version="1.2")
header.offsets = [200000, 500000, 0]
header.scales = [0.01, 0.01, 0.01]
las_data = laspy.LasData(header)
las_data.x = rng.uniform(200000, 200060, 3000)
las_data.y = rng.uniform(500000, 500060, 3000)
las_data.z = rng.uniform(100, 101, 3000)
las_data.classification = np.full(3000, 2, np.uint8)
las_path = tmp_path / "B03_FileInput" / "input" / "t.las"
las_path.parent.mkdir(parents=True)
las_data.write(las_path)
monkeypatch.setattr(engine, "download_geodata", lambda *a, **k: None)
monkeypatch.setattr(sheet, "build_sheet_surface_from_route", lambda *a, **k: [])
result = engine.run_surface_analysis(tmp_path, las_path, source_filters=None, methods=["dtm"])
processed = tmp_path / "B04_PreProcess" / "processed"
made = {path.name: path.stat().st_mtime_ns for path in processed.glob("preview_points_*.bin")}
assert "preview_points_raw.bin" in made
assert {f"preview_points_{key}.bin" for key in result["source_filters"]} <= set(made)
engine.run_surface_analysis(tmp_path, las_path, source_filters=None, methods=["dtm"])
again = {path.name: path.stat().st_mtime_ns for path in processed.glob("preview_points_*.bin")}
assert again == made