Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ const example = query({
});
```

The legacy `queryNearest` helper now delegates to `nearest` and is deprecated.
It only accepts a `maxDistance` numeric argument for backwards compatibility.
New integrations should prefer `nearest`.

This query will find all points that lie within the query rectangle, sort them
in ascending `sortKey` order, and return at most 16 results.

Expand Down Expand Up @@ -265,18 +269,24 @@ const example = query({
handler: async (ctx) => {
const maxResults = 16;
const maxDistance = 10000;
const result = await geospatial.queryNearest(
ctx,
{ latitude: 40.7813, longitude: -73.9737 },
maxResults,
const result = await geospatial.nearest(ctx, {
point: { latitude: 40.7813, longitude: -73.9737 },
limit: maxResults,
maxDistance,
);
filter: (q) => q.eq("category", "coffee"),
});
return result;
},
});
```

The `maxDistance` parameter is optional, but providing it can greatly speed up
The second argument is an options object containing `point`, `limit`, and
optionally `maxDistance` and `filter`. You can combine `maxDistance` with the
same filter builder used by `query`, including `eq`, `in`, `gte`, and `lt`
conditions. These filters are enforced through the indexed `pointsByFilterKey`
range before documents are loaded, so the database does the heavy lifting and
the query avoids reading unrelated points. Pairing that with a sensible
`maxDistance` further constrains the search space and can greatly speed up
searching the index.

## Example
Expand Down
7 changes: 3 additions & 4 deletions example/convex/example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,11 @@ export const nearestPoints = query({
maxDistance: v.optional(v.number()),
},
handler: async (ctx, { point, maxRows, maxDistance }) => {
const results = await geospatial.queryNearest(
ctx,
const results = await geospatial.nearest(ctx, {
point,
maxRows,
limit: maxRows,
maxDistance,
);
});
return await Promise.all(
results.map(async (result) => {
const row = await ctx.db.get(result.key as Id<"locations">);
Expand Down
23 changes: 2 additions & 21 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,4 @@
},
"types": "./dist/client/index.d.ts",
"module": "./dist/client/index.js"
}
}
60 changes: 52 additions & 8 deletions src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,22 @@ export type GeospatialDocument<
sortKey: number;
};

export type NearestQueryOptions<
Doc extends GeospatialDocument = GeospatialDocument,
> = {
point: Point;
limit: number;
maxDistance?: number;
filter?: NonNullable<GeospatialQuery<Doc>["filter"]>;
};

/**
* @deprecated Use `NearestQueryOptions` with `nearest` instead.
*/
export type QueryNearestOptions<
Doc extends GeospatialDocument = GeospatialDocument,
> = Pick<NearestQueryOptions<Doc>, "maxDistance" | "filter">;

export interface GeospatialIndexOptions {
/**
* The minimum S2 cell level to use when querying. Defaults to 4.
Expand Down Expand Up @@ -207,29 +223,57 @@ export class GeospatialIndex<
* 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.
* @param options - The nearest query parameters.
* @returns - An array of objects with the key-coordinate pairs and their distance from the query point in meters.
*/
async queryNearest(
async nearest(
ctx: QueryCtx,
point: Point,
maxResults: number,
maxDistance?: number,
{
point,
limit,
maxDistance,
filter,
}: NearestQueryOptions<GeospatialDocument<Key, Filters>>,
) {
const filterBuilder = new FilterBuilderImpl<
GeospatialDocument<Key, Filters>
>();
if (filter) {
filter(filterBuilder);
}

const resp = await ctx.runQuery(this.component.query.nearestPoints, {
point,
maxDistance,
maxResults,
maxResults: limit,
minLevel: this.minLevel,
maxLevel: this.maxLevel,
levelMod: this.levelMod,
logLevel: this.logLevel,
filtering: filterBuilder.filterConditions,
sorting: { interval: filterBuilder.interval ?? {} },
});
return resp as { key: Key; coordinates: Point; distance: number }[];
}

/**
* Query for the nearest points to a given point.
*
* @deprecated Use `nearest(ctx, { point, limit, maxDistance, filter })` instead.
*/
async queryNearest(
ctx: QueryCtx,
point: Point,
maxResults: number,
maxDistance?: number,
) {
return this.nearest(ctx, {
point,
limit: maxResults,
maxDistance,
});
}

/**
* Debug the S2 cells that would be queried for a given rectangle.
*
Expand Down
2 changes: 2 additions & 0 deletions src/component/_generated/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import type * as lib_tupleKey from "../lib/tupleKey.js";
import type * as lib_xxhash from "../lib/xxhash.js";
import type * as query from "../query.js";
import type * as streams_cellRange from "../streams/cellRange.js";
import type * as streams_constants from "../streams/constants.js";
import type * as streams_databaseRange from "../streams/databaseRange.js";
import type * as streams_filterKeyRange from "../streams/filterKeyRange.js";
import type * as streams_intersection from "../streams/intersection.js";
Expand Down Expand Up @@ -51,6 +52,7 @@ const fullApi: ApiFromModules<{
"lib/xxhash": typeof lib_xxhash;
query: typeof query;
"streams/cellRange": typeof streams_cellRange;
"streams/constants": typeof streams_constants;
"streams/databaseRange": typeof streams_databaseRange;
"streams/filterKeyRange": typeof streams_filterKeyRange;
"streams/intersection": typeof streams_intersection;
Expand Down
8 changes: 8 additions & 0 deletions src/component/_generated/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
"query",
"internal",
{
filtering: Array<{
filterKey: string;
filterValue: string | number | boolean | null | bigint;
occur: "should" | "must";
}>;
levelMod: number;
logLevel: "DEBUG" | "INFO" | "WARN" | "ERROR";
maxDistance?: number;
Expand All @@ -155,6 +160,9 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
minLevel: number;
nextCursor?: string;
point: { latitude: number; longitude: number };
sorting: {
interval: { endExclusive?: number; startInclusive?: number };
};
},
Array<{
coordinates: { latitude: number; longitude: number };
Expand Down
Loading