option('truck'); $delay = (int) $this->option('speed'); $truck = Truck::where('plate_number', $plate)->first(); if (! $truck) { $this->error("Truck with plate number {$plate} not found."); return self::FAILURE; } // Find the active or scheduled trip for this truck $trip = Trip::where('truck_id', $truck->id) ->whereIn('status', [Trip::STATUS_IN_PROGRESS, Trip::STATUS_SCHEDULED, Trip::STATUS_AT_DUMPSITE]) ->first(); if (! $trip) { $this->error("No active or scheduled trip found for truck {$plate}."); return self::FAILURE; } $driver = User::find($trip->team->driver_id); if (! $driver) { $this->error('No driver assigned to the team for this trip.'); return self::FAILURE; } $this->info("Starting simulation on Trip: {$trip->trip_number} with Truck: {$truck->plate_number}"); $this->info("Driver: {$driver->first_name} {$driver->last_name}"); // Reset trip to clean state for replay/simulation DB::transaction(function () use ($trip) { $trip->forceFill([ 'status' => Trip::STATUS_SCHEDULED, 'actual_start_time' => null, ])->save(); $trip->timelineEvents()->delete(); foreach ($trip->stops as $stop) { $stop->forceFill([ 'status' => TripStop::STATUS_PENDING, 'actual_arrival' => null, 'actual_departure' => null, 'coordinates_at_arrival' => null, ])->save(); } }); $this->info('Trip state reset to Scheduled.'); // Start the trip $startLat = 13.7900; $startLng = 121.0200; $this->info("Starting trip at: Lat {$startLat}, Lng {$startLng}"); $trip = $executor->start($trip, $driver, $startLat, $startLng); $this->info('Trip status: IN_PROGRESS'); $currentLat = $startLat; $currentLng = $startLng; $stops = $trip->stops()->orderBy('sequence')->get(); foreach ($stops as $index => $stop) { $dop = $stop->dropOffPoint; if (! $dop || ! $dop->coordinates) { continue; } $destLat = $dop->coordinates->latitude; $destLng = $dop->coordinates->longitude; $this->comment("\nMoving towards Stop #{$stop->sequence}: {$dop->name}..."); // Interpolate 5 steps to reach the stop $stepsCount = 5; for ($step = 1; $step <= $stepsCount; $step++) { $ratio = $step / $stepsCount; $lat = $currentLat + ($destLat - $currentLat) * $ratio; $lng = $currentLng + ($destLng - $currentLng) * $ratio; // Calculate heading $heading = 45; // arbitrary default if ($destLng != $currentLng) { $rad = atan2($destLat - $currentLat, $destLng - $currentLng); $heading = (int) round(90 - rad2deg($rad)); if ($heading < 0) { $heading += 360; } } $tracker->record( truck: $truck, lat: $lat, lng: $lng, heading: $heading, speedKmh: 35.0, trip: $trip ); $this->line(" Step {$step}/{$stepsCount} -> Lat: ".round($lat, 6).', Lng: '.round($lng, 6).", Heading: {$heading}°"); sleep($delay); } $currentLat = $destLat; $currentLng = $destLng; // Arrive at stop $this->info("Arriving at Stop #{$stop->sequence}: {$dop->name}"); $stop = $executor->arriveAtStop($stop, $driver, $currentLat, $currentLng); sleep($delay * 2); // Depart stop $this->info("Departing from Stop #{$stop->sequence}: {$dop->name}"); $executor->departStop($stop, $driver, $currentLat, $currentLng); sleep($delay); } // Move to the Dumpsite $dumpsite = $trip->dumpsite; if ($dumpsite && $dumpsite->coordinates) { $destLat = $dumpsite->coordinates->latitude; $destLng = $dumpsite->coordinates->longitude; $this->comment("\nMoving towards Dumpsite: {$dumpsite->name}..."); $stepsCount = 5; for ($step = 1; $step <= $stepsCount; $step++) { $ratio = $step / $stepsCount; $lat = $currentLat + ($destLat - $currentLat) * $ratio; $lng = $currentLng + ($destLng - $currentLng) * $ratio; // Calculate heading $heading = 45; if ($destLng != $currentLng) { $rad = atan2($destLat - $currentLat, $destLng - $currentLng); $heading = (int) round(90 - rad2deg($rad)); if ($heading < 0) { $heading += 360; } } // The last step will fall inside the dumpsite geofence and trigger it! $result = $tracker->record( truck: $truck, lat: $lat, lng: $lng, heading: $heading, speedKmh: 45.0, trip: $trip ); $this->line(" Step {$step}/{$stepsCount} -> Lat: ".round($lat, 6).', Lng: '.round($lng, 6)); if ($result->geofenceTriggered) { $this->warn(' [GEOFENCE] Entered Dumpsite boundary! Trip status transitioned automatically.'); } sleep($delay); } } $this->info("\nSimulation complete! Truck reached the dumpsite."); return self::SUCCESS; } }