routific 1.1.1 → 1.7.4
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.
- checksums.yaml +4 -4
- data/README.md +135 -133
- data/lib/routific.rb +31 -44
- data/lib/routific/break.rb +59 -0
- data/lib/routific/job.rb +25 -0
- data/lib/routific/options.rb +10 -14
- data/lib/routific/route.rb +43 -26
- data/lib/routific/vehicle.rb +26 -9
- data/lib/routific/visit.rb +48 -9
- data/lib/routific/way_point.rb +12 -5
- data/lib/util.rb +41 -0
- metadata +17 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 797096fa2ec79542c96e1e04ecc4063877543b0a
|
|
4
|
+
data.tar.gz: 59a5dee35ac98053066d501915d56f5f73d5c00d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 93d962ad00b5d846a28e7a8af20fb64ce314d5476318399fd06c023f155265fc675bfbeacc47b82a85f96e5460229f87c2ad4486714e9b75f437e55cafad331a
|
|
7
|
+
data.tar.gz: 30b2e995c76dfd06fe2dd2f84c7ddf56e88d81b65d8e981c73d0fda7408726cc6a9edce418295da00cf1860762a5fcc264fbede97570203cd71d6dae082fd5fc
|
data/README.md
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
Routific Ruby Gem
|
|
2
2
|
=================
|
|
3
3
|
|
|
4
|
-
[](https://travis-ci.org/routific/routific-gem)
|
|
5
5
|
|
|
6
|
-
This Ruby Gem assists users to easily access the [Routific API][1], which is a practical and scalable solution to the Vehicle Routing Problem.
|
|
6
|
+
This Ruby Gem assists users to easily access the [Routific API][1], which is a practical and scalable solution to the Vehicle Routing Problem and Traveling Salesman Problem.
|
|
7
7
|
|
|
8
8
|
[1]: https://routific.com/developers
|
|
9
9
|
|
|
10
|
+
Please refer to the full [documentation](https://docs.routific.com) for a detailled documentation of the API.
|
|
11
|
+
|
|
10
12
|
Installing
|
|
11
13
|
----------
|
|
12
14
|
|
|
@@ -14,100 +16,14 @@ Installing
|
|
|
14
16
|
|
|
15
17
|
Usage
|
|
16
18
|
-----
|
|
17
|
-
Remember to require it and
|
|
18
|
-
|
|
19
|
-
```ruby
|
|
20
|
-
require 'routific'
|
|
21
|
-
routific = Routific.new(--API_KEY--)
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
### Instance methods
|
|
25
|
-
|
|
26
|
-
`routific.setVisit( id, [params] )`
|
|
27
|
-
|
|
28
|
-
Sets a visit for the specified location using the specified parameters
|
|
29
|
-
|
|
30
|
-
Required arguments in params:
|
|
31
|
-
|
|
32
|
-
- location: Object representing the location of the visit.
|
|
33
|
-
+ lat: Latitude of this location
|
|
34
|
-
+ lng: Longitude of this location
|
|
35
|
-
+ name: (optional) Name of the location
|
|
36
|
-
|
|
37
|
-
Optional arguments in params:
|
|
38
|
-
|
|
39
|
-
- start: the earliest time for this visit. Default value is 00:00, if not specified.
|
|
40
|
-
- end: the latest time for this visit. Default value is 23:59, if not specified.
|
|
41
|
-
- duration: the length of this visit in minutes
|
|
42
|
-
- demand: the capacity that this visit requires
|
|
43
|
-
|
|
44
|
-
`routific.setVehicle( id, params )`
|
|
45
|
-
|
|
46
|
-
Sets a vehicle with the specified ID and parameters
|
|
47
|
-
|
|
48
|
-
Required arguments in params:
|
|
49
|
-
|
|
50
|
-
- start_location: Object representing the start location for this vehicle.
|
|
51
|
-
+ lat: Latitude of this location
|
|
52
|
-
+ lng: Longitude of this location
|
|
53
|
-
+ name: (optional) Name of the location
|
|
54
|
-
|
|
55
|
-
Optional arguments in params:
|
|
56
|
-
|
|
57
|
-
- end_location: Object representing the end location for this vehicle.
|
|
58
|
-
+ lat: Latitude of this location
|
|
59
|
-
+ lng: Longitude of this location
|
|
60
|
-
+ name: (optional) Name of the location
|
|
61
|
-
|
|
62
|
-
- shift_start: this vehicle's start shift time (e.g. '08:00'). Default value is 00:00, if not specified.
|
|
63
|
-
- shift_end: this vehicle's end shift time (e.g. '17:00'). Default value is 23:59, if not specified.
|
|
64
|
-
- capacity: the capacity that this vehicle can load
|
|
65
|
-
|
|
66
|
-
`routific.setOptions( params )`
|
|
67
|
-
|
|
68
|
-
Sets optional options onto the route requests.
|
|
69
|
-
|
|
70
|
-
Optional arguments must be one of the following:
|
|
71
|
-
|
|
72
|
-
- traffic
|
|
73
|
-
- min_visits_per_vehicle
|
|
74
|
-
- balance
|
|
75
|
-
- min_vehicles
|
|
76
|
-
- shortest_distance
|
|
77
|
-
|
|
78
|
-
`routific.getRoute()`
|
|
79
|
-
|
|
80
|
-
Returns the route using the previously provided network, visits and fleet information
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
### Class methods
|
|
84
|
-
|
|
85
|
-
`Routific.setToken( token )`
|
|
86
|
-
|
|
87
|
-
Sets the default access token to use
|
|
88
|
-
|
|
89
|
-
`Routific.getRoute( id, [params] )`
|
|
90
|
-
|
|
91
|
-
Returns the route using the specified access token, network, visits and fleet information
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
Both getRoute functions return the Route object, which has the following attributes:
|
|
95
|
-
|
|
96
|
-
- status: A sanity check, will always be success when the HTTP code is 200
|
|
97
|
-
- fitness: Total travel-time, representing the fitness score of the solution (less is better)
|
|
98
|
-
- unserved: List of visits that could not be scheduled.
|
|
99
|
-
- vehicleRoutes: The optimized schedule
|
|
100
|
-
|
|
101
|
-
Examples
|
|
102
|
-
--------
|
|
103
|
-
Example 1:
|
|
19
|
+
Remember to require it and set your token before using it
|
|
104
20
|
|
|
105
21
|
```ruby
|
|
106
22
|
require 'routific'
|
|
23
|
+
Routific.set_token("YOUR_API_KEY_HERE")
|
|
24
|
+
routific = Routific.new
|
|
107
25
|
|
|
108
|
-
routific
|
|
109
|
-
|
|
110
|
-
routific.setVisit("order_1", {
|
|
26
|
+
routific.set_visit("order_1", {
|
|
111
27
|
"start" => "9:00",
|
|
112
28
|
"end" => "12:00",
|
|
113
29
|
"duration" => 10,
|
|
@@ -118,7 +34,18 @@ routific.setVisit("order_1", {
|
|
|
118
34
|
}
|
|
119
35
|
})
|
|
120
36
|
|
|
121
|
-
routific.
|
|
37
|
+
routific.set_visit("order_2", {
|
|
38
|
+
"start" => "9:00",
|
|
39
|
+
"end" => "12:00",
|
|
40
|
+
"duration" => 10,
|
|
41
|
+
"location" => {
|
|
42
|
+
"name"=> "3780 Arbutus",
|
|
43
|
+
"lat"=> 49.2474624,
|
|
44
|
+
"lng"=> -123.1532338
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
routific.set_vehicle("vehicle_1", {
|
|
122
49
|
"start_location" => {
|
|
123
50
|
"name" => "800 Kingsway",
|
|
124
51
|
"lat" => 49.2553636,
|
|
@@ -133,50 +60,125 @@ routific.setVehicle("vehicle_1", {
|
|
|
133
60
|
"shift_end" => "12:00",
|
|
134
61
|
})
|
|
135
62
|
|
|
136
|
-
|
|
63
|
+
routific.set_options({
|
|
64
|
+
"polylines" => true
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
route = routific.get_route()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
puts route.status # => "success"
|
|
71
|
+
puts route.total_travel_time # => 29
|
|
72
|
+
puts route.polylines["vehicle_1"] # => s`i}|AbxswiFnn@gdBvG?vVf@jC?nZRb[f@j\z@nZRnZRj\f@nZz
|
|
73
|
+
|
|
74
|
+
vehicle_routes = route.vehicle_routes
|
|
75
|
+
|
|
76
|
+
v1_route = vehicle_routes["vehicle_1"]
|
|
77
|
+
puts v1_route.length # => 4 (start -> order_1 -> order_2 -> end)
|
|
78
|
+
|
|
79
|
+
v1_route.each do |w|
|
|
80
|
+
puts "#{w.location_id}: #{w.arrival_time} ~ #{w.finish_time}"
|
|
81
|
+
end
|
|
82
|
+
# vehicle_1_start: 08:49 ~
|
|
83
|
+
# order_1: 09:00 ~ 09:10
|
|
84
|
+
# order_2: 09:18 ~ 09:28
|
|
85
|
+
# vehicle_1_end: 09:38 ~
|
|
86
|
+
|
|
137
87
|
```
|
|
138
88
|
|
|
139
|
-
|
|
89
|
+
### Class methods
|
|
90
|
+
|
|
91
|
+
`Routific.set_token( token )` sets the authentication token to use.
|
|
140
92
|
|
|
141
|
-
|
|
142
|
-
require 'routific'
|
|
93
|
+
### Instance methods
|
|
143
94
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
visits = {
|
|
147
|
-
"order_1" => {
|
|
148
|
-
"start" => "9:00",
|
|
149
|
-
"end" => "12:00",
|
|
150
|
-
"duration" => 10,
|
|
151
|
-
"location" => {
|
|
152
|
-
"name" => "6800 Cambie",
|
|
153
|
-
"lat" => 49.227107,
|
|
154
|
-
"lng" => -123.1163085
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
fleet = {
|
|
160
|
-
"vehicle_1" => {
|
|
161
|
-
"start_location" => {
|
|
162
|
-
"name" => "800 Kingsway",
|
|
163
|
-
"lat" => 49.2553636,
|
|
164
|
-
"lng" => -123.0873365
|
|
165
|
-
},
|
|
166
|
-
"end_location" => {
|
|
167
|
-
"name" => "800 Kingsway",
|
|
168
|
-
"lat" => 49.2553636,
|
|
169
|
-
"lng" => -123.0873365
|
|
170
|
-
},
|
|
171
|
-
"shift_start" => "8:00",
|
|
172
|
-
"shift_end" => "12:00"
|
|
173
|
-
}
|
|
174
|
-
}
|
|
95
|
+
#### `routific.set_visit( id, params )`
|
|
175
96
|
|
|
176
|
-
|
|
177
|
-
visits: visits,
|
|
178
|
-
fleet: fleet
|
|
179
|
-
}
|
|
97
|
+
Sets a visit with the specified id and parameters:
|
|
180
98
|
|
|
181
|
-
|
|
99
|
+
- `location` (*required*): Object representing the location of the visit.
|
|
100
|
+
+ lat: Latitude of this location
|
|
101
|
+
+ lng: Longitude of this location
|
|
102
|
+
+ name: (optional) Name of the location
|
|
103
|
+
- `start`: the earliest time for this visit. Default value is 00:00, if not specified.
|
|
104
|
+
- `end`: the latest time for this visit. Default value is 23:59, if not specified.
|
|
105
|
+
- `duration`: the length of this visit in minutes
|
|
106
|
+
- `demand`: the capacity that this visit requires
|
|
107
|
+
- `priority`: higher priority visits are more likely to be served
|
|
108
|
+
- `type`: restrict the vehicle that can serve this visit
|
|
109
|
+
- `time_windows`: specify different time-windows for serving the visit.
|
|
110
|
+
It should be an array of hashes: `[ { "start" => "08:00", "end" => "12:00" } ]`
|
|
111
|
+
|
|
112
|
+
#### `routific.set_vehicle( id, params )`
|
|
113
|
+
|
|
114
|
+
Sets a vehicle with the specified ID and parameters:
|
|
115
|
+
- `start_location` (*required*): Object representing the start location for this vehicle.
|
|
116
|
+
+ lat: Latitude of this location
|
|
117
|
+
+ lng: Longitude of this location
|
|
118
|
+
+ name: (optional) Name of the location
|
|
119
|
+
- `end_location`: Object representing the end location for this vehicle.
|
|
120
|
+
+ lat: Latitude of this location
|
|
121
|
+
+ lng: Longitude of this location
|
|
122
|
+
+ name: (optional) Name of the location
|
|
123
|
+
- `shift_start`: this vehicle's start shift time (e.g. '08:00'). Default value is 00:00, if not specified.
|
|
124
|
+
- `shift_end`: this vehicle's end shift time (e.g. '17:00'). Default value is 23:59, if not specified.
|
|
125
|
+
- `capacity`: the capacity that this vehicle can load
|
|
126
|
+
- `type`: restrict the visit this vehicle can serve
|
|
127
|
+
- `speed`: vehicle speed
|
|
128
|
+
- `min_visits`: minimum number of visits that should be assigned to this vehicle
|
|
129
|
+
- `strict_start`: force the departure time to be `shift_start`
|
|
130
|
+
- `breaks`: specify breaks for the driver.
|
|
131
|
+
It should be an array of hashes: `[ { "id" => "lunch", "start" => "12:00", "end" => "12:30" } ]`
|
|
132
|
+
|
|
133
|
+
#### `routific.set_options( params )`
|
|
134
|
+
|
|
135
|
+
Sets optional options onto the route requests.
|
|
136
|
+
Optional arguments must be one of the following:
|
|
137
|
+
|
|
138
|
+
- `traffic`
|
|
139
|
+
- `min_visits_per_vehicle`
|
|
140
|
+
- `balance`
|
|
141
|
+
- `min_vehicles`
|
|
142
|
+
- `shortest_distance`
|
|
143
|
+
- `squash_duration`
|
|
144
|
+
- `max_vehicle_overtime`
|
|
145
|
+
- `max_visit_lateness`
|
|
146
|
+
|
|
147
|
+
#### `routific.get_route()`
|
|
148
|
+
|
|
149
|
+
Returns an optimized route using the previously provided visits, fleet and options.
|
|
150
|
+
The request may timeout if the problem is too large.
|
|
151
|
+
|
|
152
|
+
It returns a route object with the following attributes:
|
|
153
|
+
- `status`: A sanity check
|
|
154
|
+
- `unserved`: List of visits that could not be scheduled.
|
|
155
|
+
- `vehicle_routes`: The optimized schedule
|
|
156
|
+
- other attributes that you can find in the [full documentation](https://docs.routific.com)
|
|
157
|
+
|
|
158
|
+
The `vehicle_routes` attribute is a hash mapping vehicle ID to the corresponding route, represented as an array of waypoints: `{ "vehicle_1" => [ way_point_1, way_point_2, way_point_3, way_point_4 ] }`
|
|
159
|
+
|
|
160
|
+
The waypoint object has the following attributes:
|
|
161
|
+
- `location_id`
|
|
162
|
+
- `arrival_time`
|
|
163
|
+
- `finish_time`
|
|
164
|
+
- other attributes that you can find in the [full documentation](https://docs.routific.com)
|
|
165
|
+
|
|
166
|
+
#### `routific.get_route_async()`
|
|
167
|
+
|
|
168
|
+
For requests with 60 visits and more, it is recommended to make asynchronous call to the API.
|
|
169
|
+
|
|
170
|
+
```ruby
|
|
171
|
+
job = routific.get_route_async()
|
|
172
|
+
puts job.status # => "pending"
|
|
173
|
+
sleep 5
|
|
174
|
+
job.fetch
|
|
175
|
+
puts job.status # => "finished"
|
|
176
|
+
route = job.route
|
|
182
177
|
```
|
|
178
|
+
|
|
179
|
+
It returns a job object with the following attibutes:
|
|
180
|
+
- `status`: status of the task ('pending', 'processing', 'finished', 'error')
|
|
181
|
+
- `id`: a unique identifier for the task
|
|
182
|
+
- `created_at`, `finished_at`: creation and finish times
|
|
183
|
+
- `input`: the data used for the request
|
|
184
|
+
- `route`: a route object
|
data/lib/routific.rb
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
require 'rest-client'
|
|
2
|
-
require 'json'
|
|
3
|
-
|
|
4
1
|
require_relative './routific/location'
|
|
5
2
|
require_relative './routific/visit'
|
|
3
|
+
require_relative './routific/break'
|
|
6
4
|
require_relative './routific/vehicle'
|
|
7
5
|
require_relative './routific/route'
|
|
8
6
|
require_relative './routific/way_point'
|
|
9
7
|
require_relative './routific/options'
|
|
8
|
+
require_relative './routific/job'
|
|
9
|
+
|
|
10
|
+
require_relative './util'
|
|
10
11
|
|
|
11
12
|
# Main class of this gem
|
|
12
13
|
class Routific
|
|
13
|
-
attr_reader :
|
|
14
|
+
attr_reader :visits, :fleet, :options
|
|
15
|
+
@@token = nil
|
|
14
16
|
|
|
15
17
|
# Constructor
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@token = token
|
|
18
|
+
def initialize()
|
|
19
|
+
Routific.validate_token
|
|
19
20
|
@visits = {}
|
|
20
21
|
@fleet = {}
|
|
21
22
|
@options = {}
|
|
@@ -24,75 +25,61 @@ class Routific
|
|
|
24
25
|
# Sets a visit for the specified location using the specified parameters
|
|
25
26
|
# id: ID of location to visit
|
|
26
27
|
# params: parameters for this visit
|
|
27
|
-
def
|
|
28
|
+
def set_visit(id, params={})
|
|
28
29
|
visits[id] = RoutificApi::Visit.new(id, params)
|
|
29
30
|
end
|
|
30
31
|
|
|
31
32
|
# Sets a vehicle with the specified ID and parameters
|
|
32
33
|
# id: vehicle ID
|
|
33
34
|
# params: parameters for this vehicle
|
|
34
|
-
def
|
|
35
|
+
def set_vehicle(id, params)
|
|
35
36
|
fleet[id] = RoutificApi::Vehicle.new(id, params)
|
|
36
37
|
end
|
|
37
38
|
|
|
38
39
|
# Sets options with the specified params
|
|
39
40
|
# params: parameters for these options
|
|
40
|
-
def
|
|
41
|
+
def set_options(params)
|
|
41
42
|
@options = RoutificApi::Options.new(params)
|
|
42
43
|
end
|
|
43
44
|
|
|
44
45
|
# Returns the route using the previously provided visits and fleet information
|
|
45
|
-
def
|
|
46
|
+
def get_route
|
|
46
47
|
data = {
|
|
47
48
|
visits: visits,
|
|
48
49
|
fleet: fleet
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
data[:options] = options if options
|
|
52
|
-
|
|
53
|
+
result = Util.send_request("POST", "vrp", Routific.token, data)
|
|
54
|
+
RoutificApi::Route.parse(result)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def get_route_async
|
|
58
|
+
data = {
|
|
59
|
+
visits: visits,
|
|
60
|
+
fleet: fleet
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
data[:options] = options if options
|
|
64
|
+
result = Util.send_request("POST", "vrp-long", Routific.token, data)
|
|
65
|
+
RoutificApi::Job.new(result["job_id"], data)
|
|
53
66
|
end
|
|
54
67
|
|
|
55
68
|
class << self
|
|
56
69
|
# Sets the default access token to use
|
|
57
|
-
def
|
|
70
|
+
def set_token(token)
|
|
58
71
|
@@token = token
|
|
72
|
+
validate_token
|
|
73
|
+
@@token = Util.prefix_token(@@token)
|
|
59
74
|
end
|
|
60
75
|
|
|
61
76
|
def token
|
|
62
77
|
@@token
|
|
63
78
|
end
|
|
64
79
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
# If the default access token either is nil or has not been set, an ArgumentError is raised
|
|
68
|
-
def getRoute(data, token = @@token)
|
|
69
|
-
if token.nil?
|
|
70
|
-
raise ArgumentError, "access token must be set"
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# Prefix the token with "bearer " if missing during assignment
|
|
74
|
-
prefixed_token = (/bearer /.match(token).nil?) ? "bearer #{token}" : token
|
|
75
|
-
|
|
76
|
-
begin
|
|
77
|
-
# Sends HTTP request to Routific API server
|
|
78
|
-
response = RestClient.post('https://api.routific.com/v1/vrp',
|
|
79
|
-
data.to_json,
|
|
80
|
-
'Authorization' => prefixed_token,
|
|
81
|
-
content_type: :json,
|
|
82
|
-
accept: :json
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
# Parse the HTTP request response to JSON
|
|
86
|
-
jsonResponse = JSON.parse(response)
|
|
87
|
-
|
|
88
|
-
# Parse the JSON representation into a RoutificApi::Route object
|
|
89
|
-
RoutificApi::Route.parse(jsonResponse)
|
|
90
|
-
rescue => e
|
|
91
|
-
puts e
|
|
92
|
-
errorResponse = JSON.parse e.response.body
|
|
93
|
-
puts "Received HTTP #{e.message}: #{errorResponse["error"]}"
|
|
94
|
-
nil
|
|
95
|
-
end
|
|
80
|
+
def validate_token
|
|
81
|
+
raise ArgumentError, "access token must be set" if @@token.nil?
|
|
96
82
|
end
|
|
83
|
+
|
|
97
84
|
end
|
|
98
85
|
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module RoutificApi
|
|
2
|
+
# This class represent a vehicle break
|
|
3
|
+
class Break
|
|
4
|
+
REQUIRED_PARAMS = %w{ id start end }
|
|
5
|
+
|
|
6
|
+
attr_reader *REQUIRED_PARAMS
|
|
7
|
+
attr_reader :in_transit
|
|
8
|
+
|
|
9
|
+
# Constructor
|
|
10
|
+
#
|
|
11
|
+
# Required arguments in params:
|
|
12
|
+
# id: unique identifier for the break (e.g. 'lunch-break')
|
|
13
|
+
# start: start time of the break (e.g. '12:00')
|
|
14
|
+
# end: end time of the break (e.g. '12:30')
|
|
15
|
+
#
|
|
16
|
+
# Optional argument in params:
|
|
17
|
+
# in_transit: whether the vehicle can be in movement during the break (e.g. true). The default value is false.
|
|
18
|
+
def initialize(params)
|
|
19
|
+
validate(params)
|
|
20
|
+
|
|
21
|
+
@id = params["id"]
|
|
22
|
+
@start = params["start"]
|
|
23
|
+
@end = params["end"]
|
|
24
|
+
@in_transit = params["in_transit"] unless params["in_transit"].nil?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def ==(another_break)
|
|
28
|
+
self.id == another_break.id &&
|
|
29
|
+
self.start == another_break.start &&
|
|
30
|
+
self.end == another_break.end &&
|
|
31
|
+
self.in_transit == another_break.in_transit
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def to_json(options = nil)
|
|
35
|
+
as_json(options).to_json
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def as_json(options = nil)
|
|
39
|
+
jsonData = {}
|
|
40
|
+
jsonData["id"] = self.id
|
|
41
|
+
jsonData["start"] = self.start
|
|
42
|
+
jsonData["end"] = self.end
|
|
43
|
+
jsonData["in_transit"] = self.in_transit unless self.in_transit.nil?
|
|
44
|
+
|
|
45
|
+
jsonData
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
# Validates the parameters being provided
|
|
50
|
+
# Raises an ArgumentError if any of the required parameters is not provided.
|
|
51
|
+
def validate(params)
|
|
52
|
+
REQUIRED_PARAMS.each do |param|
|
|
53
|
+
if params[param].nil?
|
|
54
|
+
raise ArgumentError, "'#{param}' parameter must be provided in break"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
data/lib/routific/job.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module RoutificApi
|
|
2
|
+
# This class represents a job returned by vrp-long
|
|
3
|
+
class Job
|
|
4
|
+
FIELDS = [:status, :created_at, :finished_at]
|
|
5
|
+
attr_reader *FIELDS
|
|
6
|
+
attr_reader :input, :id, :route
|
|
7
|
+
|
|
8
|
+
# Constructor
|
|
9
|
+
def initialize(id, input)
|
|
10
|
+
@id = id
|
|
11
|
+
@input = input
|
|
12
|
+
@status = 'pending'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def fetch
|
|
16
|
+
job_data = Util.send_request("GET", "jobs/#{@id}")
|
|
17
|
+
|
|
18
|
+
FIELDS.each do |field|
|
|
19
|
+
instance_variable_set "@#{field}", job_data[field.to_s]
|
|
20
|
+
end
|
|
21
|
+
@route = RoutificApi::Route.parse(job_data["output"]) if job_data["output"]
|
|
22
|
+
job_data['status']
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/routific/options.rb
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
module RoutificApi
|
|
2
2
|
# This class represents a set of options for the request
|
|
3
3
|
class Options
|
|
4
|
-
VALID_PARAMS = %w{ traffic min_visits_per_vehicle balance min_vehicles shortest_distance }
|
|
4
|
+
VALID_PARAMS = %w{ traffic min_visits_per_vehicle balance min_vehicles shortest_distance squash_durations max_vehicle_overtime max_visit_lateness polylines }
|
|
5
5
|
|
|
6
6
|
attr_reader *VALID_PARAMS
|
|
7
7
|
|
|
8
8
|
def initialize(params)
|
|
9
9
|
# Validates the provided parameters
|
|
10
10
|
validate(params)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@min_vehicles = params["min_vehicles"]
|
|
16
|
-
@shortest_distance = params["shortest_distance"]
|
|
11
|
+
|
|
12
|
+
VALID_PARAMS.each do |param|
|
|
13
|
+
instance_variable_set "@#{param}", params[param]
|
|
14
|
+
end
|
|
17
15
|
end
|
|
18
16
|
|
|
19
17
|
def to_json(options)
|
|
@@ -24,19 +22,17 @@ module RoutificApi
|
|
|
24
22
|
# def to_json(options = nil)
|
|
25
23
|
def as_json(options = nil)
|
|
26
24
|
jsonData = {}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
|
|
26
|
+
VALID_PARAMS.each do |param|
|
|
27
|
+
instance_var = instance_variable_get "@#{param}"
|
|
28
|
+
jsonData[param] = instance_var if instance_var
|
|
29
|
+
end
|
|
33
30
|
jsonData
|
|
34
31
|
end
|
|
35
32
|
|
|
36
33
|
private
|
|
37
34
|
# Validates the parameters being provided
|
|
38
35
|
# Raises an ArgumentError if any of the provided params is not supported
|
|
39
|
-
# Supported params are traffic, min_visits_per_vehicle, and balance
|
|
40
36
|
def validate(params)
|
|
41
37
|
invalid = params.keys.reject { |k| VALID_PARAMS.include?(k) }
|
|
42
38
|
|
data/lib/routific/route.rb
CHANGED
|
@@ -1,46 +1,63 @@
|
|
|
1
1
|
module RoutificApi
|
|
2
2
|
# This class represents the resulting route returned by the Routific API
|
|
3
3
|
class Route
|
|
4
|
-
|
|
4
|
+
FIELDS = [
|
|
5
|
+
:status,
|
|
6
|
+
:unserved, :num_unserved,
|
|
7
|
+
:distances, :total_distance,
|
|
8
|
+
:total_working_time, :total_travel_time,
|
|
9
|
+
:total_break_time, :total_idle_time,
|
|
10
|
+
:total_visit_lateness, :num_late_visits,
|
|
11
|
+
:vehicle_overtime, :total_vehicle_overtime,
|
|
12
|
+
:polylines,
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
attr_reader *FIELDS
|
|
16
|
+
attr_reader :vehicle_routes
|
|
5
17
|
|
|
6
18
|
# Constructor
|
|
7
|
-
def initialize(
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
19
|
+
def initialize(data)
|
|
20
|
+
FIELDS.each do |field|
|
|
21
|
+
instance_variable_set "@#{field}", data[field]
|
|
22
|
+
end
|
|
23
|
+
add_solution(data[:solution] || {})
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def add_solution(solution)
|
|
27
|
+
@vehicle_routes = {}
|
|
28
|
+
|
|
29
|
+
solution.each do |vehicle_name, way_points|
|
|
30
|
+
# Get all way points for this vehicle
|
|
31
|
+
way_points.each do |waypoint_info|
|
|
32
|
+
# Get all information for this way point
|
|
33
|
+
way_point = RoutificApi::WayPoint.new(waypoint_info)
|
|
34
|
+
add_way_point(vehicle_name, way_point)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
12
37
|
end
|
|
13
38
|
|
|
14
39
|
# Adds a new way point for the specified vehicle
|
|
15
|
-
def
|
|
16
|
-
if @
|
|
40
|
+
def add_way_point(vehicle_name, way_point)
|
|
41
|
+
if @vehicle_routes[vehicle_name].nil?
|
|
17
42
|
# No previous way point was added for the specified vehicle, so create a new array
|
|
18
|
-
@
|
|
43
|
+
@vehicle_routes[vehicle_name] = []
|
|
19
44
|
end
|
|
20
45
|
# Adds the provided way point for the specified vehicle
|
|
21
|
-
@
|
|
46
|
+
@vehicle_routes[vehicle_name] << way_point
|
|
22
47
|
end
|
|
23
48
|
|
|
24
49
|
class << self
|
|
25
50
|
# Parse the JSON representation of a route, and return it as a Route object
|
|
26
|
-
def parse(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
route = RoutificApi::Route.new(status, fitness, unserved)
|
|
51
|
+
def parse(data)
|
|
52
|
+
data = process_data(data)
|
|
53
|
+
RoutificApi::Route.new(data)
|
|
54
|
+
end
|
|
31
55
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
way_points.each do |waypoint_info|
|
|
36
|
-
# Get all information for this way point
|
|
37
|
-
way_point = RoutificApi::WayPoint.new(waypoint_info)
|
|
38
|
-
route.addWayPoint(vehicle_name, way_point)
|
|
39
|
-
end
|
|
56
|
+
def process_data(hash)
|
|
57
|
+
hash.keys.each do |key|
|
|
58
|
+
hash[(key.to_sym rescue key) || key] = hash.delete(key)
|
|
40
59
|
end
|
|
41
|
-
|
|
42
|
-
# Return the resulting Route object
|
|
43
|
-
route
|
|
60
|
+
hash
|
|
44
61
|
end
|
|
45
62
|
end
|
|
46
63
|
end
|
data/lib/routific/vehicle.rb
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
require_relative './break'
|
|
2
|
+
|
|
1
3
|
module RoutificApi
|
|
2
4
|
# This class represents a vehicle in the fleet
|
|
3
5
|
class Vehicle
|
|
4
|
-
attr_accessor :id, :start_location, :end_location, :shift_start,
|
|
6
|
+
attr_accessor :id, :start_location, :end_location, :shift_start,
|
|
7
|
+
:shift_end, :capacity, :strict_start, :min_visits, :speed, :breaks, :type
|
|
5
8
|
|
|
6
9
|
# Constructor
|
|
7
10
|
#
|
|
@@ -24,6 +27,13 @@ module RoutificApi
|
|
|
24
27
|
@shift_start = params["shift_start"]
|
|
25
28
|
@shift_end = params["shift_end"]
|
|
26
29
|
@capacity = params["capacity"]
|
|
30
|
+
@strict_start = params["strict_start"]
|
|
31
|
+
@min_visits = params["min_visits"]
|
|
32
|
+
@speed = params["speed"]
|
|
33
|
+
@type = params["type"]
|
|
34
|
+
if params["breaks"]
|
|
35
|
+
@breaks = params["breaks"].map{ |brk| RoutificApi::Break.new(brk) }
|
|
36
|
+
end
|
|
27
37
|
end
|
|
28
38
|
|
|
29
39
|
def to_json(options=nil)
|
|
@@ -33,14 +43,21 @@ module RoutificApi
|
|
|
33
43
|
# Returns the JSON representation of this object
|
|
34
44
|
# def to_json(options = nil)
|
|
35
45
|
def as_json(options = nil)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
json_data = {}
|
|
47
|
+
json_data["start_location"] = self.start_location.as_json
|
|
48
|
+
json_data["end_location"] = self.end_location.as_json if self.end_location
|
|
49
|
+
json_data["shift_start"] = self.shift_start if self.shift_start
|
|
50
|
+
json_data["shift_end"] = self.shift_end if self.shift_end
|
|
51
|
+
json_data["capacity"] = self.capacity if self.capacity
|
|
52
|
+
json_data["strict_start"] = self.strict_start if not self.strict_start.nil?
|
|
53
|
+
json_data["min_visits"] = self.min_visits if self.min_visits
|
|
54
|
+
json_data["speed"] = self.speed if self.speed
|
|
55
|
+
json_data["type"] = self.type if self.type
|
|
56
|
+
if self.breaks
|
|
57
|
+
json_data["breaks"] = self.breaks.map{ |brk| brk.as_json }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
json_data
|
|
44
61
|
end
|
|
45
62
|
|
|
46
63
|
|
data/lib/routific/visit.rb
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
module RoutificApi
|
|
2
2
|
# This class represents a location to be visited
|
|
3
3
|
class Visit
|
|
4
|
-
attr_reader :id, :start, :end, :duration, :demand, :location
|
|
4
|
+
attr_reader :id, :start, :end, :duration, :demand, :location, :priority,
|
|
5
|
+
:time_windows, :type
|
|
5
6
|
|
|
6
7
|
# Constructor
|
|
7
8
|
#
|
|
@@ -19,6 +20,11 @@ module RoutificApi
|
|
|
19
20
|
@duration = params["duration"]
|
|
20
21
|
@demand = params["demand"]
|
|
21
22
|
@location = RoutificApi::Location.new(params["location"])
|
|
23
|
+
@priority = params["priority"]
|
|
24
|
+
@type = params["type"]
|
|
25
|
+
if params["time_windows"]
|
|
26
|
+
@time_windows = params["time_windows"].map{ |tw| TimeWindow.new(tw) }
|
|
27
|
+
end
|
|
22
28
|
end
|
|
23
29
|
|
|
24
30
|
def to_json(options)
|
|
@@ -28,14 +34,19 @@ module RoutificApi
|
|
|
28
34
|
# Returns the JSON representation of this object
|
|
29
35
|
# def to_json(options = nil)
|
|
30
36
|
def as_json(options = nil)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
json_data = {}
|
|
38
|
+
json_data["start"] = self.start if self.start
|
|
39
|
+
json_data["end"] = self.end if self.end
|
|
40
|
+
json_data["duration"] = self.duration if self.duration
|
|
41
|
+
json_data["demand"] = self.demand if self.demand
|
|
42
|
+
json_data["location"] = self.location.as_json
|
|
43
|
+
json_data["priority"] = self.priority if self.priority
|
|
44
|
+
json_data["type"] = self.type if self.type
|
|
45
|
+
if self.time_windows
|
|
46
|
+
json_data["time_windows"] = self.time_windows.map{ |tw| tw.as_json }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
json_data
|
|
39
50
|
end
|
|
40
51
|
|
|
41
52
|
private
|
|
@@ -47,5 +58,33 @@ module RoutificApi
|
|
|
47
58
|
raise ArgumentError, "'location' parameter must be provided"
|
|
48
59
|
end
|
|
49
60
|
end
|
|
61
|
+
|
|
62
|
+
public
|
|
63
|
+
class TimeWindow
|
|
64
|
+
attr_reader :start, :end
|
|
65
|
+
|
|
66
|
+
# Constructor
|
|
67
|
+
#
|
|
68
|
+
# Optional arguments
|
|
69
|
+
# start: start of the time-window
|
|
70
|
+
# end: end of the time-window
|
|
71
|
+
def initialize(params)
|
|
72
|
+
@start = params["start"]
|
|
73
|
+
@end = params["end"]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def ==(another_tw)
|
|
77
|
+
self.start == another_tw.start &&
|
|
78
|
+
self.end == another_tw.end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def as_json
|
|
82
|
+
json_data = {}
|
|
83
|
+
json_data["start"] = self.start if self.start
|
|
84
|
+
json_data["end"] = self.end if self.end
|
|
85
|
+
|
|
86
|
+
json_data
|
|
87
|
+
end
|
|
88
|
+
end
|
|
50
89
|
end
|
|
51
90
|
end
|
data/lib/routific/way_point.rb
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
module RoutificApi
|
|
2
2
|
# This class represents a location to visit in the route
|
|
3
3
|
class WayPoint
|
|
4
|
-
|
|
4
|
+
FIELDS = [
|
|
5
|
+
:location_id, :location_name,
|
|
6
|
+
:arrival_time, :finish_time,
|
|
7
|
+
:idle_time,
|
|
8
|
+
:id, :break, :start, :end, :in_transit, # breaks
|
|
9
|
+
:too_late, :late_by # visit lateness
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
attr_reader *FIELDS
|
|
5
13
|
|
|
6
14
|
# Constructor
|
|
7
15
|
def initialize(options = {})
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@location_name = options['location_name']
|
|
16
|
+
FIELDS.each do |field|
|
|
17
|
+
instance_variable_set "@#{field}", options[field.to_s]
|
|
18
|
+
end
|
|
12
19
|
end
|
|
13
20
|
end
|
|
14
21
|
end
|
data/lib/util.rb
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require 'rest-client'
|
|
2
|
+
require 'json'
|
|
3
|
+
|
|
4
|
+
module Util
|
|
5
|
+
BASE_URL = 'https://api.routific.com/v1/'
|
|
6
|
+
|
|
7
|
+
##
|
|
8
|
+
# method: "GET", "POST"
|
|
9
|
+
# endpoint: "vrp", "vrp-long", "job"
|
|
10
|
+
# token: if nil, raise ArgumentError; if missing "bearer", prefix
|
|
11
|
+
# data: only for POST requests
|
|
12
|
+
#
|
|
13
|
+
def self.send_request(method, endpoint, token = nil, data = nil)
|
|
14
|
+
url = BASE_URL + endpoint
|
|
15
|
+
headers = {
|
|
16
|
+
content_type: :json,
|
|
17
|
+
accept: :json
|
|
18
|
+
}
|
|
19
|
+
headers['Authorization'] = token if token
|
|
20
|
+
begin
|
|
21
|
+
# Sends HTTP request to Routific API server
|
|
22
|
+
response = nil
|
|
23
|
+
if method == 'GET'
|
|
24
|
+
response = RestClient.get(url, headers)
|
|
25
|
+
elsif method == 'POST'
|
|
26
|
+
response = RestClient.post(url, data.to_json, headers)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
return JSON.parse(response)
|
|
30
|
+
rescue => e
|
|
31
|
+
puts e
|
|
32
|
+
errorResponse = JSON.parse e.response.body
|
|
33
|
+
puts "Received HTTP #{e.message}: #{errorResponse["error"]}"
|
|
34
|
+
nil
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.prefix_token(token)
|
|
39
|
+
(/bearer /.match(token).nil?) ? "bearer #{token}" : token
|
|
40
|
+
end
|
|
41
|
+
end
|
metadata
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: routific
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.7.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
-
|
|
8
|
-
- Andre Soesilo
|
|
7
|
+
- Routific
|
|
9
8
|
autorequire:
|
|
10
9
|
bindir: bin
|
|
11
10
|
cert_chain: []
|
|
12
|
-
date:
|
|
11
|
+
date: 2019-11-26 00:00:00.000000000 Z
|
|
13
12
|
dependencies:
|
|
14
13
|
- !ruby/object:Gem::Dependency
|
|
15
14
|
name: rest-client
|
|
@@ -17,28 +16,28 @@ dependencies:
|
|
|
17
16
|
requirements:
|
|
18
17
|
- - "~>"
|
|
19
18
|
- !ruby/object:Gem::Version
|
|
20
|
-
version:
|
|
19
|
+
version: 2.0.1
|
|
21
20
|
type: :runtime
|
|
22
21
|
prerelease: false
|
|
23
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
24
23
|
requirements:
|
|
25
24
|
- - "~>"
|
|
26
25
|
- !ruby/object:Gem::Version
|
|
27
|
-
version:
|
|
26
|
+
version: 2.0.1
|
|
28
27
|
- !ruby/object:Gem::Dependency
|
|
29
28
|
name: json
|
|
30
29
|
requirement: !ruby/object:Gem::Requirement
|
|
31
30
|
requirements:
|
|
32
31
|
- - "~>"
|
|
33
32
|
- !ruby/object:Gem::Version
|
|
34
|
-
version:
|
|
33
|
+
version: 2.3.0
|
|
35
34
|
type: :runtime
|
|
36
35
|
prerelease: false
|
|
37
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
38
37
|
requirements:
|
|
39
38
|
- - "~>"
|
|
40
39
|
- !ruby/object:Gem::Version
|
|
41
|
-
version:
|
|
40
|
+
version: 2.3.0
|
|
42
41
|
- !ruby/object:Gem::Dependency
|
|
43
42
|
name: rspec
|
|
44
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -57,16 +56,16 @@ dependencies:
|
|
|
57
56
|
name: faker
|
|
58
57
|
requirement: !ruby/object:Gem::Requirement
|
|
59
58
|
requirements:
|
|
60
|
-
- - "
|
|
59
|
+
- - ">="
|
|
61
60
|
- !ruby/object:Gem::Version
|
|
62
|
-
version:
|
|
61
|
+
version: 1.6.2
|
|
63
62
|
type: :development
|
|
64
63
|
prerelease: false
|
|
65
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
66
65
|
requirements:
|
|
67
|
-
- - "
|
|
66
|
+
- - ">="
|
|
68
67
|
- !ruby/object:Gem::Version
|
|
69
|
-
version:
|
|
68
|
+
version: 1.6.2
|
|
70
69
|
- !ruby/object:Gem::Dependency
|
|
71
70
|
name: dotenv
|
|
72
71
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -82,24 +81,27 @@ dependencies:
|
|
|
82
81
|
- !ruby/object:Gem::Version
|
|
83
82
|
version: '0.11'
|
|
84
83
|
description: Gem to use Routific API
|
|
85
|
-
email:
|
|
84
|
+
email: support@routific.com
|
|
86
85
|
executables: []
|
|
87
86
|
extensions: []
|
|
88
87
|
extra_rdoc_files: []
|
|
89
88
|
files:
|
|
90
89
|
- README.md
|
|
91
90
|
- lib/routific.rb
|
|
91
|
+
- lib/routific/break.rb
|
|
92
|
+
- lib/routific/job.rb
|
|
92
93
|
- lib/routific/location.rb
|
|
93
94
|
- lib/routific/options.rb
|
|
94
95
|
- lib/routific/route.rb
|
|
95
96
|
- lib/routific/vehicle.rb
|
|
96
97
|
- lib/routific/visit.rb
|
|
97
98
|
- lib/routific/way_point.rb
|
|
99
|
+
- lib/util.rb
|
|
98
100
|
homepage: https://routific.com/
|
|
99
101
|
licenses:
|
|
100
102
|
- MIT
|
|
101
103
|
metadata:
|
|
102
|
-
source_code: https://github.com/
|
|
104
|
+
source_code: https://github.com/routific/routific-gem
|
|
103
105
|
post_install_message:
|
|
104
106
|
rdoc_options: []
|
|
105
107
|
require_paths:
|
|
@@ -116,7 +118,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
116
118
|
version: '0'
|
|
117
119
|
requirements: []
|
|
118
120
|
rubyforge_project:
|
|
119
|
-
rubygems_version: 2.2.
|
|
121
|
+
rubygems_version: 2.5.2.3
|
|
120
122
|
signing_key:
|
|
121
123
|
specification_version: 4
|
|
122
124
|
summary: routific API
|