/** * 地形高程的**短时缓存**(票 98 第二轮)。 * * ## 为什么要缓存 * * DOM 标记层每帧都要把 `(lon, lat)` 投到屏幕,而投影前要先取地面高程 * (`globe.getHeight`,一次四叉树下降)。标记位置是**静态**的,但相机一动, * 每帧都会对**每个标记**重查一遍 —— 几百个标记就是几百次四叉树下降, * 这是近景拖动时主线程的主要开销之一。 * * 实体投影那条路径早就按实体缓存了 3 秒(见 `projectMarkerEntity` 的注释); * 这里把同一口径用到"按坐标"的调用上,两条路径的语义就一致了。 * * ## 为什么用 TTL 而不是永久缓存 * * 地形瓦片是**逐步补齐**的:早期查不到高程会落到椭球面,补到之后才准。 * 所以缓存必须过期后重采样,否则标记会一直贴在错的(椭球面)高度上。 * * ## 一条硬纪律 * * **只缓存有效值**。地形还没铺到时 `getHeight` 回 `undefined`/`NaN`, * 若把这种"没取到"也缓存 3 秒,标记就会长时间停在椭球面 —— 比不缓存更糟。 */ /** 坐标键的默认精度:经纬度 5 位小数约 1m,足够合并同一栋楼上的重复查询。 */ export const HEIGHT_KEY_PRECISION = 5 /** * 经纬度 → 缓存键。 * @param {number} lon * @param {number} lat * @param {number} [precision] * @returns {string} */ export function heightKey(lon, lat, precision = HEIGHT_KEY_PRECISION) { return Number(lon).toFixed(precision) + ',' + Number(lat).toFixed(precision) } /** * 建一个高程缓存。 * * `read(key, nowMs, produce)`:TTL 内直接给缓存值;否则调 `produce()` 重采样, * 且**仅在结果有限时**写回。超过容量就整表清空(宁可重采一轮,也不做 LRU 的额外记账)。 * * @param {object} [options] * @param {number} [options.ttlMs] 过期时间(默认 3000,与实体高程缓存同口径) * @param {number} [options.maxEntries] 容量上限(默认 5000) */ export function createHeightCache({ ttlMs = 3000, maxEntries = 5000 } = {}) { const slots = new Map() return { read(key, nowMs, produce) { const slot = slots.get(key) if (slot && nowMs - slot.at < ttlMs) return slot.value const value = produce() // 只缓存有效值(见文件头):取不到就让它下一帧再试。 if (Number.isFinite(value)) { if (slots.size >= maxEntries) slots.clear() slots.set(key, { value, at: nowMs }) } return value }, size: () => slots.size, clear() { slots.clear() }, } }