routific 1.1.1 → 1.7.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![Build Status](https://
|
4
|
+
[![Build Status](https://travis-ci.org/routific/routific-gem.svg?branch=master)](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
|