latlng

Proximity alerts with roaming geofences

Roaming geofences fire when two moving objects get close to each other. You'll alert when a driver comes within 150 m of a waiting rider — once, without polling.

Intermediate · about 10 minutes · tested against latlng v0.2.0

Before you start

  • latlng-server running locally.
  • Node.js 22+ and npm install @latlng/sdk for the listener.

01 How roaming geofences work

A static geofence has a fixed area. A roaming geofence is relative: it fires when an object in one collection comes within a distance of any object in another collection (or the same one), and both sides can move.

Typical uses: a driver approaching a waiting rider, two vehicles getting too close on a yard, a technician arriving near a customer, or a tracked trailer drifting away from its tractor.

02 Create the collections

Drivers live in fleet-hamburg; riders waiting for pickup live in riders. A rider object only needs to exist while the request is open — give it an expire_seconds so abandoned requests clean themselves up.

Requests
curl -s -X POST http://127.0.0.1:7421/collections/fleet-hamburg
curl -s -X POST http://127.0.0.1:7421/collections/riders

curl -s -X POST http://127.0.0.1:7421/collections/riders/objects/rider-42 \
  -H 'content-type: application/json' \
  -d '{"object":{"Point":{"lat":53.5503,"lon":9.9920,"z":null}},"expire_seconds":900}'

03 Register a roaming geofence

The Roam query watches the fleet-hamburg collection and compares every update against objects in target_collection whose IDs match target_pattern. With nodwell enabled, you get one event when a driver comes within range — not a new one on every update while they wait nearby.

This example uses a channel. The same definition works as a hook with an endpoint if you want the alert delivered as a durable webhook.

Request
curl -s -X POST http://127.0.0.1:7421/channels \
  -H 'content-type: application/json' \
  -d '{
    "name": "driver-near-rider",
    "def": {
      "collection": "fleet-hamburg",
      "query": {
        "Roam": {
          "target_collection": "riders",
          "target_pattern": "*",
          "meters": 150,
          "options": {},
          "nodwell": true
        }
      },
      "detect": ["Roam"],
      "commands": ["Set"]
    }
  }'

04 Listen for proximity events

Roam events include a nearby object with the other side of the match: its collection, ID, and the distance in meters.

proximity.mjs — npm install @latlng/sdk
import { LatLngClient } from "@latlng/sdk";

const client = new LatLngClient({ leaderUrl: "http://127.0.0.1:7421" });
const ws = await client.connectWebSocket();
const subscription = await ws.subscribe(["driver-near-rider"]);

subscription.on("event", (event) => {
  const { collection, id, meters } = event.nearby;
  console.log(`${event.id} is ${Math.round(meters)} m from ${collection}/${id}`);
  // e.g. push "Your driver is arriving" to the rider's phone
});

05 Drive towards the rider

Move a cab from about 630 m away to about 130 m away, then a little closer. You get exactly one event: the first update inside 150 m fires, and nodwell suppresses the next one.

Requests
for lat in 53.5560 53.5515 53.5510; do
  curl -s -X POST http://127.0.0.1:7421/collections/fleet-hamburg/objects/cab-3 \
    -H 'content-type: application/json' \
    -d "{\"object\":{\"Point\":{\"lat\":$lat,\"lon\":9.9920,\"z\":null}}}"
done
Output
cab-3 is 133 m from riders/rider-42

06 Narrow it down

target_pattern is a glob on the target IDs, so you can scope a fence to one rider (rider-42) or a group (vip-*). The query options accept the same filters as searches — for example, only alert for drivers whose status is en-route to a pickup.

When the ride starts, delete the rider object, or let its TTL expire — the leader removes expired objects within about 100 ms by default. Either way, the fence stops matching it automatically.

What you built

  • A riders collection whose objects expire when requests are abandoned.
  • A roaming geofence between drivers and riders with a 150 m radius.
  • One alert per approach, thanks to nodwell.