fix(hns): 확산 히트맵 우측 경계 직선 절단 현상 수정 및 시뮬레이션 영역 확장
This commit is contained in:
부모
509e4e6584
커밋
f4f04af8aa
@ -535,23 +535,23 @@ export function MapView({
|
||||
|
||||
if (filtered.length === 0) return null;
|
||||
|
||||
// 경위도 바운드 계산
|
||||
let minLon = Infinity,
|
||||
maxLon = -Infinity,
|
||||
minLat = Infinity,
|
||||
maxLat = -Infinity;
|
||||
// 경위도 바운드 계산 (raw 값 별도 보존 — 캡 마스크 좌표 계산에 사용)
|
||||
let rawMinLon = Infinity,
|
||||
rawMaxLon = -Infinity,
|
||||
rawMinLat = Infinity,
|
||||
rawMaxLat = -Infinity;
|
||||
for (const p of dispersionHeatmap) {
|
||||
if (p.lon < minLon) minLon = p.lon;
|
||||
if (p.lon > maxLon) maxLon = p.lon;
|
||||
if (p.lat < minLat) minLat = p.lat;
|
||||
if (p.lat > maxLat) maxLat = p.lat;
|
||||
if (p.lon < rawMinLon) rawMinLon = p.lon;
|
||||
if (p.lon > rawMaxLon) rawMaxLon = p.lon;
|
||||
if (p.lat < rawMinLat) rawMinLat = p.lat;
|
||||
if (p.lat > rawMaxLat) rawMaxLat = p.lat;
|
||||
}
|
||||
const padLon = (maxLon - minLon) * 0.02;
|
||||
const padLat = (maxLat - minLat) * 0.02;
|
||||
minLon -= padLon;
|
||||
maxLon += padLon;
|
||||
minLat -= padLat;
|
||||
maxLat += padLat;
|
||||
const padLon = (rawMaxLon - rawMinLon) * 0.02;
|
||||
const padLat = (rawMaxLat - rawMinLat) * 0.02;
|
||||
const minLon = rawMinLon - padLon;
|
||||
const maxLon = rawMaxLon + padLon;
|
||||
const minLat = rawMinLat - padLat;
|
||||
const maxLat = rawMaxLat + padLat;
|
||||
|
||||
// 캔버스에 농도 이미지 렌더링
|
||||
const W = 1200,
|
||||
@ -596,6 +596,38 @@ export function MapView({
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
// 우측 끝 반타원 캡 마스크 — 격자 경계에서 직선으로 잘리는 현상 처리
|
||||
const tipX = ((rawMaxLon - minLon) / (maxLon - minLon)) * W;
|
||||
const lonExtent = rawMaxLon - rawMinLon;
|
||||
const rightThreshLon = rawMaxLon - lonExtent * 0.1;
|
||||
const rightPts = filtered.filter((p) => p.lon >= rightThreshLon);
|
||||
let capCenterY = H / 2;
|
||||
let capHalfH = H / 3;
|
||||
if (rightPts.length > 0) {
|
||||
const ys = rightPts.map((p) => (1 - (p.lat - minLat) / (maxLat - minLat)) * H);
|
||||
const minY = Math.min(...ys);
|
||||
const maxY = Math.max(...ys);
|
||||
capCenterY = (minY + maxY) / 2;
|
||||
capHalfH = (maxY - minY) / 2 + 15;
|
||||
}
|
||||
// capStartX: 타원 중심 (tipX에서 타원 x반경만큼 왼쪽)
|
||||
// 마스크 = 사각형(0..capStartX) + 반타원(capStartX..tipX)
|
||||
// → tipX 오른쪽으로 타원이 넘어가지 않아 실제 클리핑이 발생함
|
||||
const capRadiusX = capHalfH * 0.7;
|
||||
const capStartX = Math.max(0, tipX - capRadiusX);
|
||||
const maskCanvas = document.createElement('canvas');
|
||||
maskCanvas.width = W;
|
||||
maskCanvas.height = H;
|
||||
const mctx = maskCanvas.getContext('2d')!;
|
||||
mctx.fillStyle = 'black';
|
||||
mctx.fillRect(0, 0, capStartX, H);
|
||||
mctx.beginPath();
|
||||
mctx.ellipse(capStartX, capCenterY, capRadiusX, capHalfH, 0, -Math.PI / 2, Math.PI / 2);
|
||||
mctx.fill();
|
||||
ctx.globalCompositeOperation = 'destination-in';
|
||||
ctx.drawImage(maskCanvas, 0, 0);
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
|
||||
// Canvas → data URL 변환 (interleaved MapboxOverlay에서 HTMLCanvasElement 직접 전달 시 렌더링 안 되는 문제 회피)
|
||||
const imageUrl = canvas.toDataURL('image/png');
|
||||
return {
|
||||
|
||||
@ -196,7 +196,7 @@ export function HNSView() {
|
||||
const advectMax = meteo.windSpeed * 3600; // 1시간 이류 거리
|
||||
const L = Math.max(10_000, Math.min(50_000, advectMax));
|
||||
const sim: SimParams = {
|
||||
xRange: [-L * 0.05, L],
|
||||
xRange: [-L * 0.05, L * 1.3],
|
||||
yRange: [-L * 0.4, L * 0.4],
|
||||
nx: 400,
|
||||
ny: 320,
|
||||
|
||||
@ -446,8 +446,8 @@ function extractContourSegments(
|
||||
const segments: Array<[[number, number], [number, number]]> = [];
|
||||
if (threshold <= 0) return segments;
|
||||
|
||||
for (let j = 0; j < ny - 1; j++) {
|
||||
for (let i = 0; i < nx - 1; i++) {
|
||||
for (let j = 1; j < ny - 2; j++) {
|
||||
for (let i = 1; i < nx - 2; i++) {
|
||||
const bl = ppmGrid[j * nx + i];
|
||||
const br = ppmGrid[j * nx + (i + 1)];
|
||||
const tr = ppmGrid[(j + 1) * nx + (i + 1)];
|
||||
|
||||
불러오는 중...
Reference in New Issue
Block a user