// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ // ┃ Copyright (c) 2017, the Perspective Authors. ┃ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃ // ┃ This file is part of the Perspective library, distributed under the terms ┃ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ export class SpatialGrid { private _cells: Map = new Map(); private _xMin: number; private _yMin: number; private _cellSize: number; private _cols: number; constructor( xMin: number, xMax: number, yMin: number, yMax: number, cellSize: number, ) { this._xMin = xMin; this._yMin = yMin; this._cellSize = cellSize; this._cols = Math.max(1, Math.ceil((xMax - xMin) / cellSize)); } private _cellKey(cx: number, cy: number): number { return cy * this._cols + cx; } insert(index: number, x: number, y: number): void { const cx = Math.floor((x - this._xMin) / this._cellSize); const cy = Math.floor((y - this._yMin) / this._cellSize); const key = this._cellKey(cx, cy); let cell = this._cells.get(key); if (!cell) { cell = []; this._cells.set(key, cell); } cell.push(index); } /** * Find the nearest point to (dataX, dataY) within the given radius, * measured in pixel distance using the provided scale factors. */ query( dataX: number, dataY: number, radiusPx: number, pxPerDataX: number, pxPerDataY: number, xData: Float32Array, yData: Float32Array, ): number { const cellRadiusX = Math.ceil(radiusPx / pxPerDataX / this._cellSize); const cellRadiusY = Math.ceil(radiusPx / pxPerDataY / this._cellSize); const centerCX = Math.floor((dataX - this._xMin) / this._cellSize); const centerCY = Math.floor((dataY - this._yMin) / this._cellSize); let bestIdx = -1; let bestDistSq = radiusPx * radiusPx; for ( let cy = centerCY - cellRadiusY; cy <= centerCY + cellRadiusY; cy++ ) { for ( let cx = centerCX - cellRadiusX; cx <= centerCX + cellRadiusX; cx++ ) { const cell = this._cells.get(this._cellKey(cx, cy)); if (!cell) { continue; } for (const i of cell) { const dx = (xData[i] - dataX) * pxPerDataX; const dy = (yData[i] - dataY) * pxPerDataY; const distSq = dx * dx + dy * dy; if (distSq < bestDistSq) { bestDistSq = distSq; bestIdx = i; } } } } return bestIdx; } }