@convex-dev/geospatial
Version:
A geospatial index for Convex
159 lines • 6.36 kB
JavaScript
import { point, rectangle } from "../component/types.js";
import { FilterBuilderImpl } from "./query.js";
export { point, rectangle };
if (typeof Convex === "undefined") {
throw new Error("this is Convex backend code, but it's running somewhere else!");
}
export const DEFAULT_MIN_LEVEL = 4;
export const DEFAULT_MAX_LEVEL = 16;
export const DEFAULT_MAX_CELLS = 8;
export const DEFAULT_LEVEL_MOD = 2;
export class GeospatialIndex {
component;
logLevel;
minLevel;
maxLevel;
levelMod;
maxCells;
/**
* Create a new geospatial index, powered by S2 and Convex. This index maps unique string keys to geographic coordinates
* on the Earth's surface, with the ability to efficiently query for all keys within a given geographic area.
*
* @param component - The registered geospatial index from `components`.
* @param options - The options to configure the index.
*/
constructor(component, options) {
this.component = component;
let DEFAULT_LOG_LEVEL = "INFO";
if (process.env.GEOSPATIAL_LOG_LEVEL) {
if (!["DEBUG", "INFO", "WARN", "ERROR"].includes(process.env.GEOSPATIAL_LOG_LEVEL)) {
console.warn(`Invalid log level (${process.env.GEOSPATIAL_LOG_LEVEL}), defaulting to "INFO"`);
}
DEFAULT_LOG_LEVEL = process.env.GEOSPATIAL_LOG_LEVEL;
}
this.logLevel = options?.logLevel ?? DEFAULT_LOG_LEVEL;
this.minLevel = options?.minLevel ?? DEFAULT_MIN_LEVEL;
this.maxLevel = options?.maxLevel ?? DEFAULT_MAX_LEVEL;
this.levelMod = options?.levelMod ?? DEFAULT_LEVEL_MOD;
this.maxCells = options?.maxCells ?? DEFAULT_MAX_CELLS;
}
/**
* Insert a new key-coordinate pair into the index.
*
* @param ctx - The Convex mutation context.
* @param key - The unique string key to associate with the coordinate.
* @param coordinates - The geographic coordinate `{ latitude, longitude }` to associate with the key.
* @param filterKeys - The filter keys to associate with the key.
* @param sortKey - The sort key to associate with the key, defaults to a randomly generated number.
*/
async insert(ctx, key, coordinates, filterKeys, sortKey) {
await ctx.runMutation(this.component.document.insert, {
document: {
key,
coordinates,
filterKeys,
sortKey: sortKey ?? Math.random(),
},
minLevel: this.minLevel,
maxLevel: this.maxLevel,
levelMod: this.levelMod,
maxCells: this.maxCells,
});
}
/**
* Retrieve the coordinate associated with a specific key.
*
* @param ctx - The Convex query context.
* @param key - The unique string key to retrieve the coordinate for.
* @returns - The geographic coordinate `{ latitude, longitude }` associated with the key, or `null` if the key is not found.
*/
async get(ctx, key) {
const result = await ctx.runQuery(this.component.document.get, { key });
return result;
}
/**
* Remove a key-coordinate pair from the index.
*
* @param ctx - The Convex mutation context.
* @param key - The unique string key to remove from the index.
* @returns - `true` if the key was found and removed, `false` otherwise.
*/
async remove(ctx, key) {
return await ctx.runMutation(this.component.document.remove, {
key,
minLevel: this.minLevel,
maxLevel: this.maxLevel,
levelMod: this.levelMod,
maxCells: this.maxCells,
});
}
/**
* Query for keys within a given shape.
*
* @param ctx - The Convex query context.
* @param query - The query to execute.
* @param cursor - The continuation cursor to use for paginating through results.
* @returns - An array of objects with the key-coordinate pairs and optionally a continuation cursor.
*/
async query(ctx, query, cursor = undefined) {
const filterBuilder = new FilterBuilderImpl();
if (query.filter) {
query.filter(filterBuilder);
}
const resp = await ctx.runQuery(this.component.query.execute, {
query: {
rectangle: query.shape.rectangle,
filtering: filterBuilder.filterConditions,
sorting: { interval: filterBuilder.interval ?? {} },
maxResults: query.limit ?? 64,
},
cursor,
minLevel: this.minLevel,
maxLevel: this.maxLevel,
levelMod: this.levelMod,
maxCells: this.maxCells,
logLevel: this.logLevel,
});
return resp;
}
/**
* Query for the nearest points to a given point.
*
* @param ctx - The Convex query context.
* @param point - The point to query for.
* @param maxResults - The maximum number of results to return.
* @param maxDistance - The maximum distance to return results within in meters.
* @returns - An array of objects with the key-coordinate pairs and their distance from the query point in meters.
*/
async queryNearest(ctx, point, maxResults, maxDistance) {
const resp = await ctx.runQuery(this.component.query.nearestPoints, {
point,
maxDistance,
maxResults,
minLevel: this.minLevel,
maxLevel: this.maxLevel,
levelMod: this.levelMod,
logLevel: this.logLevel,
});
return resp;
}
/**
* Debug the S2 cells that would be queried for a given rectangle.
*
* @param ctx - The Convex query context.
* @param rectangle - The geographic area to query.
* @param maxResolution - The maximum resolution to use when querying.
* @returns - An array of S2 cell identifiers and their vertices.
*/
async debugCells(ctx, rectangle, maxResolution) {
const resp = await ctx.runQuery(this.component.query.debugCells, {
rectangle,
minLevel: this.minLevel,
maxLevel: maxResolution ?? this.maxLevel,
levelMod: this.levelMod,
maxCells: this.maxCells,
});
return resp;
}
}
//# sourceMappingURL=index.js.map