Files
Aislo/common_util/common_util_surface_sampler.ts
T

83 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =============================================================================
* common_util_surface_sampler.ts
* 확정 지표면(DTM 격자) 표고를 브라우저에서 바로 읽는다 — 노선을 고치는 동안 서버 왕복 없이.
*
* ⚠ 파이썬·TS 짝 — `common_util/common_util_surface_sampler.py` 의 `DtmGridSampler.sample_xy`
* (scipy `RegularGridInterpolator` linear + 네 칸 유효) · 격자 꼴 = `dtm_grid_bytes`.
* 한쪽을 고치면 다른 쪽도 고치고 거울 시험 `resources/tester/test_344_1_surface_sampler_mirror.py`
* 를 돌림.
*
* 칸 폭이 고르지 않음(축이 float32 로 저장돼 0.97~1.09 m) — 「(x − x0) / 1 m」 셈 금지 · 축 배열을
* 이진 탐색(`searchsorted(side="right") − 1`)해 서버와 같은 칸을 봄.
* ========================================================================== */
/** 오름차순 축 · 행렬 · 유효 비트 — 서버 `dtm_grid_bytes` 를 그대로 푼 것. */
export interface DtmGrid {
x: Float64Array;
y: Float64Array;
/** 행 = y · 열 = x (`z[iy * nx + ix]`). */
z: Float32Array | Float64Array;
/** 칸마다 한 비트(행 우선 · 낮은 비트 먼저). */
validBits: Uint8Array;
}
const GRID_MAGIC = "AGRD";
const GRID_VERSION = 1;
const HEAD_BYTES = 24;
/** 서버 `dtm_grid_bytes` 이진을 푼다 — 꼴이 다르면 throw. */
export function parseDtmGrid(buffer: ArrayBuffer): DtmGrid {
const magic = String.fromCharCode(...new Uint8Array(buffer, 0, 4));
const [version, nx, ny, zBytes] = new Uint32Array(buffer, 4, 4);
if (magic !== GRID_MAGIC || version !== GRID_VERSION || (zBytes !== 4 && zBytes !== 8)) {
throw new Error("지형 격자 꼴이 다름");
}
const x = new Float64Array(buffer, HEAD_BYTES, nx);
const y = new Float64Array(buffer, HEAD_BYTES + nx * 8, ny);
const zStart = HEAD_BYTES + (nx + ny) * 8;
const z =
zBytes === 4
? new Float32Array(buffer, zStart, nx * ny)
: new Float64Array(buffer, zStart, nx * ny);
const validBits = new Uint8Array(buffer, zStart + nx * ny * zBytes, Math.ceil((nx * ny) / 8));
return { x, y, z, validBits };
}
/** `np.searchsorted(axis, value, side="right") - 1` — value 이하인 마지막 칸 번호 · 앞이면 −1. */
function cellIndex(axis: Float64Array, value: number): number {
if (!(value >= axis[0])) return -1; // NaN 도 여기(밖)
let low = 0;
let high = axis.length;
while (high - low > 1) {
const mid = (low + high) >> 1;
if (axis[mid] <= value) low = mid;
else high = mid;
}
return low;
}
function isValid(grid: DtmGrid, index: number): boolean {
return ((grid.validBits[index >> 3] >> (index & 7)) & 1) === 1;
}
/** (x, y) 표고(m) — 네 칸이 모두 유효할 때만 값 · 밖 · 빈칸 · 마지막 선 위는 NaN(임의 표고로 안 메움).
* 식 · 셈 차례 = scipy `evaluate_linear_2d`(같은 IEEE 연산 → 같은 값). */
export function heightAt(grid: DtmGrid, x: number, y: number): number {
const nx = grid.x.length;
const ix = cellIndex(grid.x, x);
const iy = cellIndex(grid.y, y);
if (ix < 0 || iy < 0 || ix >= nx - 1 || iy >= grid.y.length - 1) return NaN;
const i00 = iy * nx + ix;
const i10 = i00 + nx;
if (!isValid(grid, i00) || !isValid(grid, i00 + 1)) return NaN;
if (!isValid(grid, i10) || !isValid(grid, i10 + 1)) return NaN;
const ty = (y - grid.y[iy]) / (grid.y[iy + 1] - grid.y[iy]);
const tx = (x - grid.x[ix]) / (grid.x[ix + 1] - grid.x[ix]);
let value = 0.0;
value = value + grid.z[i00] * (1 - ty) * (1 - tx);
value = value + grid.z[i00 + 1] * (1 - ty) * tx;
value = value + grid.z[i10] * ty * (1 - tx);
value = value + grid.z[i10 + 1] * ty * tx;
return Number.isFinite(value) ? value : NaN;
}