@fleetbase/fleetops-engine 0.4.20 → 0.4.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +1 -1
- package/server/config/fleetops.php +19 -0
- package/server/src/Console/Commands/DispatchAdhocOrders.php +44 -30
- package/server/src/Console/Commands/DispatchOrders.php +27 -21
- package/server/src/Console/Commands/FixInvalidPolymorphicRelationTypeNamespaces.php +97 -0
- package/server/src/Console/Commands/TrackOrderDistanceAndTime.php +49 -16
- package/server/src/Models/Order.php +2 -4
- package/server/src/Models/Place.php +4 -2
- package/server/src/Models/PurchaseRate.php +3 -1
- package/server/src/Providers/FleetOpsServiceProvider.php +1 -0
- package/server/src/Support/Algo.php +0 -24
- package/server/src/Support/DistanceMatrix.php +9 -1
- package/server/src/Support/OSRM.php +7 -0
- package/server/src/Support/Utils.php +216 -195
- package/server/src/routes.php +0 -3
package/composer.json
CHANGED
package/extension.json
CHANGED
package/package.json
CHANGED
|
@@ -22,6 +22,25 @@ return [
|
|
|
22
22
|
'db' => env('DB_CONNECTION', 'mysql')
|
|
23
23
|
],
|
|
24
24
|
|
|
25
|
+
/*
|
|
26
|
+
|--------------------------------------------------------------------------
|
|
27
|
+
| OSRM
|
|
28
|
+
|--------------------------------------------------------------------------
|
|
29
|
+
*/
|
|
30
|
+
'osrm' => [
|
|
31
|
+
'host' => env('OSRM_HOST', 'https://bundle.routing.fleetbase.io')
|
|
32
|
+
],
|
|
33
|
+
|
|
34
|
+
/*
|
|
35
|
+
|--------------------------------------------------------------------------
|
|
36
|
+
| Distance Matrix Calculator
|
|
37
|
+
| Options: "calculate", "google", "osrm"
|
|
38
|
+
|--------------------------------------------------------------------------
|
|
39
|
+
*/
|
|
40
|
+
'distance_matrix' => [
|
|
41
|
+
'provider' => env('DISTANCE_MATRIX_PROVIDER', 'calculate')
|
|
42
|
+
],
|
|
43
|
+
|
|
25
44
|
/*
|
|
26
45
|
|--------------------------------------------------------------------------
|
|
27
46
|
| Navigator App
|
|
@@ -7,6 +7,7 @@ use Fleetbase\FleetOps\Models\Order;
|
|
|
7
7
|
use Fleetbase\FleetOps\Support\Utils;
|
|
8
8
|
use Fleetbase\LaravelMysqlSpatial\Types\Point;
|
|
9
9
|
use Illuminate\Console\Command;
|
|
10
|
+
use Illuminate\Database\Eloquent\Collection;
|
|
10
11
|
use Illuminate\Support\Carbon;
|
|
11
12
|
|
|
12
13
|
class DispatchAdhocOrders extends Command
|
|
@@ -28,52 +29,56 @@ class DispatchAdhocOrders extends Command
|
|
|
28
29
|
/**
|
|
29
30
|
* Execute the console command.
|
|
30
31
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* @return int
|
|
32
|
+
* Processes and dispatches ad-hoc orders based on their scheduled dispatch time and driver availability.
|
|
33
|
+
* Orders without an assigned driver and that have surpassed the dispatch time threshold are considered for dispatch.
|
|
34
|
+
* The command supports sandbox and testing modes for different operational environments.
|
|
36
35
|
*/
|
|
37
|
-
public function handle()
|
|
36
|
+
public function handle(): void
|
|
38
37
|
{
|
|
39
|
-
// Set UTC as default timezone
|
|
40
38
|
date_default_timezone_set('UTC');
|
|
41
39
|
|
|
42
|
-
|
|
40
|
+
$sandboxMode = Utils::castBoolean($this->option('sandbox'));
|
|
41
|
+
$this->info('Running in ' . ($sandboxMode ? 'sandbox' : 'production') . ' mode.');
|
|
42
|
+
|
|
43
43
|
$orders = $this->getDispatchableOrders();
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
$this->alert('Found (' . $orders->count() . ') Orders to be Dispatched -- Current Time: ' . Carbon::now()->toDateTimeString());
|
|
45
|
+
$this->alert($orders->count() . ' orders found for ad-hoc dispatch. Current Time: ' . Carbon::now()->toDateTimeString());
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
$pickup = $order->getPickupLocation();
|
|
52
|
-
$distance = $order->getAdhocPingDistance();
|
|
47
|
+
foreach ($orders as $order) {
|
|
48
|
+
$pickup = $order->getPickupLocation();
|
|
49
|
+
$distance = $order->getAdhocPingDistance();
|
|
53
50
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
if (!Utils::isPoint($pickup)) {
|
|
52
|
+
$this->error('Invalid pickup location for order ' . $order->public_id);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
57
55
|
|
|
58
|
-
|
|
59
|
-
$drivers = $this->getNearbyDriversForOrder($order, $pickup, $distance);
|
|
56
|
+
$drivers = $this->getNearbyDriversForOrder($order, $pickup, $distance);
|
|
60
57
|
|
|
61
|
-
|
|
62
|
-
$this->alert('Found (' . $drivers->count() . ') driver within ' . $distance . ' meters of the pickup location for order ' . $order->public_id);
|
|
58
|
+
$this->line('Checking order ' . $order->public_id . ' for nearby drivers within ' . $distance . ' meters.');
|
|
63
59
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
60
|
+
if ($drivers->count()) {
|
|
61
|
+
$order->dispatch(true);
|
|
62
|
+
$this->info('Order ' . $order->public_id . ' dispatched successfully to ' . $drivers->count() . ' nearby drivers.');
|
|
63
|
+
foreach ($drivers as $driver) {
|
|
64
|
+
$this->info('Pinging driver ' . $driver->name . ' (' . $driver->public_id . ') ...');
|
|
68
65
|
}
|
|
66
|
+
} else {
|
|
67
|
+
$this->warn('No available drivers found for order ' . $order->public_id);
|
|
69
68
|
}
|
|
70
|
-
|
|
69
|
+
}
|
|
71
70
|
}
|
|
72
71
|
|
|
73
72
|
/**
|
|
74
|
-
* Fetches dispatchable orders based on certain criteria.
|
|
73
|
+
* Fetches ad-hoc dispatchable orders based on certain criteria.
|
|
74
|
+
*
|
|
75
|
+
* Retrieves orders that are marked as ad-hoc and have not been dispatched yet. The orders
|
|
76
|
+
* are filtered by their scheduled dispatch time, order status, and whether a driver has been
|
|
77
|
+
* assigned to them. The method also applies filters based on sandbox mode.
|
|
78
|
+
*
|
|
79
|
+
* @return Collection returns a collection of orders that meet the criteria
|
|
75
80
|
*/
|
|
76
|
-
public function getDispatchableOrders():
|
|
81
|
+
public function getDispatchableOrders(): Collection
|
|
77
82
|
{
|
|
78
83
|
$sandbox = Utils::castBoolean($this->option('sandbox'));
|
|
79
84
|
$interval = 4;
|
|
@@ -100,8 +105,17 @@ class DispatchAdhocOrders extends Command
|
|
|
100
105
|
|
|
101
106
|
/**
|
|
102
107
|
* Fetches nearby drivers for a given order based on the pickup location and distance.
|
|
108
|
+
*
|
|
109
|
+
* Retrieves drivers who are within a specified distance from the pickup location of the order.
|
|
110
|
+
* The method supports a testing mode to simulate driver availability.
|
|
111
|
+
*
|
|
112
|
+
* @param Order $order the order for which drivers are being sought
|
|
113
|
+
* @param Point $pickup the geographic point representing the pickup location
|
|
114
|
+
* @param int $distance the maximum distance (in meters) within which drivers should be located
|
|
115
|
+
*
|
|
116
|
+
* @return Collection returns a collection of nearby drivers
|
|
103
117
|
*/
|
|
104
|
-
public function getNearbyDriversForOrder(Order $order, Point $pickup, int $distance):
|
|
118
|
+
public function getNearbyDriversForOrder(Order $order, Point $pickup, int $distance): Collection
|
|
105
119
|
{
|
|
106
120
|
$testing = Utils::castBoolean($this->option('testing'));
|
|
107
121
|
|
|
@@ -14,7 +14,7 @@ class DispatchOrders extends Command
|
|
|
14
14
|
*
|
|
15
15
|
* @var string
|
|
16
16
|
*/
|
|
17
|
-
protected $signature = 'fleetops:dispatch-orders {--sandbox=false}';
|
|
17
|
+
protected $signature = 'fleetops:dispatch-orders {--sandbox=false : Whether to use the sandbox database for testing purposes.}';
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* The console command description.
|
|
@@ -26,41 +26,47 @@ class DispatchOrders extends Command
|
|
|
26
26
|
/**
|
|
27
27
|
* Execute the console command.
|
|
28
28
|
*
|
|
29
|
-
*
|
|
29
|
+
* Processes and dispatches orders that are scheduled for today. Orders are
|
|
30
|
+
* filtered based on their dispatch status, scheduled time, and other criteria.
|
|
31
|
+
* The command allows for a sandbox mode for testing.
|
|
30
32
|
*/
|
|
31
|
-
public function handle()
|
|
33
|
+
public function handle(): void
|
|
32
34
|
{
|
|
33
35
|
// Set UTC as default timezone
|
|
34
36
|
date_default_timezone_set('UTC');
|
|
35
37
|
|
|
38
|
+
$sandboxMode = Utils::castBoolean($this->option('sandbox'));
|
|
39
|
+
$this->info('Running in ' . ($sandboxMode ? 'sandbox' : 'production') . ' mode.');
|
|
40
|
+
|
|
36
41
|
// Get all scheduled dispatchable orders
|
|
37
|
-
$orders = $this->getScheduledOrders();
|
|
42
|
+
$orders = $this->getScheduledOrders($sandboxMode);
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
$this->alert('Found (' . $orders->count() . ') Orders Scheduled for Dispatched -- Current Time: ' . Carbon::now()->toDateTimeString());
|
|
44
|
+
$this->alert('Found ' . $orders->count() . ' orders scheduled for dispatch. Current Time: ' . Carbon::now()->toDateTimeString());
|
|
41
45
|
|
|
42
46
|
// Dispatch each order
|
|
43
|
-
$orders
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
} else {
|
|
50
|
-
$this->warn('Order ' . $order->public_id . ' will be dispatched today today AT ' . $order->scheduled_at);
|
|
51
|
-
}
|
|
47
|
+
foreach ($orders as $order) {
|
|
48
|
+
if ($order->shouldDispatch()) {
|
|
49
|
+
$order->dispatch();
|
|
50
|
+
$this->info('Order ' . $order->public_id . ' dispatched successfully (' . $order->scheduled_at . ').');
|
|
51
|
+
} else {
|
|
52
|
+
$this->warn('Order ' . $order->public_id . ' is not ready for dispatch (' . $order->scheduled_at . ').');
|
|
52
53
|
}
|
|
53
|
-
|
|
54
|
+
}
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
/**
|
|
57
|
-
* Fetches scheduled dispatchable orders
|
|
58
|
+
* Fetches scheduled dispatchable orders.
|
|
59
|
+
*
|
|
60
|
+
* Retrieves orders that are scheduled for today and have not yet been dispatched.
|
|
61
|
+
* Filters orders based on dispatch status, order status, and scheduled date.
|
|
62
|
+
*
|
|
63
|
+
* @param bool $sandboxMode whether to use the sandbox database
|
|
64
|
+
*
|
|
65
|
+
* @return \Illuminate\Database\Eloquent\Collection returns a collection of orders that meet the criteria
|
|
58
66
|
*/
|
|
59
|
-
|
|
67
|
+
protected function getScheduledOrders(bool $sandboxMode): \Illuminate\Database\Eloquent\Collection
|
|
60
68
|
{
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return Order::on($sandbox ? 'sandbox' : 'mysql')
|
|
69
|
+
return Order::on($sandboxMode ? 'sandbox' : 'mysql')
|
|
64
70
|
->withoutGlobalScopes()
|
|
65
71
|
->where('dispatched', 0)
|
|
66
72
|
->whereIn('status', ['pending', 'created'])
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Console\Commands;
|
|
4
|
+
|
|
5
|
+
use Illuminate\Console\Command;
|
|
6
|
+
use Illuminate\Support\Str;
|
|
7
|
+
|
|
8
|
+
class FixInvalidPolymorphicRelationTypeNamespaces extends Command
|
|
9
|
+
{
|
|
10
|
+
/**
|
|
11
|
+
* The name and signature of the console command.
|
|
12
|
+
*
|
|
13
|
+
* @var string
|
|
14
|
+
*/
|
|
15
|
+
protected $signature = 'fleetops:fix-invalid-polymorphic-relation-type-namespaces';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The console command description.
|
|
19
|
+
*
|
|
20
|
+
* @var string
|
|
21
|
+
*/
|
|
22
|
+
protected $description = 'Fixes invalid namespaces in polymorphic relations across multiple models';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Handle the console command execution.
|
|
26
|
+
*
|
|
27
|
+
* Iterates over a predefined list of models and their respective polymorphic columns,
|
|
28
|
+
* calling a method to fix invalid namespace references in these polymorphic relations.
|
|
29
|
+
* This allows correction across multiple models in a single command execution.
|
|
30
|
+
*
|
|
31
|
+
* @return void
|
|
32
|
+
*/
|
|
33
|
+
public function handle()
|
|
34
|
+
{
|
|
35
|
+
$models = [
|
|
36
|
+
['model' => \Fleetbase\FleetOps\Models\Order::class, 'columns' => ['customer_type', 'facilitator_type']],
|
|
37
|
+
['model' => \Fleetbase\FleetOps\Models\Place::class, 'columns' => ['owner_type']],
|
|
38
|
+
['model' => \Fleetbase\FleetOps\Models\Entity::class, 'columns' => ['customer_type']],
|
|
39
|
+
['model' => \Fleetbase\FleetOps\Models\PurchaseRate::class, 'columns' => ['customer_type']],
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
foreach ($models as $modelConfig) {
|
|
43
|
+
$this->fixModelRelations($modelConfig['model'], $modelConfig['columns']);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Fixes invalid namespace references in polymorphic relations for a specific model.
|
|
49
|
+
*
|
|
50
|
+
* Given a model class and an array of its polymorphic relation columns, this method
|
|
51
|
+
* iterates through each record of the model, correcting any invalid namespaces in
|
|
52
|
+
* these columns. It outputs information about the process, including the number of
|
|
53
|
+
* records updated. This function is designed to be reusable for different models.
|
|
54
|
+
*
|
|
55
|
+
* @param string $modelClass the class name of the model to be processed
|
|
56
|
+
* @param array $columns an array of column names that have polymorphic relations
|
|
57
|
+
*/
|
|
58
|
+
protected function fixModelRelations(string $modelClass, array $columns): void
|
|
59
|
+
{
|
|
60
|
+
$modelInstance = new $modelClass();
|
|
61
|
+
$modelName = class_basename($modelInstance);
|
|
62
|
+
|
|
63
|
+
$this->info("Processing $modelName...");
|
|
64
|
+
|
|
65
|
+
$records = $modelInstance::query();
|
|
66
|
+
foreach ($columns as $column) {
|
|
67
|
+
$records->orWhereNotNull($column);
|
|
68
|
+
}
|
|
69
|
+
$records = $records->get();
|
|
70
|
+
$updatedCount = 0;
|
|
71
|
+
$this->alert('Checking ' . $records->count() . ' ' . Str::plural($modelName, $records->count()) . ' for invalid polymorphic relation type namespaces.');
|
|
72
|
+
|
|
73
|
+
foreach ($records as $record) {
|
|
74
|
+
foreach ($columns as $column) {
|
|
75
|
+
if (isset($record->$column)) {
|
|
76
|
+
$original = $record->$column;
|
|
77
|
+
$normalizedOriginal = ltrim($original, '\\');
|
|
78
|
+
|
|
79
|
+
if (Str::startsWith($normalizedOriginal, 'Fleetbase\Models')) {
|
|
80
|
+
$newNamespace = Str::replaceFirst('Fleetbase\Models', 'Fleetbase\FleetOps\Models', $normalizedOriginal);
|
|
81
|
+
$record->$column = $newNamespace;
|
|
82
|
+
$updatedCount++;
|
|
83
|
+
|
|
84
|
+
$this->info("{$modelName} ID {$record->id}: Corrected namespace from '$original' to '$newNamespace'.");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if ($record->isDirty()) {
|
|
90
|
+
$record->save();
|
|
91
|
+
$this->info("Saved changes for $modelName ID {$record->public_id}.");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
$this->info("Finished processing $modelName. Total updated records: $updatedCount.");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -13,7 +13,7 @@ class TrackOrderDistanceAndTime extends Command
|
|
|
13
13
|
*
|
|
14
14
|
* @var string
|
|
15
15
|
*/
|
|
16
|
-
protected $signature = 'fleetops:update-estimations';
|
|
16
|
+
protected $signature = 'fleetops:update-estimations {--provider : The distance and time calculation provider (calculate, google, or osrm)}';
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* The console command description.
|
|
@@ -32,31 +32,64 @@ class TrackOrderDistanceAndTime extends Command
|
|
|
32
32
|
// Set UTC as default timezone
|
|
33
33
|
date_default_timezone_set('UTC');
|
|
34
34
|
|
|
35
|
-
//
|
|
36
|
-
$
|
|
35
|
+
// Determine the provider
|
|
36
|
+
$provider = $this->option('provider');
|
|
37
|
+
if (!$provider) {
|
|
38
|
+
$provider = config('fleetops.distance_matrix.provider');
|
|
39
|
+
}
|
|
40
|
+
$this->info("Using '{$provider}' as the provider for calculations.");
|
|
37
41
|
|
|
38
|
-
//
|
|
39
|
-
$
|
|
42
|
+
// Get all active/ready orders
|
|
43
|
+
$orders = $this->getActiveOrders();
|
|
40
44
|
|
|
41
|
-
// Notify Current Time and # of Orders
|
|
42
|
-
$this->alert('Found
|
|
45
|
+
// Notify Current Time and # of Orders
|
|
46
|
+
$this->alert('Found ' . $orders->count() . ' orders to update tracking estimations. Current Time: ' . Carbon::now()->toDateTimeString());
|
|
43
47
|
|
|
44
48
|
// Update for each order
|
|
45
|
-
$
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
$updated = 0;
|
|
50
|
+
foreach ($orders as $order) {
|
|
51
|
+
try {
|
|
52
|
+
// Update order distance and time estimations based on the provider
|
|
53
|
+
$order->setDistanceAndTime(['provider' => $provider]);
|
|
54
|
+
$updated++;
|
|
55
|
+
$this->info("Order ID {$order->id} - Distance & Time estimations updated.");
|
|
56
|
+
} catch (\Exception $e) {
|
|
57
|
+
$this->error("Failed to update Order ID {$order->id} - Error: " . $e->getMessage());
|
|
48
58
|
}
|
|
49
|
-
|
|
59
|
+
}
|
|
50
60
|
|
|
51
|
-
//
|
|
52
|
-
$this->info(
|
|
61
|
+
// Summary info
|
|
62
|
+
$this->info("Updated $updated/" . $orders->count() . " orders' Distance & Time Estimations.");
|
|
53
63
|
}
|
|
54
64
|
|
|
55
65
|
/**
|
|
56
|
-
*
|
|
66
|
+
* Retrieves active orders that meet specific criteria.
|
|
67
|
+
*
|
|
68
|
+
* This method returns a collection of active orders with the following conditions:
|
|
69
|
+
* - Not in 'completed' or 'canceled' status.
|
|
70
|
+
* - Not marked as deleted (`deleted_at` is null).
|
|
71
|
+
* - Associated with a company (`company_uuid` is not null).
|
|
72
|
+
* - The order process has started (`started` is not null).
|
|
73
|
+
* - Contains a payload (`payload` relationship exists).
|
|
74
|
+
* - Created within the past month.
|
|
75
|
+
*
|
|
76
|
+
* The result includes related payload data, waypoints, and information about pickup and dropoff points.
|
|
77
|
+
* Global scopes are not applied to this query.
|
|
78
|
+
*
|
|
79
|
+
* @return \Illuminate\Database\Eloquent\Collection collection of relevant Order objects
|
|
57
80
|
*/
|
|
58
|
-
|
|
81
|
+
protected function getActiveOrders()
|
|
59
82
|
{
|
|
60
|
-
|
|
83
|
+
$oneMonthAgo = Carbon::now()->subMonth();
|
|
84
|
+
|
|
85
|
+
return Order::whereNotIn('status', ['completed', 'canceled'])
|
|
86
|
+
->whereNull('deleted_at')
|
|
87
|
+
->whereNotNull('company_uuid')
|
|
88
|
+
->whereNotNull('started')
|
|
89
|
+
->where('created_at', '>=', $oneMonthAgo)
|
|
90
|
+
->whereHas('payload')
|
|
91
|
+
->with(['payload', 'payload.waypoints', 'payload.pickup', 'payload.dropoff'])
|
|
92
|
+
->withoutGlobalScopes()
|
|
93
|
+
->get();
|
|
61
94
|
}
|
|
62
95
|
}
|
|
@@ -1505,13 +1505,11 @@ class Order extends Model
|
|
|
1505
1505
|
*
|
|
1506
1506
|
* @return Order the updated Order instance
|
|
1507
1507
|
*/
|
|
1508
|
-
public function setDistanceAndTime(): Order
|
|
1508
|
+
public function setDistanceAndTime($options = []): Order
|
|
1509
1509
|
{
|
|
1510
1510
|
$origin = $this->getCurrentOriginPosition();
|
|
1511
1511
|
$destination = $this->getDestinationPosition();
|
|
1512
|
-
|
|
1513
|
-
$matrix = Utils::getDrivingDistanceAndTime($origin, $destination);
|
|
1514
|
-
|
|
1512
|
+
$matrix = Utils::getDrivingDistanceAndTime($origin, $destination, $options);
|
|
1515
1513
|
if ($origin === null || $destination === null) {
|
|
1516
1514
|
return $this;
|
|
1517
1515
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Models;
|
|
4
4
|
|
|
5
5
|
use Fleetbase\Casts\Json;
|
|
6
|
+
use Fleetbase\Casts\PolymorphicType;
|
|
6
7
|
use Fleetbase\FleetOps\Casts\Point;
|
|
7
8
|
use Fleetbase\FleetOps\Support\Geocoding;
|
|
8
9
|
use Fleetbase\FleetOps\Support\Utils;
|
|
@@ -114,8 +115,9 @@ class Place extends Model
|
|
|
114
115
|
* @var array
|
|
115
116
|
*/
|
|
116
117
|
protected $casts = [
|
|
117
|
-
'meta'
|
|
118
|
-
'location'
|
|
118
|
+
'meta' => Json::class,
|
|
119
|
+
'location' => Point::class,
|
|
120
|
+
'owner_type' => PolymorphicType::class,
|
|
119
121
|
];
|
|
120
122
|
|
|
121
123
|
/**
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Models;
|
|
4
4
|
|
|
5
5
|
use Fleetbase\Casts\Json;
|
|
6
|
+
use Fleetbase\Casts\PolymorphicType;
|
|
6
7
|
use Fleetbase\FleetOps\Support\Utils;
|
|
7
8
|
use Fleetbase\Models\Model;
|
|
8
9
|
use Fleetbase\Traits\HasApiModelBehavior;
|
|
@@ -57,7 +58,8 @@ class PurchaseRate extends Model
|
|
|
57
58
|
* @var array
|
|
58
59
|
*/
|
|
59
60
|
protected $casts = [
|
|
60
|
-
'meta'
|
|
61
|
+
'meta' => Json::class,
|
|
62
|
+
'customer_type' => PolymorphicType::class,
|
|
61
63
|
];
|
|
62
64
|
|
|
63
65
|
/**
|
|
@@ -48,6 +48,7 @@ class FleetOpsServiceProvider extends CoreServiceProvider
|
|
|
48
48
|
\Fleetbase\FleetOps\Console\Commands\TrackOrderDistanceAndTime::class,
|
|
49
49
|
\Fleetbase\FleetOps\Console\Commands\FixDriverCompanies::class,
|
|
50
50
|
\Fleetbase\FleetOps\Console\Commands\FixLegacyOrderConfigs::class,
|
|
51
|
+
\Fleetbase\FleetOps\Console\Commands\FixInvalidPolymorphicRelationTypeNamespaces::class,
|
|
51
52
|
];
|
|
52
53
|
|
|
53
54
|
/**
|
|
@@ -28,28 +28,4 @@ class Algo
|
|
|
28
28
|
|
|
29
29
|
return $result;
|
|
30
30
|
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Calculates driving distance and time using Google distance matric.
|
|
34
|
-
*
|
|
35
|
-
* @return array
|
|
36
|
-
*/
|
|
37
|
-
public static function calculateDrivingDistanceAndTime($lat1, $long1, $lat2, $long2)
|
|
38
|
-
{
|
|
39
|
-
$url =
|
|
40
|
-
'https://maps.googleapis.com/maps/api/distancematrix/json?origins=' . $lat1 . ',' . $long1 . '&destinations=' . $lat2 . ',' . $long2 . '&mode=driving&key=' . config('services.google.key');
|
|
41
|
-
$ch = curl_init();
|
|
42
|
-
curl_setopt($ch, CURLOPT_URL, $url);
|
|
43
|
-
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
|
44
|
-
curl_setopt($ch, CURLOPT_PROXYPORT, 3128);
|
|
45
|
-
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
|
|
46
|
-
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
|
|
47
|
-
$response = curl_exec($ch);
|
|
48
|
-
curl_close($ch);
|
|
49
|
-
$response_a = json_decode($response, true);
|
|
50
|
-
$dist = $response_a['rows'][0]['elements'][0]['distance']['value'];
|
|
51
|
-
$time = $response_a['rows'][0]['elements'][0]['duration']['value'];
|
|
52
|
-
|
|
53
|
-
return ['distance' => $dist, 'time' => $time];
|
|
54
|
-
}
|
|
55
31
|
}
|
|
@@ -14,7 +14,7 @@ namespace Fleetbase\FleetOps\Support;
|
|
|
14
14
|
* @license https://opensource.org/licenses/MIT MIT License
|
|
15
15
|
* @author Ronald A. Richardson
|
|
16
16
|
*/
|
|
17
|
-
class DistanceMatrix
|
|
17
|
+
class DistanceMatrix implements \JsonSerializable
|
|
18
18
|
{
|
|
19
19
|
public ?float $distance;
|
|
20
20
|
public ?float $time;
|
|
@@ -24,4 +24,12 @@ class DistanceMatrix
|
|
|
24
24
|
$this->distance = $distance;
|
|
25
25
|
$this->time = $time;
|
|
26
26
|
}
|
|
27
|
+
|
|
28
|
+
public function jsonSerialize()
|
|
29
|
+
{
|
|
30
|
+
return [
|
|
31
|
+
'distance' => $this->distance,
|
|
32
|
+
'time' => $this->time,
|
|
33
|
+
];
|
|
34
|
+
}
|
|
27
35
|
}
|
|
@@ -35,6 +35,13 @@ class OSRM
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
$coordinates = "{$start->getLng()},{$start->getLat()};{$end->getLng()},{$end->getLat()}";
|
|
38
|
+
|
|
39
|
+
return static::getRouteFromCoordinatesString($coordinates, $queryParameters);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public static function getRouteFromCoordinatesString(string $coordinates, array $queryParameters = [])
|
|
43
|
+
{
|
|
44
|
+
$cacheKey = 'getRouteFromCoordinatesString:' . md5($coordinates . serialize($queryParameters));
|
|
38
45
|
$url = self::$baseUrl . "/route/v1/driving/{$coordinates}";
|
|
39
46
|
$response = Http::get($url, $queryParameters);
|
|
40
47
|
$data = $response->json();
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Support;
|
|
4
4
|
|
|
5
5
|
use Fleetbase\FleetOps\Flow\Activity;
|
|
6
|
+
use Fleetbase\LaravelMysqlSpatial\Types\Point;
|
|
6
7
|
use Fleetbase\Support\Utils as FleetbaseUtils;
|
|
7
8
|
use Illuminate\Support\Arr;
|
|
8
9
|
use Illuminate\Support\Facades\DB;
|
|
@@ -95,11 +96,11 @@ class Utils extends FleetbaseUtils
|
|
|
95
96
|
/**
|
|
96
97
|
* Unpacks a mysql POINT column from binary to array.
|
|
97
98
|
*/
|
|
98
|
-
public static function mysqlPointAsGeometry(string $rawPoint):
|
|
99
|
+
public static function mysqlPointAsGeometry(string $rawPoint): Point
|
|
99
100
|
{
|
|
100
101
|
$coordinates = static::unpackPoint($rawPoint);
|
|
101
102
|
|
|
102
|
-
return new
|
|
103
|
+
return new Point($coordinates['lon'], $coordinates['lat']);
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
/**
|
|
@@ -162,7 +163,7 @@ class Utils extends FleetbaseUtils
|
|
|
162
163
|
public static function isCoordinates($coordinates): bool
|
|
163
164
|
{
|
|
164
165
|
try {
|
|
165
|
-
return static::getPointFromMixed($coordinates) instanceof
|
|
166
|
+
return static::getPointFromMixed($coordinates) instanceof Point;
|
|
166
167
|
} catch (\Throwable $e) {
|
|
167
168
|
return false;
|
|
168
169
|
}
|
|
@@ -175,9 +176,9 @@ class Utils extends FleetbaseUtils
|
|
|
175
176
|
*
|
|
176
177
|
* @param mixed $coordinates the coordinates input value to extract a coordinate from
|
|
177
178
|
*
|
|
178
|
-
* @return
|
|
179
|
+
* @return Point the extracted Point object
|
|
179
180
|
*/
|
|
180
|
-
public static function getPointFromMixed($coordinates):
|
|
181
|
+
public static function getPointFromMixed($coordinates): ?Point
|
|
181
182
|
{
|
|
182
183
|
$latitude = null;
|
|
183
184
|
$longitude = null;
|
|
@@ -199,7 +200,7 @@ class Utils extends FleetbaseUtils
|
|
|
199
200
|
$coordinates = $coordinates->getSpatialValue();
|
|
200
201
|
}
|
|
201
202
|
|
|
202
|
-
if ($coordinates instanceof
|
|
203
|
+
if ($coordinates instanceof Point) {
|
|
203
204
|
$latitude = $coordinates->getLat();
|
|
204
205
|
$longitude = $coordinates->getLng();
|
|
205
206
|
} elseif (static::isGeoJson($coordinates)) {
|
|
@@ -216,7 +217,7 @@ class Utils extends FleetbaseUtils
|
|
|
216
217
|
|
|
217
218
|
if (static::isJson($coordinatesJson)) {
|
|
218
219
|
try {
|
|
219
|
-
$coordinates =
|
|
220
|
+
$coordinates = Point::fromJson($coordinatesJson);
|
|
220
221
|
} catch (\Throwable $e) {
|
|
221
222
|
if ($coordinatesInGeoJson) {
|
|
222
223
|
return static::getPointFromMixed($coordinatesInGeoJson);
|
|
@@ -312,7 +313,7 @@ class Utils extends FleetbaseUtils
|
|
|
312
313
|
$longitude = $coords[1];
|
|
313
314
|
}
|
|
314
315
|
|
|
315
|
-
if ($coordinates instanceof
|
|
316
|
+
if ($coordinates instanceof Point) {
|
|
316
317
|
return $coordinates;
|
|
317
318
|
}
|
|
318
319
|
|
|
@@ -321,7 +322,7 @@ class Utils extends FleetbaseUtils
|
|
|
321
322
|
throw new \Exception('Attempted to resolve Point from invalid location.');
|
|
322
323
|
}
|
|
323
324
|
|
|
324
|
-
return new
|
|
325
|
+
return new Point((float) $latitude, (float) $longitude);
|
|
325
326
|
}
|
|
326
327
|
|
|
327
328
|
/**
|
|
@@ -334,7 +335,7 @@ class Utils extends FleetbaseUtils
|
|
|
334
335
|
*/
|
|
335
336
|
public static function isCoordinatesStrict($coordinates)
|
|
336
337
|
{
|
|
337
|
-
return static::getPointFromCoordinatesStrict($coordinates) instanceof
|
|
338
|
+
return static::getPointFromCoordinatesStrict($coordinates) instanceof Point;
|
|
338
339
|
}
|
|
339
340
|
|
|
340
341
|
/**
|
|
@@ -343,15 +344,15 @@ class Utils extends FleetbaseUtils
|
|
|
343
344
|
*
|
|
344
345
|
* @param mixed $coordinates the coordinates, which can be an array, object, or string
|
|
345
346
|
*
|
|
346
|
-
* @return
|
|
347
|
+
* @return Point Returns the Point instance
|
|
347
348
|
*/
|
|
348
|
-
public static function getPointFromCoordinatesStrict($coordinates):
|
|
349
|
+
public static function getPointFromCoordinatesStrict($coordinates): ?Point
|
|
349
350
|
{
|
|
350
351
|
if ($coordinates instanceof \Fleetbase\LaravelMysqlSpatial\Eloquent\SpatialExpression) {
|
|
351
352
|
$coordinates = $coordinates->getSpatialValue();
|
|
352
353
|
}
|
|
353
354
|
|
|
354
|
-
if ($coordinates instanceof
|
|
355
|
+
if ($coordinates instanceof Point) {
|
|
355
356
|
return $coordinates;
|
|
356
357
|
}
|
|
357
358
|
|
|
@@ -363,7 +364,7 @@ class Utils extends FleetbaseUtils
|
|
|
363
364
|
}
|
|
364
365
|
|
|
365
366
|
if (static::isJson($coordinatesJson)) {
|
|
366
|
-
$coordinates =
|
|
367
|
+
$coordinates = Point::fromJson($coordinatesJson);
|
|
367
368
|
}
|
|
368
369
|
}
|
|
369
370
|
|
|
@@ -372,7 +373,7 @@ class Utils extends FleetbaseUtils
|
|
|
372
373
|
$longitude = static::or($coordinates, ['lon', '_lon', 'long', 'lng', '_lng', '_longitude', 'longitude', 'y', '1']);
|
|
373
374
|
|
|
374
375
|
if (is_numeric($latitude) && is_numeric($longitude)) {
|
|
375
|
-
$coordinates = new
|
|
376
|
+
$coordinates = new Point((float) $latitude, (float) $longitude);
|
|
376
377
|
}
|
|
377
378
|
}
|
|
378
379
|
|
|
@@ -412,11 +413,11 @@ class Utils extends FleetbaseUtils
|
|
|
412
413
|
$longitude = $coords[1];
|
|
413
414
|
|
|
414
415
|
if (is_numeric($latitude) && is_numeric($longitude)) {
|
|
415
|
-
$coordinates = new
|
|
416
|
+
$coordinates = new Point((float) $latitude, (float) $longitude);
|
|
416
417
|
}
|
|
417
418
|
}
|
|
418
419
|
|
|
419
|
-
if ($coordinates instanceof
|
|
420
|
+
if ($coordinates instanceof Point) {
|
|
420
421
|
return $coordinates;
|
|
421
422
|
}
|
|
422
423
|
|
|
@@ -444,7 +445,7 @@ class Utils extends FleetbaseUtils
|
|
|
444
445
|
$coordinates = $coordinates->location;
|
|
445
446
|
}
|
|
446
447
|
|
|
447
|
-
if ($coordinates instanceof
|
|
448
|
+
if ($coordinates instanceof Point) {
|
|
448
449
|
$latitude = $coordinates->getLat();
|
|
449
450
|
$longitude = $coordinates->getLng();
|
|
450
451
|
} elseif (is_array($coordinates) || is_object($coordinates)) {
|
|
@@ -534,22 +535,22 @@ class Utils extends FleetbaseUtils
|
|
|
534
535
|
*
|
|
535
536
|
* @param mixed $coordinates the input value representing a location or set of coordinates
|
|
536
537
|
*
|
|
537
|
-
* @return
|
|
538
|
+
* @return Point the extracted point value
|
|
538
539
|
*/
|
|
539
|
-
public static function getPointFromCoordinates($coordinates):
|
|
540
|
+
public static function getPointFromCoordinates($coordinates): Point
|
|
540
541
|
{
|
|
541
|
-
if ($coordinates instanceof
|
|
542
|
+
if ($coordinates instanceof Point) {
|
|
542
543
|
return $coordinates;
|
|
543
544
|
}
|
|
544
545
|
|
|
545
546
|
if (is_null($coordinates) || !static::isCoordinates($coordinates)) {
|
|
546
|
-
return new
|
|
547
|
+
return new Point(0, 0);
|
|
547
548
|
}
|
|
548
549
|
|
|
549
550
|
$latitude = static::getLatitudeFromCoordinates($coordinates);
|
|
550
551
|
$longitude = static::getLongitudeFromCoordinates($coordinates);
|
|
551
552
|
|
|
552
|
-
return new
|
|
553
|
+
return new Point($latitude, $longitude);
|
|
553
554
|
}
|
|
554
555
|
|
|
555
556
|
/**
|
|
@@ -563,19 +564,19 @@ class Utils extends FleetbaseUtils
|
|
|
563
564
|
{
|
|
564
565
|
$wkt = 'POINT(0 0)';
|
|
565
566
|
|
|
566
|
-
if ($point instanceof
|
|
567
|
+
if ($point instanceof Point) {
|
|
567
568
|
$wkt = $point->toWKT();
|
|
568
569
|
}
|
|
569
570
|
|
|
570
571
|
if (is_array($point)) {
|
|
571
572
|
$json = json_encode($point);
|
|
572
|
-
$p =
|
|
573
|
+
$p = Point::fromJson($json);
|
|
573
574
|
|
|
574
575
|
$wkt = $p->toWkt();
|
|
575
576
|
}
|
|
576
577
|
|
|
577
578
|
if (is_string($point)) {
|
|
578
|
-
$p =
|
|
579
|
+
$p = Point::fromString($point);
|
|
579
580
|
|
|
580
581
|
$wkt = $p->toWKT();
|
|
581
582
|
}
|
|
@@ -605,133 +606,75 @@ class Utils extends FleetbaseUtils
|
|
|
605
606
|
*
|
|
606
607
|
* @param string $data the binary string representing the point
|
|
607
608
|
*
|
|
608
|
-
* @return
|
|
609
|
+
* @return Point the Laravel MySQL Spatial Point instance
|
|
609
610
|
*/
|
|
610
|
-
public static function rawPointToPoint(string $data):
|
|
611
|
+
public static function rawPointToPoint(string $data): Point
|
|
611
612
|
{
|
|
612
613
|
// Use the `unpack` function to extract the X, Y, and SRID values from the binary string.
|
|
613
614
|
$res = unpack('lSRID/CByteOrder/lTypeInfo/dX/dY', $data);
|
|
614
615
|
|
|
615
616
|
// Return a new Laravel MySQL Spatial Point instance with the X, Y, and SRID values.
|
|
616
|
-
return new
|
|
617
|
+
return new Point($res['X'], $res['Y'], $res['SRID']);
|
|
617
618
|
}
|
|
618
619
|
|
|
619
620
|
/**
|
|
620
|
-
* Calculates driving distance and time
|
|
621
|
-
* Returns distance in meters and time in seconds.
|
|
621
|
+
* Calculates the driving distance and time between two points.
|
|
622
622
|
*
|
|
623
|
-
*
|
|
624
|
-
*
|
|
623
|
+
* This function attempts to find the driving distance and time from the origin to the destination
|
|
624
|
+
* using various map APIs. It first attempts to resolve the origin and destination from various input types
|
|
625
|
+
* into geographical coordinates. Then, based on the provider specified in the options or defaulting to configured provider,
|
|
626
|
+
* it queries the respective service for the driving distance and time. The result is returned as a DistanceMatrix
|
|
627
|
+
* object with the distance in meters and the time in seconds.
|
|
628
|
+
*
|
|
629
|
+
* @param mixed $origin The origin of the journey. Can be a Place model, a string, or an array with latitude and longitude.
|
|
630
|
+
* @param mixed $destination The destination of the journey. Similar to the origin, it accepts multiple input types.
|
|
631
|
+
*
|
|
632
|
+
* @return DistanceMatrix an object containing the distance and time of the journey
|
|
633
|
+
*
|
|
634
|
+
* @throws \Throwable throws any exception encountered during the resolution of points or API calls
|
|
625
635
|
*/
|
|
626
|
-
public static function getDrivingDistanceAndTime($origin, $destination): DistanceMatrix
|
|
636
|
+
public static function getDrivingDistanceAndTime($origin, $destination, $options = []): DistanceMatrix
|
|
627
637
|
{
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
$
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
$origin = static::createObject(
|
|
650
|
-
[
|
|
651
|
-
'latitude' => 0,
|
|
652
|
-
'longitude' => 0,
|
|
653
|
-
]
|
|
654
|
-
);
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
if ($destination instanceof \Fleetbase\FleetOps\Models\Place) {
|
|
659
|
-
$destination = static::createObject(
|
|
660
|
-
[
|
|
661
|
-
'latitude' => $destination->location->getLat(),
|
|
662
|
-
'longitude' => $destination->location->getLng(),
|
|
663
|
-
]
|
|
664
|
-
);
|
|
665
|
-
} else {
|
|
666
|
-
try {
|
|
667
|
-
$point = static::getPointFromMixed($destination);
|
|
668
|
-
$destination = static::createObject(
|
|
669
|
-
[
|
|
670
|
-
'latitude' => $point->getLat(),
|
|
671
|
-
'longitude' => $point->getLng(),
|
|
672
|
-
]
|
|
673
|
-
);
|
|
674
|
-
} catch (\Throwable $e) {
|
|
675
|
-
if (app()->bound('sentry')) {
|
|
676
|
-
app('sentry')->captureException($e);
|
|
677
|
-
}
|
|
678
|
-
// create dummy $destination
|
|
679
|
-
$destination = static::createObject(
|
|
680
|
-
[
|
|
681
|
-
'latitude' => 0,
|
|
682
|
-
'longitude' => 0,
|
|
683
|
-
]
|
|
684
|
-
);
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
$cacheKey = $origin->latitude . ':' . $origin->longitude . ':' . $destination->latitude . ':' . $destination->longitude;
|
|
689
|
-
|
|
690
|
-
// check cache for results
|
|
691
|
-
$cachedResult = Redis::get($cacheKey);
|
|
692
|
-
|
|
693
|
-
if ($cachedResult) {
|
|
694
|
-
$json = json_decode($cachedResult);
|
|
695
|
-
|
|
696
|
-
if (!empty($json->distance) && !empty($json->time)) {
|
|
697
|
-
return new DistanceMatrix($json->distance, $json->time);
|
|
698
|
-
}
|
|
638
|
+
$origin = static::getPointFromMixed($origin);
|
|
639
|
+
$destination = static::getPointFromMixed($destination);
|
|
640
|
+
$origins = $origin->getLat() . ',' . $origin->getLng();
|
|
641
|
+
$destinations = $destination->getLat() . ',' . $destination->getLng();
|
|
642
|
+
|
|
643
|
+
// Create empty distance matric
|
|
644
|
+
$distanceMatrix = new DistanceMatrix(0, 0);
|
|
645
|
+
$provider = data_get($options, 'provider', config('fleetops.distance_matrix.provider'));
|
|
646
|
+
switch ($provider) {
|
|
647
|
+
case 'calculate':
|
|
648
|
+
$distanceMatrix = static::calculateDrivingDistanceAndTime($origins, $destinations);
|
|
649
|
+
break;
|
|
650
|
+
|
|
651
|
+
case 'osrm':
|
|
652
|
+
$distanceMatrix = static::getDistanceMatrixFromOSRM($origins, $destinations);
|
|
653
|
+
break;
|
|
654
|
+
|
|
655
|
+
case 'google':
|
|
656
|
+
default:
|
|
657
|
+
$distanceMatrix = static::getDistanceMatrixFromGoogle($origins, $destinations);
|
|
658
|
+
break;
|
|
699
659
|
}
|
|
700
660
|
|
|
701
|
-
$
|
|
702
|
-
'https://maps.googleapis.com/maps/api/distancematrix/json',
|
|
703
|
-
[
|
|
704
|
-
'origins' => $origin->latitude . ',' . $origin->longitude,
|
|
705
|
-
'destinations' => $destination->latitude . ',' . $destination->longitude,
|
|
706
|
-
'mode' => 'driving',
|
|
707
|
-
'key' => env('GOOGLE_MAPS_API_KEY'),
|
|
708
|
-
]
|
|
709
|
-
)->json();
|
|
710
|
-
|
|
711
|
-
$distance = data_get($response, 'rows.0.elements.0.distance.value');
|
|
712
|
-
$time = data_get($response, 'rows.0.elements.0.duration.value');
|
|
713
|
-
|
|
714
|
-
$result = static::createObject(
|
|
715
|
-
[
|
|
716
|
-
'distance' => $distance,
|
|
717
|
-
'time' => $time,
|
|
718
|
-
]
|
|
719
|
-
);
|
|
720
|
-
|
|
721
|
-
// cache result
|
|
722
|
-
Redis::set($cacheKey, json_encode($result));
|
|
723
|
-
|
|
724
|
-
return new DistanceMatrix($distance, $time);
|
|
661
|
+
return $distanceMatrix;
|
|
725
662
|
}
|
|
726
663
|
|
|
727
664
|
/**
|
|
728
|
-
*
|
|
729
|
-
* Returns distance in meters and time in seconds.
|
|
665
|
+
* Computes a distance matrix for multiple origins and destinations.
|
|
730
666
|
*
|
|
731
|
-
*
|
|
732
|
-
*
|
|
667
|
+
* This function is designed to handle arrays of origins and destinations to calculate a distance matrix.
|
|
668
|
+
* It converts the input arrays into a string format suitable for the map API request. The provider specified
|
|
669
|
+
* in the options (default is Google) determines which map service API is used for the calculations.
|
|
670
|
+
*
|
|
671
|
+
* @param array|\Illuminate\Support\Collection $origins array or collection of origin points
|
|
672
|
+
* @param array|\Illuminate\Support\Collection $destinations array or collection of destination points
|
|
673
|
+
* @param array $options options for API calls, like the provider and mode of transport
|
|
674
|
+
*
|
|
675
|
+
* @return DistanceMatrix an object representing the distance and time between the furthest origins and destinations
|
|
733
676
|
*/
|
|
734
|
-
public static function distanceMatrix($origins = [], $destinations = []): DistanceMatrix
|
|
677
|
+
public static function distanceMatrix($origins = [], $destinations = [], $options = []): DistanceMatrix
|
|
735
678
|
{
|
|
736
679
|
if ($origins instanceof \Illuminate\Support\Collection) {
|
|
737
680
|
$origins = $origins->all();
|
|
@@ -741,57 +684,83 @@ class Utils extends FleetbaseUtils
|
|
|
741
684
|
$destinations = $destinations->all();
|
|
742
685
|
}
|
|
743
686
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
687
|
+
// Convert origins and destinatinos to string
|
|
688
|
+
$origins = static::createCoordinateStringFromPlacesArray($origins);
|
|
689
|
+
$destinations = static::createCoordinateStringFromPlacesArray($destinations);
|
|
690
|
+
|
|
691
|
+
// Create empty distance matric
|
|
692
|
+
$distanceMatrix = new DistanceMatrix(0, 0);
|
|
693
|
+
$provider = data_get($options, 'provider', config('fleetops.distance_matrix.provider'));
|
|
694
|
+
switch ($provider) {
|
|
695
|
+
case 'calculate':
|
|
696
|
+
$distanceMatrix = static::calculateDrivingDistanceAndTime($origins, $destinations);
|
|
697
|
+
break;
|
|
698
|
+
|
|
699
|
+
case 'osrm':
|
|
700
|
+
$distanceMatrix = static::getDistanceMatrixFromOSRM($origins, $destinations);
|
|
701
|
+
break;
|
|
702
|
+
|
|
703
|
+
case 'google':
|
|
704
|
+
default:
|
|
705
|
+
$distanceMatrix = static::getDistanceMatrixFromGoogle($origins, $destinations);
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
758
708
|
|
|
759
|
-
$
|
|
760
|
-
|
|
761
|
-
$point = static::getPointFromMixed($destination);
|
|
762
|
-
$destination = static::createObject(
|
|
763
|
-
[
|
|
764
|
-
'latitude' => $point->getLat(),
|
|
765
|
-
'longitude' => $point->getLng(),
|
|
766
|
-
]
|
|
767
|
-
);
|
|
768
|
-
|
|
769
|
-
return $destination;
|
|
770
|
-
},
|
|
771
|
-
$destinations
|
|
772
|
-
);
|
|
709
|
+
return $distanceMatrix;
|
|
710
|
+
}
|
|
773
711
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
712
|
+
/**
|
|
713
|
+
* Converts an array of places into a coordinate string.
|
|
714
|
+
*
|
|
715
|
+
* Accepts an array of places (or any type that can be converted into geographical points)
|
|
716
|
+
* and converts each into a 'latitude,longitude' format. These coordinates are then concatenated into a single string,
|
|
717
|
+
* separated by the specified separator. The function gracefully handles any errors in the conversion process.
|
|
718
|
+
*
|
|
719
|
+
* @param array $places array of places to be converted into coordinates
|
|
720
|
+
* @param string $separator separator used to concatenate the coordinates into a single string
|
|
721
|
+
*
|
|
722
|
+
* @return string a concatenated string of coordinates
|
|
723
|
+
*/
|
|
724
|
+
public static function createCoordinateStringFromPlacesArray(array $places = [], $separator = '|'): string
|
|
725
|
+
{
|
|
726
|
+
$coordinates = array_map(
|
|
727
|
+
function ($place) {
|
|
728
|
+
try {
|
|
729
|
+
$point = static::getPointFromMixed($place);
|
|
730
|
+
} catch (\Throwable $e) {
|
|
731
|
+
$point = new Point(0, 0);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
return $point;
|
|
778
735
|
},
|
|
779
|
-
$
|
|
780
|
-
)
|
|
736
|
+
$places
|
|
737
|
+
);
|
|
781
738
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
return $destination->latitude . ',' . $destination->longitude;
|
|
739
|
+
return implode($separator, array_map(
|
|
740
|
+
function ($point) {
|
|
741
|
+
return $point->getLat() . ',' . $point->getLng();
|
|
786
742
|
},
|
|
787
|
-
$
|
|
743
|
+
$coordinates
|
|
788
744
|
));
|
|
745
|
+
}
|
|
789
746
|
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
747
|
+
/**
|
|
748
|
+
* Retrieves a distance matrix using the Google Distance Matrix API.
|
|
749
|
+
*
|
|
750
|
+
* This function calculates the distance and time between origins and destinations using the Google API.
|
|
751
|
+
* It first checks if there is a cached result in Redis and, if not, performs an API call to Google.
|
|
752
|
+
* The results are then cached in Redis and returned as a DistanceMatrix object.
|
|
753
|
+
*
|
|
754
|
+
* @param string $origins a string of origins in 'latitude,longitude' format, separated by a specified delimiter
|
|
755
|
+
* @param string $destinations a string of destinations in a similar format to origins
|
|
756
|
+
* @param array $options additional options for the API call, like the mode of transport
|
|
757
|
+
*
|
|
758
|
+
* @return DistanceMatrix the distance and time between the given origins and destinations
|
|
759
|
+
*/
|
|
760
|
+
public static function getDistanceMatrixFromGoogle(string $origins, string $destinations, $options = []): DistanceMatrix
|
|
761
|
+
{
|
|
762
|
+
$cacheKey = md5($origins . '_' . $destinations);
|
|
793
763
|
$cachedResult = Redis::get($cacheKey);
|
|
794
|
-
|
|
795
764
|
if ($cachedResult) {
|
|
796
765
|
$json = json_decode($cachedResult);
|
|
797
766
|
|
|
@@ -803,9 +772,9 @@ class Utils extends FleetbaseUtils
|
|
|
803
772
|
$response = Http::withOptions(['verify' => false])->get(
|
|
804
773
|
'https://maps.googleapis.com/maps/api/distancematrix/json',
|
|
805
774
|
[
|
|
806
|
-
'origins' => $
|
|
807
|
-
'destinations' => $
|
|
808
|
-
'mode' => 'driving',
|
|
775
|
+
'origins' => $origins,
|
|
776
|
+
'destinations' => $destinations,
|
|
777
|
+
'mode' => data_get($options, 'mode', 'driving'),
|
|
809
778
|
'key' => env('GOOGLE_MAPS_API_KEY'),
|
|
810
779
|
]
|
|
811
780
|
)->json();
|
|
@@ -813,17 +782,69 @@ class Utils extends FleetbaseUtils
|
|
|
813
782
|
$distance = (float) data_get($response, 'rows.0.elements.0.distance.value', 0);
|
|
814
783
|
$time = (float) data_get($response, 'rows.0.elements.0.duration.value', 0);
|
|
815
784
|
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
'distance' => $distance,
|
|
819
|
-
'time' => $time,
|
|
820
|
-
]
|
|
821
|
-
);
|
|
785
|
+
// Create distance matrix
|
|
786
|
+
$distanceMatrix = new DistanceMatrix($distance, $time);
|
|
822
787
|
|
|
823
|
-
//
|
|
824
|
-
Redis::set($cacheKey, json_encode($
|
|
788
|
+
// Cache distance matrix
|
|
789
|
+
Redis::set($cacheKey, json_encode($distanceMatrix));
|
|
825
790
|
|
|
826
|
-
return
|
|
791
|
+
return $distanceMatrix;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* Calculates distance and time between points using the OSRM API.
|
|
796
|
+
*
|
|
797
|
+
* Similar to the Google Distance Matrix function, this uses the OSRM (Open Source Routing Machine) API.
|
|
798
|
+
* It first checks for cached results before making a new API request. The origins and destinations are
|
|
799
|
+
* reformatted to the OSRM API requirement and the response is parsed into a DistanceMatrix object.
|
|
800
|
+
*
|
|
801
|
+
* @param string $origins comma-separated string of origin coordinates
|
|
802
|
+
* @param string $destinations comma-separated string of destination coordinates
|
|
803
|
+
*
|
|
804
|
+
* @return DistanceMatrix an object containing the distance and time for the specified journey
|
|
805
|
+
*/
|
|
806
|
+
public static function getDistanceMatrixFromOSRM(string $origins, string $destinations): DistanceMatrix
|
|
807
|
+
{
|
|
808
|
+
$cacheKey = md5($origins . '_' . $destinations);
|
|
809
|
+
$cachedResult = Redis::get($cacheKey);
|
|
810
|
+
if ($cachedResult) {
|
|
811
|
+
$json = json_decode($cachedResult);
|
|
812
|
+
|
|
813
|
+
if (!empty($json->distance) && !empty($json->time)) {
|
|
814
|
+
return new DistanceMatrix($json->distance, $json->time);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// OSRM uses semi-colon to delimit coordinates so replace any `|` as we typically use with `;`
|
|
819
|
+
$origins = Str::replace('|', ';', $origins);
|
|
820
|
+
$destinations = Str::replace('|', ';', $destinations);
|
|
821
|
+
$coordinatesString = $origins . ';' . $destinations;
|
|
822
|
+
// Split the string into individual coordinates
|
|
823
|
+
$coordinatesArray = explode(';', $coordinatesString);
|
|
824
|
+
|
|
825
|
+
// Reverse the latitude and longitude in each coordinate
|
|
826
|
+
$reversedCoordinates = array_map(function ($coordinate) {
|
|
827
|
+
$parts = explode(',', $coordinate);
|
|
828
|
+
if (count($parts) == 2) {
|
|
829
|
+
return $parts[1] . ',' . $parts[0]; // Swap latitude and longitude
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
return $coordinate; // Return original if it's not a valid coordinate
|
|
833
|
+
}, $coordinatesArray);
|
|
834
|
+
|
|
835
|
+
// Join the reversed coordinates back into a string
|
|
836
|
+
$reversedCoordinatesString = implode(';', $reversedCoordinates);
|
|
837
|
+
$response = OSRM::getRouteFromCoordinatesString($reversedCoordinatesString);
|
|
838
|
+
$distance = (float) data_get($response, 'routes.0.distance', 0);
|
|
839
|
+
$time = (float) data_get($response, 'routes.0.duration', 0);
|
|
840
|
+
|
|
841
|
+
// Create distance matrix
|
|
842
|
+
$distanceMatrix = new DistanceMatrix($distance, $time);
|
|
843
|
+
|
|
844
|
+
// Cache distance matrix
|
|
845
|
+
Redis::set($cacheKey, json_encode($distanceMatrix));
|
|
846
|
+
|
|
847
|
+
return $distanceMatrix;
|
|
827
848
|
}
|
|
828
849
|
|
|
829
850
|
/**
|
|
@@ -883,13 +904,13 @@ class Utils extends FleetbaseUtils
|
|
|
883
904
|
* https://en.wikipedia.org/wiki/Great-circle_distance#Formulas
|
|
884
905
|
* https://en.wikipedia.org/wiki/Antipodal_point
|
|
885
906
|
*
|
|
886
|
-
* @param
|
|
887
|
-
* @param
|
|
907
|
+
* @param Point Starting point
|
|
908
|
+
* @param Point Ending point
|
|
888
909
|
* @param float $earthRadius Mean earth radius in [m]
|
|
889
910
|
*
|
|
890
911
|
* @return float Distance between points in [m] (same as earthRadius)
|
|
891
912
|
*/
|
|
892
|
-
public static function vincentyGreatCircleDistance(
|
|
913
|
+
public static function vincentyGreatCircleDistance(Point $from, Point $to, float $earthRadius = 6371000): float
|
|
893
914
|
{
|
|
894
915
|
// convert from degrees to radians
|
|
895
916
|
$latFrom = deg2rad($from->getLat());
|
|
@@ -910,7 +931,7 @@ class Utils extends FleetbaseUtils
|
|
|
910
931
|
/**
|
|
911
932
|
* Finds the nearest timezone for a given coordinate point.
|
|
912
933
|
*/
|
|
913
|
-
public static function getNearestTimezone(
|
|
934
|
+
public static function getNearestTimezone(Point $location, string $countryCode = ''): string
|
|
914
935
|
{
|
|
915
936
|
$timezoneIds = \DateTimeZone::listIdentifiers(\DateTimeZone::PER_COUNTRY, $countryCode);
|
|
916
937
|
$lat = $location->getLat();
|
|
@@ -1010,7 +1031,7 @@ class Utils extends FleetbaseUtils
|
|
|
1010
1031
|
|
|
1011
1032
|
public static function isPoint($point): bool
|
|
1012
1033
|
{
|
|
1013
|
-
return $point instanceof
|
|
1034
|
+
return $point instanceof Point;
|
|
1014
1035
|
}
|
|
1015
1036
|
|
|
1016
1037
|
/**
|
|
@@ -1275,7 +1296,7 @@ class Utils extends FleetbaseUtils
|
|
|
1275
1296
|
*
|
|
1276
1297
|
* @return float the heading in degrees from the starting point to the ending point
|
|
1277
1298
|
*/
|
|
1278
|
-
public static function calculateHeading(
|
|
1299
|
+
public static function calculateHeading(Point $point1, Point $point2): float
|
|
1279
1300
|
{
|
|
1280
1301
|
// Extract latitude and longitude from Point objects
|
|
1281
1302
|
$lat1 = $point1->getLat();
|
package/server/src/routes.php
CHANGED
|
@@ -12,9 +12,6 @@ use Illuminate\Support\Facades\Route;
|
|
|
12
12
|
| is assigned the "api" middleware group. Enjoy building your API!
|
|
13
13
|
|
|
|
14
14
|
*/
|
|
15
|
-
|
|
16
|
-
Route::get('fleet-ops-test', 'Fleetbase\FleetOps\Http\Controllers\Controller@test');
|
|
17
|
-
|
|
18
15
|
Route::prefix(config('fleetops.api.routing.prefix', null))->namespace('Fleetbase\FleetOps\Http\Controllers')->group(
|
|
19
16
|
function ($router) {
|
|
20
17
|
/*
|