fix(B04/B05): 세부유역 서클 번호를 최상단 오버레이로
채움과 함께 그리던 번호가 등고선·흐름 화살표·관 마커에 가려 읽히지 않았다. 번호만 떼어내 프레임 맨 마지막에 얹는다. - MapRender에 drawRingBadge()·ringCenterOnScreen()을 분리 노출. drawFilledRing은 라벨이 있을 때 drawRingBadge를 부르는 형태로 바뀐다. - B04: drawBasinNumbers() 호출을 관 마커 뒤로 이동(관이 없어도 번호는 그린다). - B05: 채움은 라벨 없이 그리고 배지 자리만 모아 두었다가 배관 마커 뒤에 그린다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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<readonly [number, number]>,
|
||||
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 배수유역도가 같은 굵기·색으로 그린다. */
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user