From ba0b1a0c41a0be5223b32d70aea3380c441f2142 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 1 Sep 2026 14:48:20 +0900 Subject: [PATCH] =?UTF-8?q?fix(B04):=20=ED=8F=AC=EC=9D=B8=ED=8A=B8?= =?UTF-8?q?=ED=81=B4=EB=9D=BC=EC=9A=B0=EB=93=9C=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=EA=B0=80=20=EC=83=88=20=ED=95=84=ED=84=B0=EB=A5=BC=20400?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=A7=89=EB=8D=98=20=EA=B2=83=EC=9D=84=20?= =?UTF-8?q?=EA=B3=A0=EC=B9=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit classification 필터를 넣은 뒤 B04 화면의 포인트클라우드 패널이 "데이터가 존재하지 않습니다"로 비었다. 원인은 화이트리스트 중복이다 — point-cloud 라우터가 필터 목록을 {"grid_min_z","csf","pmf"} 로 따로 적어 두고 있어 classification 을 400으로 막았다. GET /surface/point-cloud?filter=classification -> 400 "지원하지 않는 지면 필터입니다." 필터 등록부는 Ground._FILTERS 하나뿐이므로 available_filters() 를 쓴다. 여기 따로 적어 두면 필터가 늘 때마다 이 화면만 막힌다. 이 400이 연쇄로 3D 노선 표시까지 막았다 — 노선 평면이 referenceBounds 를 필요로 하는데 그 값이 포인트클라우드 응답에서만 왔기 때문이다. 확정 서피스 bounds(수 KB)를 먼저 받아 두도록 분리했다. 포인트클라우드는 수십 MB(실측 28MB·9.3초)라 늦거나 실패할 수 있는데, 그때 노선까지 같이 사라지면 안 된다. Co-Authored-By: Claude Opus 5 (1M context) --- B04_PreProcess/B04_PreProcess_Router.py | 5 ++++- B04_PreProcess/B04_PreProcess_UI_Page.ts | 13 ++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/B04_PreProcess/B04_PreProcess_Router.py b/B04_PreProcess/B04_PreProcess_Router.py index a1df3a3d..8615af91 100644 --- a/B04_PreProcess/B04_PreProcess_Router.py +++ b/B04_PreProcess/B04_PreProcess_Router.py @@ -22,6 +22,7 @@ from B04_PreProcess.B04_PreProcess_Engine_Extent import ( planned_route_bounds, project_epsg_from_prj, ) +from B04_PreProcess.B04_PreProcess_Engine_Ground import available_filters from B04_PreProcess.B04_PreProcess_Repository import ( clear_confirmed_surface_models, get_input_file, @@ -325,7 +326,9 @@ async def get_surface_point_cloud( source_path = structured_path if filter is not None: - if filter not in {"grid_min_z", "csf", "pmf"}: + # 필터 목록은 Ground 의 등록부 하나에서 나온다 — 여기 따로 적어 두면 + # 필터가 늘 때마다 이 화면만 400으로 막힌다(2026-09-01 실사고). + if filter not in available_filters(): return JSONResponse( status_code=400, content={"status": "error", "message": "지원하지 않는 지면 필터입니다."}, diff --git a/B04_PreProcess/B04_PreProcess_UI_Page.ts b/B04_PreProcess/B04_PreProcess_UI_Page.ts index f3154c41..41781d40 100644 --- a/B04_PreProcess/B04_PreProcess_UI_Page.ts +++ b/B04_PreProcess/B04_PreProcess_UI_Page.ts @@ -529,6 +529,14 @@ export async function renderB04Surface(root: HTMLElement): Promise { terrainViewer.setContourInterval(confirmed.contour_interval_m); renderInputFiles(inputs.files); renderStatus(status); + // 3D 좌표 환산 기준은 확정 서피스에서 먼저 받는다(수 KB). 포인트클라우드는 수십 MB라 + // 늦거나 실패할 수 있는데, 그때 노선까지 같이 사라지면 안 된다. + if (confirmed.bounds) terrainViewer.setReferenceBounds(confirmed.bounds); + // 계획노선을 3D 최고 표고 평면에도 얹는다 — 라이다가 노선을 어디까지 덮는지 + // 평면상으로 바로 보인다(2026-09-01 사용자 지시). + void fetchPlannedRoute(projectId) + .then((route) => terrainViewer.setRoute(route.points ?? [])) + .catch(() => terrainViewer.setRoute([])); viewer.setLoading("포인트 데이터 로딩 중…"); try { pointCloud = await fetchSurfacePointCloud( @@ -537,11 +545,6 @@ export async function renderB04Surface(root: HTMLElement): Promise { ); terrainViewer.setReferenceBounds(pointCloud.bounds); viewer.render(pointCloud); - // 계획노선을 3D 최고 표고 평면에도 얹는다 — 라이다가 노선을 어디까지 덮는지 - // 평면상으로 바로 보인다(2026-09-01 사용자 지시). - void fetchPlannedRoute(projectId) - .then((route) => terrainViewer.setRoute(route.points ?? [])) - .catch(() => terrainViewer.setRoute([])); // 지도(2D)는 계획노선 기준으로 연다 — 라이다 범위와 다루는 범위가 다르다. mapViewer.render(projectId, confirmed.route_bounds); } catch {