diff --git a/packages/distance/package.json b/packages/distance/package.json index 52838869fc..6011ea6e12 100644 --- a/packages/distance/package.json +++ b/packages/distance/package.json @@ -97,6 +97,9 @@ "./nearest": { "default": "./nearest.js" }, + "./radial": { + "default": "./radial.js" + }, "./squared": { "default": "./squared.js" } diff --git a/packages/distance/src/index.ts b/packages/distance/src/index.ts index 0536c07c41..8ad5140e07 100644 --- a/packages/distance/src/index.ts +++ b/packages/distance/src/index.ts @@ -5,4 +5,5 @@ export * from "./haversine.js"; export * from "./knearest.js"; export * from "./manhattan.js"; export * from "./nearest.js"; +export * from "./radial.js"; export * from "./squared.js"; diff --git a/packages/distance/src/knearest.ts b/packages/distance/src/knearest.ts index 179d2e091c..0630e48466 100644 --- a/packages/distance/src/knearest.ts +++ b/packages/distance/src/knearest.ts @@ -9,7 +9,8 @@ import { DIST_SQ, DIST_SQ1, DIST_SQ2, DIST_SQ3 } from "./squared.js"; /** * A {@link INeighborhood} implementation for K-nearest neighbor queries around * a given target location, initial query radius and {@link IDistance} metric to - * determine proximity. + * determine proximity. See {@link Radial} for an unbounded and unsorted + * version. * * @remarks * The K-nearest neighbors will be accumulated via an internal diff --git a/packages/distance/src/radial.ts b/packages/distance/src/radial.ts new file mode 100644 index 0000000000..7b7f829407 --- /dev/null +++ b/packages/distance/src/radial.ts @@ -0,0 +1,138 @@ +import type { IDeref } from "@thi.ng/api"; +import { clamp0 } from "@thi.ng/math/interval"; +import type { ReadonlyVec } from "@thi.ng/vectors"; +import type { IDistance, INeighborhood, Neighbor } from "./api.js"; +import { DIST_SQ, DIST_SQ1, DIST_SQ2, DIST_SQ3 } from "./squared.js"; + +/** + * A {@link INeighborhood} implementation for radial neighbor queries around a + * given target location, initial query radius and {@link IDistance} metric to + * determine proximity. Unbounded and unsorted version of {@link KNearest}. + * + * @remarks + * Qualifying neighbors will be accumulated in order of processing via an + * internal array and can be obtained via {@link Radial.deref} or + * {@link Radial.values}. + * + * @typeParam D - spatial position for distance metric + * @typeParam T - indexed value + */ +export class Radial + implements INeighborhood, IDeref[]> +{ + protected _r!: number; + protected _items!: [number, T][]; + + constructor( + public readonly dist: IDistance, + public target: D, + public radius = Infinity + ) { + this.setRadius(radius); + } + + /** + * Clears current results. + */ + reset() { + this._items = []; + return this; + } + + /** + * Resets search/reference position and clears current results. + * + * @param target + */ + setTarget(target: D) { + this.target = target; + this.reset(); + } + + /** + * Resets search/query radius and clears current results. + * + * @param r + */ + setRadius(r: number) { + this.radius = clamp0(r); + this._r = this.dist.to(this.radius); + this.reset(); + } + + /** + * Returns an array of current neighbor result tuples (each `[dist, val]`). + * + * @remarks + * Use {@link Radial.values} to obtain result values **without** their + * distance metrics. + */ + deref() { + return this._items; + } + + /** + * Similar to {@link Radial.deref}, but returns array of result values **without** + * their distance metrics. + */ + values() { + return this._items.map((x) => x[1]); + } + + includesDistance(d: number, eucledian = true) { + return (eucledian ? this.dist.to(d) : d) <= this._r; + } + + consider(pos: D, val: T) { + const d = this.dist.metric(this.target, pos); + if (d <= this._r) { + this._items.push([d, val]); + } + return d; + } +} + +/** + * Defines a {@link Radial} instance for arbitrary length vector positions + * and, by default, using an infinite region radius and {@link DIST_SQ} distance + * metric. + * + * @param p - + * @param r - + * @param dist - + */ +export const radial = (p: ReadonlyVec, r?: number, dist = DIST_SQ) => + new Radial(dist, p, r); + +/** + * Defines a {@link Radial} instance for 2D vector positions and, by default, + * using an infinite region radius and {@link DIST_SQ2} distance metric. + * + * @param p - + * @param r - + * @param dist - + */ +export const radial2 = (p: ReadonlyVec, r?: number, dist = DIST_SQ2) => + new Radial(dist, p, r); + +/** + * Defines a {@link Radial} instance for 3D vector positions, by default, + * using an infinite region radius and {@link DIST_SQ3} distance metric. + * + * @param p - + * @param r - + * @param dist - + */ +export const radial3 = (p: ReadonlyVec, r?: number, dist = DIST_SQ3) => + new Radial(dist, p, r); + +/** + * Defines a {@link Radial} instance for numeric positions and, by default, + * using an infinite region radius and {@link DIST_SQ1} distance metric. + * + * @param p - + * @param r - + * @param dist - + */ +export const radialN = (p: number, r?: number, dist = DIST_SQ1) => + new Radial(dist, p, r);