Compare commits

...

1 커밋

3개의 변경된 파일50개의 추가작업 그리고 18개의 파일을 삭제

파일 보기

@ -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)];