// grid-db/src/query/nearby.js // Grid-DB · 近邻查询 // 基于欧几里得距离的邻近查询 // PRJ-GDB-001 · Phase 1 · ZY-GDB-P1-004 // 版权:国作登字-2026-A-00037559 'use strict'; const GridCell = require('../core/grid-cell'); /** * NearbyQuery — 近邻查询 * * 本体论锚定:近邻 = 以某个格子为中心,画一个圆,找出圆内所有格子。 * 圆由中心坐标和半径定义。 * 结果按距离排序,最近的在前面。 */ class NearbyQuery { /** * @param {object} deps * @param {Map|object} deps.index 索引 * @param {object} deps.pageManager 页管理器 */ constructor({ index, pageManager }) { if (!index) { throw new Error('NearbyQuery: index 不能为空'); } if (!pageManager) { throw new Error('NearbyQuery: pageManager 不能为空'); } this._index = index; this._pageManager = pageManager; } /** * 近邻查询 * * @param {string} namespace 命名空间 * @param {number} centerX 中心 X 坐标 * @param {number} centerY 中心 Y 坐标 * @param {number} radius 搜索半径(欧几里得距离) * @param {object} [options] * @param {string} [options.layer] 层级过滤 * @param {number} [options.limit] 最大返回数 * @param {string} [options.sortBy] 排序方式(默认 'distance') * @returns {Array<{cell: GridCell, data: *, distance: number}>} */ nearby(namespace, centerX, centerY, radius, options = {}) { const { layer, limit, sortBy = 'distance' } = options; const results = []; const entries = this._getEntries(); for (const [key, pageId] of entries) { try { const cell = GridCell.fromKey(key); // 命名空间过滤 if (cell.namespace !== namespace) continue; // 层级过滤 if (layer && cell.layer !== layer) continue; // 欧几里得距离计算 const dx = cell.gridX - centerX; const dy = cell.gridY - centerY; const distance = Math.sqrt(dx * dx + dy * dy); // 半径过滤 if (distance > radius) continue; // 读取数据 const dataBuf = this._pageManager.readPage(pageId); if (dataBuf) { let data; try { data = JSON.parse(dataBuf.toString('utf-8')); } catch { data = dataBuf; } results.push({ cell, data, distance }); } } catch { // 跳过无效键格式(fromKey 解析失败),不影响其他键的查询 } } // 按距离升序排序 if (sortBy === 'distance') { results.sort((a, b) => a.distance - b.distance); } // limit if (limit && limit > 0) { return results.slice(0, limit); } return results; } /** * 获取索引中的所有条目(兼容 Map 和 BTree) * @returns {Array<[string, *]>} */ _getEntries() { if (this._index instanceof Map) { return Array.from(this._index.entries()); } if (typeof this._index.toJSON === 'function') { const json = this._index.toJSON(); return json.entries.map(e => [e.key, e.value]); } if (typeof this._index.entries === 'function') { return Array.from(this._index.entries()); } return []; } } module.exports = NearbyQuery;