From a7f09a827e4f2a71d60f8a38ffa96181ecfa46bd Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 2 Aug 2026 00:00:44 +0900 Subject: [PATCH] =?UTF-8?q?fix(B04/B05):=20=EC=84=B8=EB=B6=80=EC=9C=A0?= =?UTF-8?q?=EC=97=AD=20=EC=84=9C=ED=81=B4=20=EB=B2=88=ED=98=B8=EB=A5=BC=20?= =?UTF-8?q?=EC=B5=9C=EC=83=81=EB=8B=A8=20=EC=98=A4=EB=B2=84=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EB=A1=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 채움과 함께 그리던 번호가 등고선·흐름 화살표·관 마커에 가려 읽히지 않았다. 번호만 떼어내 프레임 맨 마지막에 얹는다. - MapRender에 drawRingBadge()·ringCenterOnScreen()을 분리 노출. drawFilledRing은 라벨이 있을 때 drawRingBadge를 부르는 형태로 바뀐다. - B04: drawBasinNumbers() 호출을 관 마커 뒤로 이동(관이 없어도 번호는 그린다). - B05: 채움은 라벨 없이 그리고 배지 자리만 모아 두었다가 배관 마커 뒤에 그린다. Co-Authored-By: Claude Opus 5 (1M context) --- B04_wf1_Surface/B04_wf1_Surface_UI_Basins.ts | 12 ++++-- .../B04_wf1_Surface_UI_MapRender.ts | 37 +++++++++++++++++-- .../B05_wf2_Route_UI_Drainage_Render.ts | 26 ++++++++++++- 3 files changed, 66 insertions(+), 9 deletions(-) diff --git a/B04_wf1_Surface/B04_wf1_Surface_UI_Basins.ts b/B04_wf1_Surface/B04_wf1_Surface_UI_Basins.ts index c13700b7..4f9ad460 100644 --- a/B04_wf1_Surface/B04_wf1_Surface_UI_Basins.ts +++ b/B04_wf1_Surface/B04_wf1_Surface_UI_Basins.ts @@ -495,10 +495,12 @@ export function createDetailBasinOverlay(onChange: () => void): DetailBasinOverl draw(context, normalizer, view) { normalizerRef = normalizer; if (!shown) return; + // 유역 번호는 **맨 마지막에** 얹는다 — 채움과 함께 그리면 관 마커에 가려진다 + // (2026-08-01 사용자 지시). 자리는 채움을 그리면서 같이 모아 둔다. + const labels: Array<{ index: number; number: number; point: [number, number] }> = []; if (normalizer && basins.length > 0) { // 유역 번호를 얹을 자리는 폴리곤을 그리면서 같이 모은다 — 화면 좌표를 두 번 계산하지 // 않는다. 번호는 채움 위에 한꺼번에 얹어야 이웃 유역 채움에 덮이지 않는다. - const labels: Array<{ index: number; number: number; point: [number, number] }> = []; context.save(); context.lineJoin = "round"; basins.forEach((basin, index) => { @@ -527,9 +529,11 @@ export function createDetailBasinOverlay(onChange: () => void): DetailBasinOverl if (!muted) labels.push({ index, number: basin.index, point: ringCentroid(ring) }); }); context.restore(); - drawBasinNumbers(context, labels); } - if (pipes.length === 0) return; + if (pipes.length === 0) { + drawBasinNumbers(context, labels); + return; + } context.save(); context.textAlign = "center"; context.textBaseline = "middle"; @@ -550,6 +554,8 @@ export function createDetailBasinOverlay(onChange: () => void): DetailBasinOverl context.fillText(String(index + 1), x, y); }); context.restore(); + // 유역 번호는 맨 마지막 — 관 마커에도 가리지 않게 한다(2026-08-01 사용자 지시). + drawBasinNumbers(context, labels); }, dispose() { requestSequence += 1; diff --git a/B04_wf1_Surface/B04_wf1_Surface_UI_MapRender.ts b/B04_wf1_Surface/B04_wf1_Surface_UI_MapRender.ts index 4905c2ba..f624ac39 100644 --- a/B04_wf1_Surface/B04_wf1_Surface_UI_MapRender.ts +++ b/B04_wf1_Surface/B04_wf1_Surface_UI_MapRender.ts @@ -604,9 +604,21 @@ export function drawFilledRing( context.lineWidth = 1.6; context.stroke(); if (!entry.label) return; - // 면적 중심(정점 평균)에 번호를 원형 배지로 얹는다. - const centerX = sumX / entry.ring.length; - const centerY = sumY / entry.ring.length; + drawRingBadge(context, [sumX / entry.ring.length, sumY / entry.ring.length], color, entry.label); +} + +/** 유역 번호 배지 한 개(원 + 숫자). 화면 좌표를 그대로 받는다. + * + * 채움과 따로 떼어 둔 이유: 번호는 **맨 위에** 있어야 한다. 채움과 함께 그리면 그 위에 + * 얹히는 등고선·화살표·관 마커에 가려 읽을 수 없다(2026-08-01 사용자 지시). */ +export function drawRingBadge( + context: CanvasRenderingContext2D, + center: readonly [number, number], + color: string, + label: string, +): void { + const [centerX, centerY] = center; + context.save(); context.beginPath(); context.arc(centerX, centerY, 11, 0, Math.PI * 2); context.fillStyle = color; @@ -618,7 +630,24 @@ export function drawFilledRing( context.textAlign = "center"; context.textBaseline = "middle"; context.fillStyle = themeColor("--map-label-text", "#1f2937"); - context.fillText(entry.label, centerX, centerY); + context.fillText(label, centerX, centerY); + context.restore(); +} + +/** 폴리곤 정점 평균의 화면 좌표 — 배지를 얹을 자리. */ +export function ringCenterOnScreen( + ring: ReadonlyArray, + normalizer: Normalizer, + view: ViewState, +): [number, number] { + const affine = affineOf(view); + let sumX = 0; + let sumY = 0; + ring.forEach(([lon, lat]) => { + sumX += ((lon - normalizer.lonMin) / normalizer.lonRange) * affine.ax + affine.bx; + sumY += (1 - (lat - normalizer.latMin) / normalizer.latRange) * affine.ay + affine.by; + }); + return [sumX / ring.length, sumY / ring.length]; } /** 상류 세류망 강조 — B04 분석 오버레이와 B05 배수유역도가 같은 굵기·색으로 그린다. */ diff --git a/B05_wf2_Route/B05_wf2_Route_UI_Drainage_Render.ts b/B05_wf2_Route/B05_wf2_Route_UI_Drainage_Render.ts index 59b8b9d2..c1121a29 100644 --- a/B05_wf2_Route/B05_wf2_Route_UI_Drainage_Render.ts +++ b/B05_wf2_Route/B05_wf2_Route_UI_Drainage_Render.ts @@ -12,6 +12,8 @@ import { drawFilledRing, + drawRingBadge, + ringCenterOnScreen, drawPreparedLayer, drawRidgeRing, drawUpstreamLines, @@ -66,13 +68,23 @@ export function drawDrainageScene( scene: DrainageScene, ): void { const { meta, normalizer } = scene; + // 유역 번호는 **맨 위에** 얹는다 — 채움과 함께 그리면 등고선·화살표·관 마커에 가려진다 + // (2026-08-01 사용자 지시). 자리는 채움을 그리면서 같이 모아 둔다. + const badges: Array<{ center: [number, number]; color: string; label: string }> = []; // 세부유역 채움을 가장 아래에 깔아 등고선·세류 판독을 가리지 않게 한다. if (normalizer) { scene.basins.forEach((basin) => { const color = basinColor(basin.index); + if (basin.polygon_lonlat.length >= 3) { + badges.push({ + center: ringCenterOnScreen(basin.polygon_lonlat, normalizer, view), + color, + label: String(basin.index), + }); + } drawFilledRing( context, - { ring: basin.polygon_lonlat, label: String(basin.index) }, + { ring: basin.polygon_lonlat }, normalizer, view, // 하나를 고르면 나머지는 옅게 물러난다. @@ -111,6 +123,7 @@ export function drawDrainageScene( } if (!meta) { scene.pipeEditor.draw(context, view, scene.pipeColor); + drawBadges(context, badges); return; } const projector = createMetricProjector(meta, view); @@ -141,6 +154,15 @@ export function drawDrainageScene( scene.maxHotspotArea, ); } - // 배관(관 매설) 마커 — 계획선 위 최상단. + // 배관(관 매설) 마커 — 계획선 위. scene.pipeEditor.draw(context, view, scene.pipeColor); + // 유역 번호 — 무엇에도 가리지 않게 맨 마지막. + drawBadges(context, badges); +} + +function drawBadges( + context: CanvasRenderingContext2D, + badges: ReadonlyArray<{ center: [number, number]; color: string; label: string }>, +): void { + badges.forEach(({ center, color, label }) => drawRingBadge(context, center, color, label)); }