Skip to content

Find a component

Loading the directory…

↑ ↓ to select · Enter to open · Esc to closeBrowse all →
Contributor login

Geospatial

Geographic point indexing

Bijection source
git clone https://github.com/bijectionhq/geospatial.git
cd geospatial
git checkout 1b43993f3ee9b848ffe0efbf9703a43797a149e8

Bijection repository access is required. Request access.

Description

Geographic point indexing

Explore the package README for API concepts, configuration, and examples. Check the Bijection source for adaptation-specific setup.

Available as a reference

Review the source and its dependencies before adopting this package. Source availability does not establish npm publication or runtime qualification.

Getting started

  1. Get the source at the revision above and read its package documentation.
  2. Check the required Bijection SDK version, dependencies, and external service credentials.
  3. Follow the package setup instructions and the guide to using components.
  4. Exercise the component in your deployment according to your project’s verification rules.

Open source

Maintained source: bijectionhq/geospatial. Based on convex/geospatial. License: Apache-2.0. Consult the repository for license terms, notices, and contributor attribution.

From the upstream publisher

A geospatial index for Convex

Upstream overview

From the upstream package documentation.

A geospatial indexing component that enables efficient storage and querying of geographic points within Convex. You can insert points with coordinates, filter keys, and sort keys, then query for all points within rectangular regions or find the nearest points to a given location. The component supports equality filters, range filters on sort keys, and pagination through large result sets while maintaining Convex's consistency and reactivity guarantees.

Use cases

From the upstream package documentation.

  • Location-based app features where you need to find nearby restaurants, stores, or services within a user's viewport on a map
  • Fleet management systems that track vehicle locations and need to efficiently query which vehicles are in specific geographic regions
  • Real estate platforms filtering properties by price range within map boundaries, with results sorted by price or distance
  • Event discovery apps finding concerts, meetups, or activities within walking distance of a user's location
  • Asset tracking applications monitoring equipment or inventory across geographic regions with complex filtering requirements

How it works

From the upstream package documentation.

The component installs as a Convex component and provides a GeospatialIndex class that wraps the underlying geospatial storage. You insert points using the insert() method with coordinates, optional filter keys for querying, and sort keys for result ordering. The component stores points in an indexed structure that enables efficient rectangular and nearest-neighbor queries.

For querying, the query() method accepts rectangular shapes and supports filter conditions including eq() for exact matches, in() for set membership, and range conditions like gte() and lt() on sort keys. The nearest() method finds closest points to a query location with optional maximum distance constraints. Both query types return paginated results with cursor-based continuation for large datasets.

The component integrates with Convex's mutation and query functions, maintaining consistency and reactivity. All geographic computations happen server-side using the indexed data structure, avoiding expensive client-side distance calculations while leveraging Convex's automatic caching and real-time updates.

Upstream package FAQ

What coordinate system does the Geospatial component use?

The Convex Geospatial component uses the WGS84 coordinate system with latitude and longitude values in decimal degrees. This is the standard system used by GPS and most mapping services, making it compatible with location data from mobile devices and mapping APIs.

Can I query points within custom polygon shapes?

Yes, the Geospatial component supports querying points within custom polygon regions defined by coordinate arrays. You can define irregular shapes like delivery zones, administrative boundaries, or custom service areas and efficiently find all points contained within them.

How does spatial indexing work with Convex reactivity?

The Geospatial component's spatial queries work seamlessly with Convex's reactive subscriptions. When geographic data changes, components automatically re-render with updated results. The spatial indexing optimizes query performance while maintaining real-time reactivity for location-based features.

What's the performance difference between radius and polygon queries?

Radius queries in the Geospatial component are generally faster than polygon queries since they use simpler distance calculations. Complex polygons with many vertices require more computation for point-in-polygon tests, but the component uses optimized algorithms to minimize performance impact for typical use cases.

Use with agents and CLI

Give your coding agent the component’s source identity and setup guidance.

agent-prompt.md
Help me integrate Geospatial with Bijection.

Read: https://bijection.com/components/geospatial/SKILL.md

Check SDK compatibility and dependency availability.
Follow this project's execution and verification rules.

Upstream README.md

View repository ↗

Documentation from convex/geospatial, recorded October 23, 2026. APIs and setup may differ in the Bijection adaptation.

This component adds a geospatial index to Convex, allowing you to efficiently store and query points on the Earth's surface.

  • Insert points into the geospatial key value store along with their geographic coordinates.
  • Efficiently query for all points within a given rectangle on the sphere.
  • Control the sort order for the results with a custom sorting key.
  • Filter query results with equality and IN clauses.
  • And since it's built on Convex, everything is automatically consistent, reactive, and cached!

This component is currently in beta. It's missing some functionality, but what's there should work. We've tested the example app up to about 1,000,000 points, so reach out if you're using a much larger dataset. If you find a bug or have a feature request, you can file it here.

Pre-requisite: Convex

You'll need an existing Convex project to use the component. Convex is a hosted backend platform, including a database, serverless functions, and a ton more you can learn about here.

Run npm create convex or follow any of the quickstarts to set one up.

Installation

First, add @convex-dev/geospatial to your Convex project:

npm install @convex-dev/geospatial

Then, install the component into your Convex project within the convex/convex.config.ts file:

// convex/convex.config.ts
import geospatial from "@convex-dev/geospatial/convex.config.js";
import { defineApp } from "convex/server";

const app = defineApp();
app.use(geospatial);

export default app;

Finally, create a new GeospatialIndex within your convex/ folder, and point it to the installed component:

// convex/index.ts
import { GeospatialIndex } from "@convex-dev/geospatial";
import { components } from "./_generated/api";

const geospatial = new GeospatialIndex(components.geospatial);

Inserting points

After installing the component, you can insert, get, and remove points from the index. You can specify a filterKeys record for filtering at query time and optionally a sortKey for the query result order. We currently only support ascending order on the sortKey.

// convex/index.ts

const example = mutation({
  handler: async (ctx) => {
    const cityId = await ctx.db.insert("cities", {...});
    await geospatial.insert(
      ctx,
      "American Museum of Natural History",
      {
        latitude: 40.7813,
        longitude: -73.9737,
      },
      { category: "museum" },
      28.0, // Price used as the sort key
    );
    const result = await geospatial.get(ctx, cityId);
    await geospatial.remove(ctx, cityId);
  },
});

If you would like some more typesafety, you can specify a type argument for the GeospatialIndex class. This will also provide you with auto-complete for the filterKeys and sortKey parameters. Above the key was "American Museum of Natural History" but most commonly the key will be an ID in another table of yours.

// convex/index.ts
import { GeospatialIndex, Point } from "@convex-dev/geospatial";
import { components } from "./_generated/api";
import { Id } from "./_generated/dataModel";

export const geospatial = new GeospatialIndex<
  Id<"museums">,
  { category: string; anotherFilter?: number }
>(components.geospatial);

Querying points within a shape

After inserting some points, you can query them with the query API.

// convex/index.ts

const example = query({
  handler: async (ctx) => {
    const rectangle = {
      west: -73.9712,
      south: 40.7831,
      east: -72.9712,
      north: 41.7831,
    };
    const result = await geospatial.query(ctx, {
      shape: { type: "rectangle", rectangle },
      limit: 16,
    });
    return result;
  },
});

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.

You can optionally add filter conditions to queries.

The first type of filter condition is an in() filter, which requires that a matching document have a filter field with a value in a specified set.

// convex/index.ts

const example = query({
  handler: async (ctx) => {
    const rectangle = {
      west: -73.9712,
      south: 40.7831,
      east: -72.9712,
      north: 41.7831,
    };
    const result = await geospatial.query(ctx, {
      shape: { type: "rectangle", rectangle },
      filter: (q) => q.in("category", ["museum", "restaurant"]),
    });
    return result;
  },
});

The second type of filter condition is an eq() filter, which requires that a matching document have a filter field with a value equal to a specified value.

// convex/index.ts

const example = query({
  handler: async (ctx) => {
    const result = await geospatial.query(ctx, {
      shape: { type: "rectangle", rectangle },
      filter: (q) => q.eq("category", "museum"),
    });
    return result;
  },
});

The final type of filter condition allows you to specify ranges over the sortKey. We currently only support (optional) inclusive lower bounds and exclusive upper bounds.

// convex/index.ts

const example = query({
  handler: async (ctx) => {
    const rectangle = {
      west: -73.9712,
      south: 40.7831,
      east: -72.9712,
      north: 41.7831,
    };
    const result = await geospatial.query(ctx, {
      shape: { type: "rectangle", rectangle },
      filter: (q) => q.gte("sortKey", 10).lt("sortKey", 30),
    });
    return result;
  },
});

Queries take in a limit, which bounds the maximum number of rows returned. If this limit is hit, the query will return a nextCursor for continuation. The query may also return a nextCursor with fewer than limit results if it runs out of its IO budget while executing.

In either case, you can continue the stream by passing nextCursor to the next call's cursor parameter.

// convex/index.ts

const example = query({
  handler: async (ctx) => {
    const rectangle = {
      west: -73.9712,
      south: 40.7831,
      east: -72.9712,
      north: 41.7831,
    };
    const startCursor = undefined;
    const result = await geospatial.query(
      ctx,
      {
        shape: { type: "rectangle", rectangle },
        limit: 16,
      },
      startCursor,
    );
    if (result.nextCursor) {
      // Continue the query, starting from the first query's cursor.
      const nextResult = await geospatial.query(
        ctx,
        {
          shape: { type: "rectangle", rectangle },
          limit: 16,
        },
        result.nextCursor,
      );
      return [...result.results, ...nextResult.results];
    }
    return result.results; // { key, coordinates }[]
  },
});

Note: you typically pass the nextCursor in from a client that is paginating through results, to avoid loading too much data in a single query.

Querying the points nearest a query point

You can also query for the points closest to a given point, optionally limiting to a maximum distance (in meters).

// convex/index.ts

const example = query({
  handler: async (ctx) => {
    const maxResults = 16;
    const maxDistance = 10000;
    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 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

See example/ for a full example with a Leaflet-based frontend.

Development

Install dependencies and fire up the example app to get started.

npm install
cd example
npm install
npm run dev

The component definition is in src/ and reflects what users of the component will install. The example app, which is entirely independent, lives in example/.