latlng

Stream live geofence events over WebSocket

Channels push geofence events to subscribers the moment they happen. You'll create a channel, subscribe from Node.js with the TypeScript SDK, and forward events to a dashboard.

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

Before you start

  • latlng-server running locally with a fleet-hamburg collection.
  • Node.js 22+ (which ships a global WebSocket) and npm install @latlng/sdk.

01 Hooks or channels?

Hooks push events to an HTTP endpoint and keep retrying until they're acknowledged — use them when another service must not miss an event. Channels stream events to whoever is subscribed right now over WebSocket — use them for live dashboards, control-room views, and services that only care about the present.

Both use the same geofence definition, so you can start with a channel while prototyping and switch to a hook later.

02 Create a channel

A channel has a name and a geofence definition. This one reports vehicles entering and leaving the port area.

Request
curl -s -X POST http://127.0.0.1:7421/channels \
  -H 'content-type: application/json' \
  -d '{
    "name": "port-area",
    "def": {
      "collection": "fleet-hamburg",
      "query": {
        "Within": {
          "area": { "GeoJson": { "type": "Polygon", "coordinates": [[
            [9.930, 53.538], [9.975, 53.538], [9.975, 53.548], [9.930, 53.548], [9.930, 53.538]
          ]] } },
          "options": {}
        }
      },
      "detect": ["Enter", "Exit"],
      "commands": ["Set"]
    }
  }'

03 Subscribe with the TypeScript SDK

connectWebSocket() opens a connection to the server's /ws endpoint using the same URL and token as the HTTP client. Subscribing requires the subscriptions:read permission when auth is enabled. subscribe takes a list of channel names; psubscribe takes glob patterns such as port-*.

subscribe.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(["port-area"]);

subscription.on("event", (event) => {
  // event.object is a parsed geometry: { type: "point", lat, lon, z }
  console.log(`${event.detect} ${event.id} at ${event.object.lat}, ${event.object.lon}`);
});

subscription.on("close", () => console.log("subscription closed"));

04 Generate some traffic

In another terminal, move a truck into the port area and out again. The subscriber prints each transition as it happens.

traffic.mjs
import { LatLngClient } from "@latlng/sdk";

const client = new LatLngClient({ leaderUrl: "http://127.0.0.1:7421" });

await client.setPoint("fleet-hamburg", "truck-9", { lat: 53.551, lon: 9.95 }); // outside
await client.setPoint("fleet-hamburg", "truck-9", { lat: 53.543, lon: 9.95 }); // inside
await client.setPoint("fleet-hamburg", "truck-9", { lat: 53.551, lon: 9.95 }); // outside again
Subscriber output
enter truck-9 at 53.543, 9.95
exit truck-9 at 53.551, 9.95

05 Fan events out to dashboards

Run the subscriber inside your backend and forward events to connected dashboards over your own WebSocket or Server-Sent Events endpoint. That keeps latlng credentials on the server — ideally a dashboard-scoped token — and lets you add per-user filtering.

Each event carries the collection, object ID, detect type, current geometry, fields, and a nanosecond timestamp — enough to update a map marker and an activity feed without another query.

Channel events are live only: subscribers that are disconnected miss them, and each subscriber's in-memory mailbox (4096 events by default, set with subscriber_queue_capacity) drops the oldest events if it can't keep up. If every event matters, use a hook as well.

What you built

  • A port-area channel that emits enter and exit events.
  • A Node.js subscriber built on the TypeScript SDK.
  • A pattern for forwarding events to dashboards without exposing latlng directly.