s2-tools
Version:
A collection of geospatial tools primarily designed for WGS84, Web Mercator, and S2.
141 lines • 4.42 kB
JavaScript
/** A local KD key-value store */
export class KDSpatialIndex {
nodeSize;
#store = [];
/**
* @param nodeSize - the size of each kd-tree node
*/
constructor(nodeSize = 64) {
this.nodeSize = nodeSize;
}
/** @returns the length of the store */
get length() {
return this.#store.length;
}
/**
* Push a value into the store
* @param value - the value to store
*/
push(value) {
this.#store.push(value);
}
/**
* Get a value at index
* @param index - the position in the store to get the value from
* @returns the value
*/
get(index) {
return this.#store[index];
}
/**
* Get a range of values within an index range
* @param indexStart - the start index
* @param indexEnd - the end index
* @returns the values
*/
getRange(indexStart, indexEnd) {
return this.#store.slice(indexStart, indexEnd);
}
/**
* iterate through the values
* @yields an iterator
*/
*values() {
for (const value of this.#store)
yield value;
}
/** Sort the store in place */
sort() {
this.#sort(0, this.#store.length - 1, 0);
}
/**
* iterate through the values
* @returns an iterator
*/
[Symbol.iterator]() {
return this.values();
}
/** Closes the store */
close() {
this.#store = [];
}
/**
* Recursively kd-sort the store
* @param left - the leftmost index
* @param right - the rightmost index
* @param axis - 0 for x, 1 for y
*/
#sort(left, right, axis) {
if (right - left <= this.nodeSize)
return;
const m = (left + right) >> 1; // middle index
// sort ids and coords around the middle index so that the halves lie
// either left/right or top/bottom correspondingly (taking turns)
this.#select(m, left, right, axis);
// recursively kd-sort first half and second half on the opposite axis
this.#sort(left, m - 1, 1 - axis);
this.#sort(m + 1, right, 1 - axis);
}
/**
* Custom Floyd-Rivest selection algorithm: sort coords so that
* [left..k-1] items are smaller than k-th item (on either x or y axis)
* @param k - the sorting anchor index between left and right
* @param left - the leftmost index
* @param right - the rightmost index
* @param axis - 0 for x, 1 for y
*/
#select(k, left, right, axis) {
while (right > left) {
if (right - left > 600) {
const n = right - left + 1;
const m = k - left + 1;
const z = Math.log(n);
const s = 0.5 * Math.exp((2 * z) / 3);
const sd = 0.5 * Math.sqrt((z * s * (n - s)) / n) * (m - n / 2 < 0 ? -1 : 1);
const newLeft = Math.max(left, Math.floor(k - (m * s) / n + sd));
const newRight = Math.min(right, Math.floor(k + ((n - m) * s) / n + sd));
this.#select(k, newLeft, newRight, axis);
}
const p = this.#store[k];
const t = axis === 0 ? p.x : p.y;
let i = left;
let j = right;
this.#swap(left, k);
const rp = this.#store[right];
if ((axis === 0 ? rp.x : rp.y) > t)
this.#swap(left, right);
while (i < j) {
this.#swap(i, j);
i++;
j--;
while (i < this.length && (axis === 0 ? this.#store[i].x : this.#store[i].y) < t)
i++;
while (j >= 0 && (axis === 0 ? this.#store[j].x : this.#store[j].y) > t)
j--;
}
if ((axis === 0 ? this.#store[left].x : this.#store[left].y) === t) {
this.#swap(left, j);
}
else {
j++;
this.#swap(j, right);
}
if (j <= k)
left = j + 1;
if (k <= j)
right = j - 1;
}
}
/**
* swap two values given their indices
* @param i - the first index
* @param j - the second index
*/
#swap(i, j) {
const store = this.#store;
const tmp = store[i];
store[i] = store[j];
store[j] = tmp;
}
}
//# sourceMappingURL=index.js.map