Skip to content

fix(geo): make bounding-box task search antimeridian-aware - #94

Merged
cybermax4200 merged 1 commit into
ecotask-network:mainfrom
Paranoa-dev:fix/antimeridian-bounding-box
Aug 23, 2026
Merged

fix(geo): make bounding-box task search antimeridian-aware#94
cybermax4200 merged 1 commit into
ecotask-network:mainfrom
Paranoa-dev:fix/antimeridian-bounding-box

Conversation

@Paranoa-dev

Copy link
Copy Markdown
Contributor

Fix: Antimeridian-aware bounding-box task search

Summary

listTasks (and the latent geoService.buildBoundingBoxFilter helper) built the
geo filter as a pair of BETWEEN predicates:

where.AND = [
  { lat: { gte: swLat, lte: neLat } },
  { lng: { gte: swLng, lte: neLng } },
];

When a viewport crosses the 180° meridian — i.e. swLng > neLng (for example a
search window of 170 -> -170 covering the Pacific / date line) — the
lng BETWEEN 170 AND -170 predicate is logically impossible and matches no
rows. This made real geo tasks near ±180° longitude (and, secondarily, near the
poles) unfindable: a correctness bug for a location-based product.

Root cause

lng BETWEEN swLng AND neLng only works when swLng <= neLng. Across the
antimeridian the "low" side of the range wraps past +180 into the "high" side,
so the valid interval is actually two disjoint hemispheres:
(swLng .. 180] union [-180 .. neLng].

Changes

src/services/geoService.ts

buildBoundingBoxFilter is now antimeridian-aware. When swLng > neLng it
returns an OR of the two hemispheres instead of a single BETWEEN:

const lng =
  swLng <= neLng
    ? { gte: swLng, lte: neLng }
    : { OR: [{ gte: swLng }, { lte: neLng }] };

Non-crossing viewports keep the exact same single-range predicate, so existing
behavior is unchanged.

src/models/task.ts

listTasks now builds its geo filter through buildBoundingBoxFilter instead of
hand-rolling the AND array. This also revives the previously dead helper and
keeps the two code paths in sync. The lat predicate (poles) is left as a plain
range — poles are explicitly noted as secondary in the issue and out of scope
for this change (a future PostGIS/geography migration is the proper fix there,
as called out in the issue).

Tests

  • tests/services/geoService.test.ts: asserts the antimeridian split returns
    { lat, lng: { OR: [{ gte: 170 }, { lte: -170 }] } }, while the existing
    non-crossing assertion still passes.
  • tests/models/task.test.ts: adds a geo describe block with
    • a non-crossing window (10 -> 20) returning only the in-range task, and
    • a crossing window (170 -> -170) returning both the lng: 179 and
      lng: -179 tasks while excluding lng: 0.

Acceptance criteria

  • ✅ A task at lng: 179 with a search window 170 -> -170 is returned (covered
    by the new task.test.ts test).
  • ✅ Existing non-crossing searches are unchanged (existing geoService.test.ts
    and task.test.ts suites still pass).
  • ✅ Cursor pagination (where.OR for the cursor) continues to compose
    correctly with the new where.AND geo filter (verified by the existing
    cursor-composition test).

Verification

npx jest tests/services/geoService.test.ts tests/models/task.test.ts
# 21 passed

npx tsc --noEmit   # clean
npm run lint       # clean (husky pre-commit hook also ran eslint + prettier)

Note: tests/models/task.concurrency.test.ts requires a live database
(Prisma DATABASE_URL) and is unrelated to this change; it fails in CI
without a provisioned DB.

Out of scope

  • Switching to PostGIS / geography type for true great-circle + pole handling
    (noted as future work in the issue).
  • No API/response-shape changes; this is a query-correctness fix only.

closes #89

listTasks previously built lat/lng as a single BETWEEN predicate, which
matches nothing when a viewport crosses the 180th meridian (swLng > neLng,
e.g. 170 -> -170). geoService.buildBoundingBoxFilter now splits the
longitude range into two hemispheres (swLng..180] union [-180..neLng] via an
OR predicate, and listTasks now routes through that helper so it is no
longer dead code.

- Adds antimeridian tests in geoService.test.ts and task.test.ts
- Non-crossing searches are unchanged

closes ecotask-network#89
@cybermax4200
cybermax4200 merged commit eef5200 into ecotask-network:main Aug 23, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bounding-box task search breaks across the antimeridian and at the poles

2 participants