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
radiusMetersper 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
- Record the checkpoint in
Trip.actualCheckpoints(append to JSON array — never removed). - Emit real-time notification to subscribed passengers via WebSocket.
- Log
VehicleLocationLogrow.
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 |
Related
- Haversine Distance — the distance function used
- ETA Estimation — uses checkpoint progress for remaining distance
- Active Trip Suggestions — uses
actualCheckpointsto determine which stops the bus has already passed