Rideshare platforms require real-time GPS tracking and efficient driver matching. Laravel with PostGIS and Reverb creates responsive location-based services. At ZIRA Software, we've built tracking systems handling thousands of concurrent rides.
Database Schema with PostGIS
// Enable PostGIS extension
DB::statement('CREATE EXTENSION IF NOT EXISTS postgis');
Schema::create('drivers', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained();
$table->string('vehicle_type'); // car, bike, scooter
$table->string('license_plate');
$table->enum('status', ['offline', 'available', 'busy']);
$table->geography('current_location', 'point', 4326)->nullable();
$table->float('rating')->default(5.0);
$table->timestamp('last_location_update')->nullable();
$table->timestamps();
$table->spatialIndex('current_location');
});
Schema::create('rides', function (Blueprint $table) {
$table->id();
$table->foreignId('rider_id')->constrained('users');
$table->foreignId('driver_id')->nullable()->constrained('drivers');
$table->geography('pickup_location', 'point', 4326);
$table->geography('dropoff_location', 'point', 4326);
$table->string('pickup_address');
$table->string('dropoff_address');
$table->enum('status', ['pending', 'accepted', 'arriving', 'in_progress', 'completed', 'cancelled']);
$table->decimal('estimated_fare', 10, 2);
$table->decimal('final_fare', 10, 2)->nullable();
$table->integer('estimated_duration_seconds');
$table->float('distance_km');
$table->timestamp('pickup_time')->nullable();
$table->timestamp('dropoff_time')->nullable();
$table->timestamps();
});
Schema::create('location_history', function (Blueprint $table) {
$table->id();
$table->foreignId('ride_id')->constrained()->onDelete('cascade');
$table->geography('location', 'point', 4326);
$table->float('speed')->nullable();
$table->float('heading')->nullable();
$table->timestamp('recorded_at');
$table->index(['ride_id', 'recorded_at']);
});
Driver Location Service
// app/Services/LocationService.php
class LocationService
{
public function updateDriverLocation(Driver $driver, float $lat, float $lng): void
{
$point = DB::raw("ST_SetSRID(ST_MakePoint({$lng}, {$lat}), 4326)");
$driver->update([
'current_location' => $point,
'last_location_update' => now(),
]);
// Broadcast to active ride if exists
if ($driver->activeRide) {
$this->broadcastLocation($driver->activeRide, $lat, $lng);
}
}
public function findNearbyDrivers(float $lat, float $lng, float $radiusKm = 5, int $limit = 10): Collection
{
return Driver::query()
->where('status', 'available')
->whereRaw(
"ST_DWithin(current_location, ST_SetSRID(ST_MakePoint(?, ?), 4326)::geography, ?)",
[$lng, $lat, $radiusKm * 1000]
)
->selectRaw("*, ST_Distance(current_location, ST_SetSRID(ST_MakePoint(?, ?), 4326)::geography) as distance", [$lng, $lat])
->orderBy('distance')
->limit($limit)
->get();
}
private function broadcastLocation(Ride $ride, float $lat, float $lng): void
{
broadcast(new DriverLocationUpdated($ride, [
'latitude' => $lat,
'longitude' => $lng,
'timestamp' => now()->toISOString(),
]))->toOthers();
}
}
Ride Matching Service
// app/Services/RideMatchingService.php
class RideMatchingService
{
public function __construct(
private LocationService $location,
private PricingService $pricing,
) {}
public function requestRide(User $rider, array $pickup, array $dropoff): Ride
{
// Calculate route and pricing
$route = $this->calculateRoute($pickup, $dropoff);
$ride = Ride::create([
'rider_id' => $rider->id,
'pickup_location' => DB::raw("ST_SetSRID(ST_MakePoint({$pickup['lng']}, {$pickup['lat']}), 4326)"),
'dropoff_location' => DB::raw("ST_SetSRID(ST_MakePoint({$dropoff['lng']}, {$dropoff['lat']}), 4326)"),
'pickup_address' => $pickup['address'],
'dropoff_address' => $dropoff['address'],
'status' => 'pending',
'estimated_fare' => $route['fare'],
'estimated_duration_seconds' => $route['duration'],
'distance_km' => $route['distance'],
]);
// Find and notify nearby drivers
$this->notifyNearbyDrivers($ride, $pickup['lat'], $pickup['lng']);
return $ride;
}
public function acceptRide(Driver $driver, Ride $ride): Ride
{
if ($ride->status !== 'pending') {
throw new RideAlreadyAcceptedException();
}
DB::transaction(function () use ($driver, $ride) {
$ride->update([
'driver_id' => $driver->id,
'status' => 'accepted',
]);
$driver->update(['status' => 'busy']);
});
// Notify rider
broadcast(new RideAccepted($ride))->toOthers();
// Calculate ETA
$eta = $this->calculateETA($driver, $ride);
broadcast(new DriverETA($ride, $eta));
return $ride->fresh('driver');
}
private function notifyNearbyDrivers(Ride $ride, float $lat, float $lng): void
{
$drivers = $this->location->findNearbyDrivers($lat, $lng);
foreach ($drivers as $driver) {
$driver->user->notify(new NewRideRequest($ride, $driver->distance));
}
}
private function calculateETA(Driver $driver, Ride $ride): int
{
// Use routing service (Google Maps, OSRM, etc.)
$driverLocation = $driver->current_location;
$pickupLocation = $ride->pickup_location;
// Return ETA in seconds
return $this->getRouteTime($driverLocation, $pickupLocation);
}
}
Real-time Location Broadcasting
// app/Events/DriverLocationUpdated.php
class DriverLocationUpdated implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets;
public function __construct(
public Ride $ride,
public array $location,
) {}
public function broadcastOn(): array
{
return [
new PrivateChannel('ride.' . $this->ride->id),
];
}
public function broadcastWith(): array
{
return [
'latitude' => $this->location['latitude'],
'longitude' => $this->location['longitude'],
'timestamp' => $this->location['timestamp'],
];
}
}
Mobile Location Updates
// app/Http/Controllers/Api/DriverLocationController.php
class DriverLocationController extends Controller
{
public function __construct(
private LocationService $location,
) {}
public function update(Request $request)
{
$validated = $request->validate([
'latitude' => 'required|numeric|between:-90,90',
'longitude' => 'required|numeric|between:-180,180',
'speed' => 'nullable|numeric|min:0',
'heading' => 'nullable|numeric|between:0,360',
]);
$driver = $request->user()->driver;
$this->location->updateDriverLocation(
$driver,
$validated['latitude'],
$validated['longitude']
);
// Store in location history if on active ride
if ($driver->activeRide) {
LocationHistory::create([
'ride_id' => $driver->activeRide->id,
'location' => DB::raw("ST_SetSRID(ST_MakePoint({$validated['longitude']}, {$validated['latitude']}), 4326)"),
'speed' => $validated['speed'],
'heading' => $validated['heading'],
'recorded_at' => now(),
]);
}
return response()->json(['status' => 'updated']);
}
}
Frontend Map Integration
// resources/js/components/RideTracker.vue
export default {
props: ['rideId'],
data() {
return {
map: null,
driverMarker: null,
driverLocation: null,
};
},
mounted() {
this.initMap();
this.subscribeToLocation();
},
methods: {
initMap() {
this.map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v11',
center: [this.pickup.lng, this.pickup.lat],
zoom: 14,
});
},
subscribeToLocation() {
Echo.private(`ride.${this.rideId}`)
.listen('DriverLocationUpdated', (e) => {
this.updateDriverPosition(e.latitude, e.longitude);
})
.listen('RideStatusChanged', (e) => {
this.handleStatusChange(e.status);
});
},
updateDriverPosition(lat, lng) {
if (!this.driverMarker) {
this.driverMarker = new mapboxgl.Marker({ color: '#3B82F6' })
.setLngLat([lng, lat])
.addTo(this.map);
} else {
this.driverMarker.setLngLat([lng, lat]);
}
// Smooth pan to driver
this.map.panTo([lng, lat]);
},
},
};
Conclusion
Rideshare platforms with Laravel, PostGIS, and Reverb deliver real-time GPS tracking and efficient driver matching. Proper geospatial indexing ensures fast location queries at scale.
Building location-based platforms? Contact ZIRA Software for GPS tracking development.