Skip to content

Checkpoint Proximity Detection

Detects when a vehicle arrives within range of a route checkpoint. Runs on every GPS update from the driver app and triggers checkpoint-cleared events for trip tracking, progress logging, and passenger notifications.


Where It Is Used

Layer File Role
Server apps/api-server/src/location/location.service.ts Persistent checkpoint clearing via database query
Mobile driver apps/mobile-driver/src/app/core/services/location.service.ts Real-time detection on device for instant feedback

Server-Side Detection

Input

  • Vehicle GPS position (lat, lng) sent by the driver app
  • All active checkpoints for the vehicle's current trip (with radiusMeters per checkpoint)

Algorithm

SELECT * FROM "Checkpoint"
WHERE
  6371000 * acos(
    cos(radians(:lat)) * cos(radians("lat"))
    * cos(radians("lng") - radians(:lng))
    + sin(radians(:lat)) * sin(radians("lat"))
  ) <= "radiusMeters"
LIMIT 1

This is a Haversine proximity query in raw SQL, used as a fallback when PostGIS ST_DWithin is unavailable. The PostGIS version uses:

ST_DWithin(location::geography, ST_MakePoint(:lng, :lat)::geography, "radiusMeters")

On Match

  1. Record the checkpoint in Trip.actualCheckpoints (append to JSON array — never removed).
  2. Emit real-time notification to subscribed passengers via WebSocket.
  3. Log VehicleLocationLog row.

Mobile-Side Detection

The driver app maintains its own checkpoint list and checks proximity locally for instant haptic/audio feedback, without waiting for the server round-trip.

GPS Update Triggers

  • Distance filter: 10 m — location updates fire only after the device moves at least 10 m.
  • Heartbeat: 30 s — even if stationary, a position is reported every 30 seconds.
  • Maximum frequency: 10 s between updates during movement.

Algorithm

for (const checkpoint of remainingCheckpoints) {
  const dist = haversineMeters(
    currentLat, currentLng,
    checkpoint.lat, checkpoint.lng
  );
  if (dist <= CHECKPOINT_RADIUS_METERS) {
    markCheckpointReached(checkpoint);
    triggerHapticFeedback();
    syncToServer(checkpoint.id);
  }
}

CHECKPOINT_RADIUS_METERS = 50 m (device-side constant; server uses per-checkpoint radiusMeters).

Trip Completion

After each checkpoint is cleared, the app checks:

if (reachedCount >= totalCheckpoints && totalCheckpoints > 0) {
  tripReadyToFinish$.emit();
}

This enables the "Finish Trip" button in the driver UI.


Threshold Rationale

Threshold Value Reason
Server radiusMeters Configurable per checkpoint Allows tight checkpoints at narrow streets and wider ones at large terminals
Mobile constant 50 m Provides a generous buffer for GPS drift (~5–15 m typical urban error)
SQL LIMIT 1 First match only Avoids double-clearing adjacent checkpoints on slow segments