File

src/tracking/geofence.service.ts

Index

Methods

Constructor

constructor(prisma: PrismaService)
Parameters :
Name Type Optional
prisma PrismaService No

Methods

Async checkGeofences
checkGeofences(vehicleId: string, lat: number, lng: number)

Check if a vehicle position is inside any geofence. Called by the tracking service when GPS events come in.

Parameters :
Name Type Optional
vehicleId string No
lat number No
lng number No
Returns : Promise<Array<literal type>>
import { Injectable, Logger } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';

@Injectable()
export class GeofenceService {
  private readonly logger = new Logger(GeofenceService.name);

  constructor(private prisma: PrismaService) {}

  /**
   * Check if a vehicle position is inside any geofence.
   * Called by the tracking service when GPS events come in.
   */
  async checkGeofences(
    vehicleId: string,
    lat: number,
    lng: number,
  ): Promise<
    Array<{ geofenceId: string; name: string; event: 'enter' | 'exit' }>
  > {
    const geofences = await this.prisma.geofence.findMany({
      where: { isActive: true },
    });
    const events: Array<{
      geofenceId: string;
      name: string;
      event: 'enter' | 'exit';
    }> = [];

    for (const gf of geofences) {
      if (!gf.centerLat || !gf.centerLng || !gf.radiusMeters) continue;

      const distance = this.haversineMeters(
        lat,
        lng,
        gf.centerLat,
        gf.centerLng,
      );
      const isInside = distance <= gf.radiusMeters;

      // Check previous state (was the vehicle inside before?)
      const lastEvent = await this.prisma.gpsEvent.findFirst({
        where: {
          vehicleId,
          source: { in: ['geofence_enter', 'geofence_exit'] },
        },
        orderBy: { timestamp: 'desc' },
      });

      const wasInside = lastEvent?.source === 'geofence_enter';

      if (isInside && !wasInside) {
        events.push({ geofenceId: gf.id, name: gf.name, event: 'enter' });
      } else if (!isInside && wasInside) {
        events.push({ geofenceId: gf.id, name: gf.name, event: 'exit' });
      }
    }

    return events;
  }

  private haversineMeters(
    lat1: number,
    lon1: number,
    lat2: number,
    lon2: number,
  ): number {
    const R = 6371000;
    const dLat = ((lat2 - lat1) * Math.PI) / 180;
    const dLon = ((lon2 - lon1) * Math.PI) / 180;
    const a =
      Math.sin(dLat / 2) ** 2 +
      Math.cos((lat1 * Math.PI) / 180) *
        Math.cos((lat2 * Math.PI) / 180) *
        Math.sin(dLon / 2) ** 2;
    return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  }
}

results matching ""

    No results matching ""