gowelle / laravel-route-matrix
A Laravel wrapper package for Google Routes API (Compute Routes)
Installs: 16
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/gowelle/laravel-route-matrix
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.0
- illuminate/contracts: ^10.0|^11.0|^12.0
- illuminate/http: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
Requires (Dev)
- larastan/larastan: ^3.8
- laravel/pint: ^1.0
- orchestra/testbench: ^8.0|^9.0|^10.0
- pestphp/pest: ^2.0|^3.0
- pestphp/pest-plugin-laravel: ^2.0|^3.0
This package is auto-updated.
Last update: 2026-01-15 23:39:23 UTC
README
A Laravel wrapper for the Google Routes API with support for route calculation, distance matrices, and waypoint optimization.
Table of Contents
- Features
- Requirements
- Installation
- Quick Start
- Usage
- Route Matrix
- Response Objects
- Eloquent Integration
- Configuration
- Exception Handling
- Testing
- License
Features
- ๐ Multiple Travel Modes - Driving, walking, bicycling, two-wheeler, transit
- ๐ฆ Traffic-Aware Routing - Real-time and historical traffic data
- ๐ Flexible Waypoints - Coordinates, Place IDs, or addresses
- ๐ Distance Matrix - Calculate NรM origin-destination pairs
- ๐ฏ Find Closest - Get closest/fastest destination helpers
- โก Fluent API - Elegant, chainable method calls
- ๐งช Fully Tested - 125+ tests with Pest PHP
Requirements
- PHP 8.2+
- Laravel 10.x, 11.x, or 12.x
- Google Cloud API key with Routes API enabled
Installation
Install the package via Composer:
composer require gowelle/laravel-route-matrix
Publish the configuration file:
php artisan vendor:publish --tag=google-routes-config
Add your Google API key to your .env file:
GOOGLE_ROUTES_API_KEY=your-api-key-here
Quick Start
use Gowelle\LaravelRouteMatrix\Facades\GoogleRoutes; // Posta -> Mlimani City Mall $response = GoogleRoutes::from(['lat' => -6.8163, 'lng' => 39.2807]) ->to(['lat' => -6.7724, 'lng' => 39.2083]) ->get(); $route = $response->first(); echo "Distance: {$route->getDistanceInKilometers()} km"; echo "Duration: {$route->getFormattedDuration()}";
Usage
Basic Route Calculation
use Gowelle\LaravelRouteMatrix\Facades\GoogleRoutes; $response = GoogleRoutes::from(['lat' => -6.8163, 'lng' => 39.2807]) ->to(['lat' => -6.7724, 'lng' => 39.2083]) ->get(); // Access the first (recommended) route $route = $response->first(); echo $route->distanceMeters; // 8200 echo $route->duration; // "900s" echo $route->getDurationInSeconds(); // 900
Using Addresses
$response = GoogleRoutes::from('Julius Nyerere International Airport, Dar es Salaam') ->to('Mlimani City Mall, Dar es Salaam') ->get();
Using Place IDs
use Gowelle\LaravelRouteMatrix\ValueObjects\Waypoint; // Example with Place IDs $response = GoogleRoutes::from(Waypoint::fromPlaceId('ChIJ...' /* Posta */)) ->to(Waypoint::fromAddress('Mlimani City Mall, Dar es Salaam')) ->get();
With Intermediate Waypoints
// Posta -> Kariakoo -> Magomeni -> Mlimani City $response = GoogleRoutes::from(['lat' => -6.8163, 'lng' => 39.2807]) ->via(['lat' => -6.8235, 'lng' => 39.2695]) // Kariakoo ->via(['lat' => -6.8059, 'lng' => 39.2536]) // Magomeni ->to(['lat' => -6.7724, 'lng' => 39.2083]) ->get(); // Access individual legs foreach ($route->legs as $leg) { echo "Leg distance: {$leg->distanceMeters}m\n"; }
Travel Modes
use Gowelle\LaravelRouteMatrix\Enums\TravelMode; // Using enum $response = GoogleRoutes::from($origin) ->to($destination) ->travelMode(TravelMode::DRIVE) ->get(); // Using shortcuts $response = GoogleRoutes::from($origin) ->to($destination) ->driving() // or walking(), bicycling(), transit() ->get();
Traffic-Aware Routing
use Gowelle\LaravelRouteMatrix\Enums\RoutingPreference; $response = GoogleRoutes::from($origin) ->to($destination) ->routingPreference(RoutingPreference::TRAFFIC_AWARE_OPTIMAL) ->get(); // Or use the shortcut $response = GoogleRoutes::from($origin) ->to($destination) ->withOptimalTraffic() ->get();
Route Modifiers
$response = GoogleRoutes::from($origin) ->to($destination) ->avoidTolls() ->avoidHighways() ->avoidFerries() ->get();
Alternative Routes
$response = GoogleRoutes::from($origin) ->to($destination) ->withAlternatives() ->get(); // Get the main route $mainRoute = $response->first(); // Get alternative routes $alternatives = $response->getAlternatives(); foreach ($alternatives as $route) { echo "Alternative: {$route->getFormattedDuration()}\n"; }
Fuel-Efficient Routes
$response = GoogleRoutes::from($origin) ->to($destination) ->withFuelEfficientRoute() ->get(); $fuelEfficientRoute = $response->getFuelEfficientRoute();
Waypoint Optimization
$response = GoogleRoutes::from($origin) ->via($waypoint1) ->via($waypoint2) ->via($waypoint3) ->to($destination) ->optimizeWaypointOrder() ->get(); // Get the optimized order $optimizedOrder = $response->first()->optimizedIntermediateWaypointIndex;
Departure Time
use Carbon\Carbon; $response = GoogleRoutes::from($origin) ->to($destination) ->departureTime(Carbon::now()->addHour()) ->get(); // Or depart now $response = GoogleRoutes::from($origin) ->to($destination) ->departNow() ->get();
Extra Computations
$response = GoogleRoutes::from($origin) ->to($destination) ->withTolls() ->withFuelConsumption() ->withTrafficOnPolyline() ->get();
Custom Field Mask
Specify which fields to include in the response:
$response = GoogleRoutes::from($origin) ->to($destination) ->fields([ 'routes.duration', 'routes.distanceMeters', 'routes.polyline.encodedPolyline', 'routes.legs.steps', 'routes.viewport', ]) ->get();
High Quality Polylines
use Gowelle\LaravelRouteMatrix\Enums\PolylineEncoding; $response = GoogleRoutes::from($origin) ->to($destination) ->highQualityPolyline() ->get(); // Or use GeoJSON format $response = GoogleRoutes::from($origin) ->to($destination) ->geoJsonPolyline() ->get();
Localization
use Gowelle\LaravelRouteMatrix\Enums\Units; $response = GoogleRoutes::from($origin) ->to($destination) ->language('es-ES') ->region('ES') ->units(Units::METRIC) // or imperial() ->get();
Complete Example
use Gowelle\LaravelRouteMatrix\Facades\GoogleRoutes; use Gowelle\LaravelRouteMatrix\Enums\TravelMode; use Gowelle\LaravelRouteMatrix\Enums\RoutingPreference; use Carbon\Carbon; // Posta to Mlimani City with waypoints and options $response = GoogleRoutes::from(['lat' => -6.8163, 'lng' => 39.2807]) ->to(['lat' => -6.7724, 'lng' => 39.2083]) ->via(['lat' => -6.8235, 'lng' => 39.2695]) // Kariakoo ->travelMode(TravelMode::DRIVE) ->routingPreference(RoutingPreference::TRAFFIC_AWARE_OPTIMAL) ->avoidTolls() ->departureTime(Carbon::now()->addHour()) ->withAlternatives() ->withFuelEfficientRoute() ->language('en-US') ->metric() ->fields([ 'routes.duration', 'routes.distanceMeters', 'routes.polyline.encodedPolyline', 'routes.legs', 'routes.routeLabels', ]) ->get(); // Process the response $route = $response->first(); echo "Distance: " . $route->getDistanceInKilometers() . " km\n"; echo "Duration: " . $route->getFormattedDuration() . "\n"; echo "Polyline: " . $route->polyline?->encodedPolyline . "\n"; // Check for warnings if (!empty($route->warnings)) { foreach ($route->warnings as $warning) { echo "Warning: {$warning}\n"; } }
Eloquent Integration (Routable Trait)
You can make your Eloquent models "routable" by implementing the Routable contract and using the HasRoute trait. This allows you to pass models directly to the API headers.
- Implement the interface and trait:
use Illuminate\Database\Eloquent\Model; use Gowelle\LaravelRouteMatrix\Contracts\Routable; use Gowelle\LaravelRouteMatrix\Traits\HasRoute; class Store extends Model implements Routable { use HasRoute; // Optional: Customize how the waypoint is resolved // (Defaults to looking for lat/lng, latitude/longitude, or address attributes) }
- Use models in route requests:
$store = Store::find(1); $customer = User::find(5); // Assuming User also implements Routable $response = GoogleRoutes::from($store) ->to($customer) ->get();
- Or initiate directly from the model:
$response = $store->routeTo($customer) ->driving() ->get();
Response Caching
To reduce API costs and improve performance, you can enable built-in caching.
- Configure caching in
config/google-routes.php:
'cache' => [ 'enabled' => true, 'store' => 'redis', // or 'file', 'database' 'ttl' => 3600, // Cache duration in seconds ],
When enabled, identical requests (same origin, destination, and options) will be served from the cache for the specified TTL duration.
Route Matrix (Distance Matrix)
The Route Matrix API allows you to calculate distances and travel times between multiple origins and destinations efficiently.
One Origin to Multiple Destinations
use Gowelle\LaravelRouteMatrix\Facades\GoogleRoutes; $response = GoogleRoutes::matrix() ->addOrigin(['lat' => -6.8163, 'lng' => 39.2807]) // Posta (Your location) ->addDestination(['lat' => -6.8235, 'lng' => 39.2695]) // Kariakoo ->addDestination(['lat' => -6.7724, 'lng' => 39.2083]) // Mlimani City ->addDestination(['lat' => -6.7567, 'lng' => 39.2772]) // Masaki ->driving() ->get(); // Find the closest destination $closest = $response->getClosestDestination(0); echo "Closest: {$closest->getDistanceInKilometers()} km"; // Find the fastest destination $fastest = $response->getFastestDestination(0); echo "Fastest: {$fastest->getFormattedDuration()}";
Multiple Origins to One Destination
// Find which store/warehouse is closest to a customer $response = GoogleRoutes::matrix() ->addOrigin(['lat' => -6.8163, 'lng' => 39.2807]) // Store A (Posta) ->addOrigin(['lat' => -6.8235, 'lng' => 39.2695]) // Store B (Kariakoo) ->addOrigin(['lat' => -6.7724, 'lng' => 39.2083]) // Store C (Mlimani City) ->addDestination(['lat' => -6.7567, 'lng' => 39.2772]) // Customer (Masaki) ->driving() ->get(); $closestStore = $response->getClosestOrigin(0); echo "Ship from store at origin index: {$closestStore->originIndex}";
Many to Many (Full Matrix)
$response = GoogleRoutes::matrix() ->origins([ ['lat' => -6.8163, 'lng' => 39.2807], // Posta ['lat' => -6.8235, 'lng' => 39.2695], // Kariakoo ]) ->destinations([ ['lat' => -6.7724, 'lng' => 39.2083], // Mlimani City ['lat' => -6.7567, 'lng' => 39.2772], // Masaki ]) ->driving() ->withTraffic() ->get(); // Access specific element (origin 0 โ destination 1) $element = $response->get(0, 1); echo "Distance: {$element->getDistanceInKilometers()} km"; echo "Duration: {$element->getFormattedDuration()}"; // Convert to 2D matrix format $matrix = $response->toMatrix(); // $matrix[originIndex][destinationIndex] = RouteMatrixElement
Sorting Results
// Get all destinations sorted by distance (closest first) $sortedByDistance = $response->sortedByDistance(); // Get all destinations sorted by duration (fastest first) $sortedByDuration = $response->sortedByDuration(); // Get only elements where a route was found $validRoutes = $response->withRoutes();
RouteMatrixElement Properties
Each element in the matrix contains:
originIndex- Index of the origin (0-based)destinationIndex- Index of the destination (0-based)distanceMeters- Distance in metersduration- Duration string (e.g., "1234s")condition- Route condition (ROUTE_EXISTS, ROUTE_NOT_FOUND)
Helper methods:
routeExists()- Check if a route was foundgetDurationInSeconds()- Get duration as integergetDistanceInKilometers()- Get distance in kmgetDistanceInMiles()- Get distance in milesgetFormattedDuration()- Get human-readable duration
Real-World Example: Courier Pickup & Delivery
A courier needs to pick up packages from multiple stores and deliver to customers (some packages share the same destination):
use Gowelle\LaravelRouteMatrix\Facades\GoogleRoutes; // Courier's current location (Dar es Salaam - Posta) $courierLocation = ['lat' => -6.8160, 'lng' => 39.2803]; // Stores to pickup from $stores = [ ['id' => 'store_1', 'name' => 'Kariakoo Market', 'lat' => -6.8235, 'lng' => 39.2695], ['id' => 'store_2', 'name' => 'Mlimani City Mall', 'lat' => -6.7724, 'lng' => 39.2083], ['id' => 'store_3', 'name' => 'Slipway Shopping', 'lat' => -6.7488, 'lng' => 39.2656], ]; // Packages with destinations (some share same customer) $packages = [ ['id' => 'pkg_1', 'store_id' => 'store_1', 'customer' => 'Masaki Customer', 'lat' => -6.7567, 'lng' => 39.2772], ['id' => 'pkg_2', 'store_id' => 'store_2', 'customer' => 'Masaki Customer', 'lat' => -6.7567, 'lng' => 39.2772], ['id' => 'pkg_3', 'store_id' => 'store_2', 'customer' => 'Mikocheni Customer', 'lat' => -6.7651, 'lng' => 39.2451], ['id' => 'pkg_4', 'store_id' => 'store_3', 'customer' => 'Kinondoni Customer', 'lat' => -6.7735, 'lng' => 39.2401], ]; // Get unique destinations (consolidate packages to same location) $uniqueDestinations = collect($packages) ->unique(fn($pkg) => $pkg['lat'] . ',' . $pkg['lng']) ->values() ->all(); // STEP 1: Find optimal pickup order $pickupRoute = GoogleRoutes::from($courierLocation); foreach ($stores as $store) { $pickupRoute->via(['lat' => $store['lat'], 'lng' => $store['lng']]); } $lastStore = end($stores); $response = $pickupRoute ->to(['lat' => $lastStore['lat'], 'lng' => $lastStore['lng']]) ->optimizeWaypointOrder() ->driving() ->withTraffic() ->get(); $optimizedOrder = $response->first()->optimizedIntermediateWaypointIndex ?? []; echo "Pickup order: " . implode(' โ ', array_map(fn($i) => $stores[$i]['name'], $optimizedOrder)); // STEP 2: Calculate delivery matrix from last pickup $deliveryMatrix = GoogleRoutes::matrix() ->addOrigin(['lat' => $lastStore['lat'], 'lng' => $lastStore['lng']]) ->driving() ->withTraffic(); foreach ($uniqueDestinations as $dest) { $deliveryMatrix->addDestination(['lat' => $dest['lat'], 'lng' => $dest['lng']]); } $matrixResponse = $deliveryMatrix->get(); // Find closest delivery from last pickup $firstDelivery = $matrixResponse->getClosestDestination(0); echo "First delivery: {$uniqueDestinations[$firstDelivery->destinationIndex]['customer']}"; echo "ETA: {$firstDelivery->getFormattedDuration()}"; // STEP 3: Build full optimized route (pickups + deliveries) $allStops = array_merge( array_map(fn($s) => ['lat' => $s['lat'], 'lng' => $s['lng'], 'type' => 'pickup', 'name' => $s['name']], $stores), array_map(fn($d) => ['lat' => $d['lat'], 'lng' => $d['lng'], 'type' => 'delivery', 'name' => $d['customer']], $uniqueDestinations) ); $fullRoute = GoogleRoutes::from($courierLocation); foreach ($allStops as $stop) { $fullRoute->via(['lat' => $stop['lat'], 'lng' => $stop['lng']]); } $optimizedResponse = $fullRoute ->to($courierLocation) // Return to base ->optimizeWaypointOrder() ->driving() ->withTraffic() ->get(); $route = $optimizedResponse->first(); echo "Total distance: {$route->getDistanceInKilometers()} km"; echo "Total time: {$route->getFormattedDuration()}"; // Display optimized route $finalOrder = $route->optimizedIntermediateWaypointIndex ?? []; foreach ($finalOrder as $index => $stopIndex) { $stop = $allStops[$stopIndex]; $icon = $stop['type'] === 'pickup' ? '๐ฆ' : '๐'; echo ($index + 1) . ". {$icon} {$stop['name']}"; }
Output:
Pickup order: Kariakoo Market โ Mlimani City Mall โ Slipway Shopping
First delivery: Masaki Customer
ETA: 12 min
Total distance: 32.4 km
Total time: 1h 8m
1. ๐ฆ Kariakoo Market
2. ๐ฆ Mlimani City Mall
3. ๐ Mikocheni Customer
4. ๐ Kinondoni Customer
5. ๐ฆ Slipway Shopping
6. ๐ Masaki Customer (2 packages)
Response Objects
RoutesResponse
The main response object containing:
routes- Collection of Route objectsfallbackInfo- Information about routing fallback (if any)geocodingResults- Geocoding information for address waypoints
Route
Individual route containing:
distanceMeters- Total distance in metersduration- Duration string (e.g., "165s")polyline- Encoded polyline or GeoJSONlegs- Collection of RouteLeg objectsviewport- Map bounding boxrouteLabels- Route type labelswarnings- Route warnings
Helper methods:
getDurationInSeconds()- Get duration as integergetDistanceInKilometers()- Get distance in kmgetDistanceInMiles()- Get distance in milesgetFormattedDuration()- Get human-readable durationisDefaultRoute()- Check if default routeisFuelEfficient()- Check if fuel-efficient route
Configuration
The configuration file (config/google-routes.php) includes:
return [ 'api_key' => env('GOOGLE_ROUTES_API_KEY'), 'base_url' => env('GOOGLE_ROUTES_BASE_URL', 'https://routes.googleapis.com'), 'timeout' => env('GOOGLE_ROUTES_TIMEOUT', 30), 'defaults' => [ 'travel_mode' => env('GOOGLE_ROUTES_TRAVEL_MODE', 'DRIVE'), 'language_code' => env('GOOGLE_ROUTES_LANGUAGE', 'en-US'), 'units' => env('GOOGLE_ROUTES_UNITS', 'METRIC'), 'routing_preference' => env('GOOGLE_ROUTES_ROUTING_PREFERENCE', 'TRAFFIC_AWARE'), ], 'default_field_mask' => [ 'routes.duration', 'routes.distanceMeters', 'routes.polyline.encodedPolyline', ], /* |-------------------------------------------------------------------------- | Response Caching |-------------------------------------------------------------------------- | | Configure response caching to reduce API costs and improve performance. | */ 'cache' => [ 'enabled' => env('GOOGLE_ROUTES_CACHE_ENABLED', false), 'store' => env('GOOGLE_ROUTES_CACHE_STORE', null), 'ttl' => env('GOOGLE_ROUTES_CACHE_TTL', 3600), ], ];
Exception Handling
The package throws specific exceptions:
use Gowelle\LaravelRouteMatrix\Exceptions\GoogleRoutesException; use Gowelle\LaravelRouteMatrix\Exceptions\InvalidApiKeyException; use Gowelle\LaravelRouteMatrix\Exceptions\InvalidRequestException; use Gowelle\LaravelRouteMatrix\Exceptions\NoRouteFoundException; try { $response = GoogleRoutes::from($origin) ->to($destination) ->get(); } catch (InvalidApiKeyException $e) { // API key is missing or invalid } catch (InvalidRequestException $e) { // Request parameters are invalid } catch (NoRouteFoundException $e) { // No route could be found } catch (GoogleRoutesException $e) { // Other API errors }
Testing
Run the test suite:
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security
If you discover any security-related issues, please email gowelle.john@icloud.com instead of using the issue tracker.
Credits
License
The MIT License (MIT). Please see License File for more information.