arcgis_vrps 0.0.2.1 → 0.0.2.2
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 +18 -0
- data/lib/arcgis_vrps.rb +546 -7
- data/lib/arcgis_vrps/auth.rb +29 -0
- data/lib/arcgis_vrps/depots.rb +57 -0
- data/lib/arcgis_vrps/orders.rb +85 -0
- data/lib/arcgis_vrps/routes.rb +56 -0
- data/lib/test/data_load.rb +116 -0
- data/lib/test/sample_data/postcodes +129 -0
- metadata +37 -17
- data/lib/arcgis_vrps/sample.rb +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d3af625d3074f6f84386182d7b120aa4a57c0d99
|
4
|
+
data.tar.gz: 2f2d8fd5876c98473a09c5ba532d4b3c81be802c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2c67e0d567464c17c154c44ec418ad0ff9fcf3ad9021ec9f8defd751005faaee6aed6a72fe3da0ef0ea927e284d5f17d27fb03a79e74fd54d628e3b70964d311
|
7
|
+
data.tar.gz: 66e43e5f763b7b51399bb11c17e69b0486ad63be53d55e6e011072fc33527cb84e3057a123ae0aad2d0b6db37ae48c0e643999c057ee37205c983c1e7f15ecf8
|
data/README.md
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# Overview #
|
2
|
+
A ruby gem as an interface for ArcGIS (platform of Esri) Vehicle Routing Problem service REST API
|
3
|
+
|
4
|
+
You can install this gem by:
|
5
|
+
|
6
|
+
gem install arcgis_vrps
|
7
|
+
|
8
|
+
If at any moment you get the error similar to:
|
9
|
+
|
10
|
+
SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed
|
11
|
+
|
12
|
+
Please follow the steps outlined [here](https://gist.github.com/fnichol/867550)
|
13
|
+
|
14
|
+
|
15
|
+
To debug / test the gem do the following on the command line:
|
16
|
+
|
17
|
+
- irb -Ilib -rarcgis_vrps
|
18
|
+
- a=Arcgis_Vrps.new
|
data/lib/arcgis_vrps.rb
CHANGED
@@ -1,10 +1,549 @@
|
|
1
|
+
# This require has to be below cause the above is using it.
|
2
|
+
# Else, you'll get error: Uninitialized constant
|
3
|
+
require 'json'
|
4
|
+
require 'net/http'
|
5
|
+
require 'arcgis_vrps/orders'
|
6
|
+
require 'arcgis_vrps/depots'
|
7
|
+
require 'arcgis_vrps/routes'
|
8
|
+
require 'arcgis_vrps/auth'
|
9
|
+
require 'test/data_load'
|
10
|
+
|
1
11
|
class Arcgis_Vrps
|
2
|
-
|
3
|
-
|
4
|
-
|
12
|
+
# Use for testing for now
|
13
|
+
# def initialize()
|
14
|
+
# auth = Auth.new("rdvAIjUCEMegkoId", "a22acf9678c24b34960c320dddfe96cc")
|
15
|
+
# @token = auth.generateToken
|
16
|
+
|
17
|
+
# puts
|
18
|
+
# puts "Token generated #{@token}"
|
19
|
+
# puts
|
20
|
+
|
21
|
+
# loadSampleData(false) # This will get coordinates from MapSynq for all McD and write to /lib/test/sample_data/coordinateArray file. Change to true if the file don't exist
|
22
|
+
# # mockCase1() # Routes without any start & end time (Basic routing)
|
23
|
+
# mockCase2(5) # 9 locations with set times that vehicles must reach (no overlapping time)
|
24
|
+
# # mockCase3() # 8 locations with set times that vehicles must reach (with overlapping time)
|
25
|
+
# # mockCase4() # Same as use case 3 but with more overlapping times than routes available
|
26
|
+
|
27
|
+
# # getOutputRoutes("jf163f4d4f7874fd69b4f6f90e2d5d9aa")
|
28
|
+
# # getOutputOrders("jf163f4d4f7874fd69b4f6f90e2d5d9aa")
|
29
|
+
# end
|
30
|
+
|
31
|
+
# def initialize(client_id, client_secret)
|
32
|
+
auth = Auth.new(client_id, client_secret)
|
33
|
+
@token = auth.generateToken
|
34
|
+
|
35
|
+
# puts
|
36
|
+
# puts "Token generated #{@token}"
|
37
|
+
# puts
|
38
|
+
|
5
39
|
end
|
6
|
-
end
|
7
40
|
|
8
|
-
#
|
9
|
-
|
10
|
-
|
41
|
+
def submitJob(ordersObj, depotsObj, routesObj, timer) # TODO: Check to decide whether to use synchronous or async
|
42
|
+
|
43
|
+
puts "Submitting job"
|
44
|
+
|
45
|
+
jobStatusNoOfCalls = 0
|
46
|
+
|
47
|
+
t1 = Time.now
|
48
|
+
|
49
|
+
submitJobUrl = 'https://logistics.arcgis.com/arcgis/rest/services/World/VehicleRoutingProblem/GPServer/SolveVehicleRoutingProblem/submitJob'
|
50
|
+
|
51
|
+
params = {
|
52
|
+
orders: ordersObj.to_json,
|
53
|
+
depots: depotsObj.to_json,
|
54
|
+
routes: routesObj.to_json,
|
55
|
+
default_date: (Time.now.to_f * 1000).to_i,
|
56
|
+
# default_date: 1441933200000,
|
57
|
+
token: @token,
|
58
|
+
f: 'json',
|
59
|
+
time_units: 'Seconds',
|
60
|
+
distance_units: 'Meters'
|
61
|
+
}
|
62
|
+
|
63
|
+
res = Net::HTTP.post_form(URI(submitJobUrl), params)
|
64
|
+
|
65
|
+
@jobId = JSON.parse(res.body)["jobId"]
|
66
|
+
|
67
|
+
|
68
|
+
if @jobId.nil?
|
69
|
+
raise "Error! #{res.body}"
|
70
|
+
else
|
71
|
+
puts "Received job ID: #{@jobId}"
|
72
|
+
print "Getting job status..."
|
73
|
+
|
74
|
+
if timer.nil?
|
75
|
+
timer = 1 # Timer of 500ms to constantly check for updates
|
76
|
+
end
|
77
|
+
|
78
|
+
start_time = Time.now
|
79
|
+
end_time = start_time+timer
|
80
|
+
|
81
|
+
completed = false
|
82
|
+
|
83
|
+
begin
|
84
|
+
while completed == false
|
85
|
+
print "."
|
86
|
+
jobStatusNoOfCalls += 1
|
87
|
+
while Time.now < end_time
|
88
|
+
end
|
89
|
+
|
90
|
+
completed = getJobStatus()
|
91
|
+
end
|
92
|
+
print "Completed!\n"
|
93
|
+
puts
|
94
|
+
# puts completed.to_s
|
95
|
+
# puts
|
96
|
+
|
97
|
+
t2 = Time.now
|
98
|
+
|
99
|
+
time_jobStatusIsComplete = t2-t1
|
100
|
+
|
101
|
+
puts "Time taken for job to be completed: #{time_jobStatusIsComplete}"
|
102
|
+
puts
|
103
|
+
|
104
|
+
routesReceived = getOutputRoutes(nil).to_json
|
105
|
+
routesReceived = "Routes received: \n #{routesReceived}"
|
106
|
+
ordersReceived = getOutputOrders(nil).to_json
|
107
|
+
ordersReceived = "Orders received: \n #{ordersReceived}"
|
108
|
+
|
109
|
+
t3 = Time.now
|
110
|
+
|
111
|
+
time_fullJobComplete = t3-t1
|
112
|
+
|
113
|
+
extraContent = "Total time taken for getting routes and orders: #{time_fullJobComplete} \nNumber of calls to jobStatus: #{jobStatusNoOfCalls} \n\n"
|
114
|
+
|
115
|
+
puts extraContent
|
116
|
+
puts
|
117
|
+
|
118
|
+
return extraContent, routesReceived, ordersReceived
|
119
|
+
rescue Exception => e
|
120
|
+
puts
|
121
|
+
puts e
|
122
|
+
puts
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def getJobStatus
|
128
|
+
urlWithJobId = 'https://logistics.arcgis.com/arcgis/rest/services/World/VehicleRoutingProblem/GPServer/SolveVehicleRoutingProblem/jobs/'+@jobId
|
129
|
+
res = Net::HTTP.post_form URI(urlWithJobId),
|
130
|
+
token: @token,
|
131
|
+
returnMessages: true,
|
132
|
+
f: 'json'
|
133
|
+
|
134
|
+
status = JSON.parse(res.body)["jobStatus"]
|
135
|
+
# puts res.body
|
136
|
+
# puts
|
137
|
+
|
138
|
+
if status == "esriJobSucceeded"
|
139
|
+
return res.body
|
140
|
+
elsif status == "esriJobFailed"
|
141
|
+
raise "\nJob failed with the following message stack:\n #{res.body}"
|
142
|
+
else
|
143
|
+
return false
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def getOutputRoutes(jobId)
|
148
|
+
unless jobId.nil?
|
149
|
+
@jobId = jobId
|
150
|
+
end
|
151
|
+
|
152
|
+
urlWithJobId = 'https://logistics.arcgis.com/arcgis/rest/services/World/VehicleRoutingProblem/GPServer/SolveVehicleRoutingProblem/jobs/'+@jobId+'/results/out_routes'
|
153
|
+
|
154
|
+
res = Net::HTTP.post_form URI(urlWithJobId),
|
155
|
+
token: @token,
|
156
|
+
f: 'json'
|
157
|
+
|
158
|
+
# We return the entire string is because it have meta data about the geometry type, spatial reference, etc
|
159
|
+
routesFeatures = JSON.parse(res.body)
|
160
|
+
# JSON.parse(res.body)["value"]["features"]["geometry"]["paths"] will return an array of coordinates in the following format: [longitude, latitude]
|
161
|
+
# routesFeatures = JSON.parse(res.body)["value"]["features"]
|
162
|
+
|
163
|
+
if routesFeatures.nil?
|
164
|
+
routesFeatures = res.body
|
165
|
+
end
|
166
|
+
|
167
|
+
return routesFeatures
|
168
|
+
end
|
169
|
+
|
170
|
+
def getOutputOrders(jobId)
|
171
|
+
unless jobId.nil?
|
172
|
+
@jobId = jobId
|
173
|
+
end
|
174
|
+
|
175
|
+
urlWithJobId = 'https://logistics.arcgis.com/arcgis/rest/services/World/VehicleRoutingProblem/GPServer/SolveVehicleRoutingProblem/jobs/'+@jobId+'/results/out_stops'
|
176
|
+
|
177
|
+
res = Net::HTTP.post_form URI(urlWithJobId),
|
178
|
+
token: @token,
|
179
|
+
f: 'json'
|
180
|
+
|
181
|
+
# We return the entire string is because it have meta data about the geometry type, spatial reference, etc
|
182
|
+
ordersFeatures = JSON.parse(res.body)
|
183
|
+
# ordersFeatures = JSON.parse(res.body)["value"]["features"]
|
184
|
+
|
185
|
+
if ordersFeatures.nil?
|
186
|
+
ordersFeatures = res.body
|
187
|
+
end
|
188
|
+
|
189
|
+
return ordersFeatures
|
190
|
+
end
|
191
|
+
|
192
|
+
def loadSampleData(loadFromFile)
|
193
|
+
@loadData = Data_Load.new()
|
194
|
+
|
195
|
+
if loadFromFile
|
196
|
+
@loadData.getCoordinateFromMapSynq()
|
197
|
+
@loadData.writeCoordinateToFile()
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def mockCase1(numberOfVehicles) # Routes without any start & end time (Basic routing)
|
202
|
+
|
203
|
+
puts "Setting up orders"
|
204
|
+
|
205
|
+
@loadData.getCoordinateFromFile()
|
206
|
+
coordinate = @loadData.coordinateArrayFromFile
|
207
|
+
|
208
|
+
ord = Orders.new()
|
209
|
+
|
210
|
+
i = 0
|
211
|
+
coordinate.each do |coor|
|
212
|
+
store_name = "Store_"+i.to_s
|
213
|
+
|
214
|
+
orderAttributeObj = ord.getBasicOrderAttributeObj(store_name)
|
215
|
+
ord.addOrders(coor[0], coor[1], orderAttributeObj)
|
216
|
+
|
217
|
+
if i == 50
|
218
|
+
break
|
219
|
+
end
|
220
|
+
|
221
|
+
i += 1
|
222
|
+
end
|
223
|
+
|
224
|
+
ordersObj = ord.getOrderObj(nil)
|
225
|
+
|
226
|
+
puts "Setting up depots"
|
227
|
+
|
228
|
+
dep = Depots.new()
|
229
|
+
deportAttributeObj = dep.getDepotAttributeObj("depot_1")
|
230
|
+
dep.addDepots(103.848427, 1.277751, deportAttributeObj)
|
231
|
+
depotsObj = dep.getDepotObj(nil)
|
232
|
+
|
233
|
+
puts "Setting up routes"
|
234
|
+
|
235
|
+
route = Routes.new()
|
236
|
+
|
237
|
+
startDateTime = (Time.now.to_f * 1000).to_i
|
238
|
+
|
239
|
+
for i in 1..numberOfVehicles
|
240
|
+
truck_name = "Truck_#{i}"
|
241
|
+
route.addRoutes(truck_name, "depot_1", startDateTime, startDateTime)
|
242
|
+
end
|
243
|
+
|
244
|
+
routesObj = route.getRouteObj()
|
245
|
+
|
246
|
+
begin
|
247
|
+
contents = submitJob(ordersObj, depotsObj, routesObj, nil)
|
248
|
+
|
249
|
+
writeToFile("Mock Case 1 - #{numberOfVehicles}.json", contents)
|
250
|
+
rescue Exception => e
|
251
|
+
puts
|
252
|
+
puts e
|
253
|
+
puts
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def mockCase2(numberOfVehicles) # 9 locations with set times that vehicles must reach (no overlapping time)
|
258
|
+
|
259
|
+
puts "Setting up orders"
|
260
|
+
|
261
|
+
@loadData.getCoordinateFromFile()
|
262
|
+
coordinate = @loadData.coordinateArrayFromFile
|
263
|
+
|
264
|
+
ord = Orders.new()
|
265
|
+
|
266
|
+
i = 0
|
267
|
+
|
268
|
+
minutesInSeconds = 1800000 # 30 minutes in milliseconds
|
269
|
+
timeWindowStart1 = 1441933200000 # Default date time: 11th Sept 2015, 9:00:00 AM in milliseconds
|
270
|
+
timeWindowEnd1 = timeWindowStart1 + minutesInSeconds # Add 30 minutes every run
|
271
|
+
|
272
|
+
coordinate.each do |coor|
|
273
|
+
store_name = "Store_"+i.to_s
|
274
|
+
serviceTime = 30
|
275
|
+
|
276
|
+
if i < 9 && i >= 0
|
277
|
+
if i < 5 && i >= 1 # Can reach later than the set time here
|
278
|
+
timeWindowStart1 += (minutesInSeconds*2) # Add 1 hour every run
|
279
|
+
timeWindowEnd1 = timeWindowStart1 + minutesInSeconds # Add 30 minutes every run
|
280
|
+
maxViolationTime1 = nil
|
281
|
+
elsif i < 9 && i >= 5 # Can reach later than the set time based on the x minute set by MaxViolationTime1
|
282
|
+
timeWindowStart1 += (minutesInSeconds*2) # Add 1 hour every run
|
283
|
+
timeWindowEnd1 = timeWindowStart1 + minutesInSeconds # Add 30 minutes every run
|
284
|
+
maxViolationTime1 = 0
|
285
|
+
end
|
286
|
+
orderAttributeObj = ord.getOrderAttributeObj(store_name, serviceTime, timeWindowStart1, timeWindowEnd1, maxViolationTime1)
|
287
|
+
elsif i >= 9
|
288
|
+
timeWindowStart1 = nil
|
289
|
+
timeWindowEnd1 = nil
|
290
|
+
maxViolationTime1 = nil
|
291
|
+
orderAttributeObj = ord.getBasicOrderAttributeObj(store_name)
|
292
|
+
end
|
293
|
+
|
294
|
+
ord.addOrders(coor[0], coor[1], orderAttributeObj)
|
295
|
+
|
296
|
+
if i == 49
|
297
|
+
break
|
298
|
+
end
|
299
|
+
|
300
|
+
i += 1
|
301
|
+
end
|
302
|
+
|
303
|
+
ordersObj = ord.getOrderObj(nil)
|
304
|
+
|
305
|
+
puts "Setting up depots"
|
306
|
+
|
307
|
+
dep = Depots.new()
|
308
|
+
deportAttributeObj = dep.getDepotAttributeObj("depot_1")
|
309
|
+
dep.addDepots(103.848427, 1.277751, deportAttributeObj)
|
310
|
+
depotsObj = dep.getDepotObj(nil)
|
311
|
+
|
312
|
+
puts "Setting up routes"
|
313
|
+
|
314
|
+
route = Routes.new()
|
315
|
+
|
316
|
+
startDateTime = 1441933200000
|
317
|
+
for i in 1..numberOfVehicles
|
318
|
+
truck_name = "Truck_#{i}"
|
319
|
+
route.addRoutes(truck_name, "depot_1", startDateTime, startDateTime)
|
320
|
+
end
|
321
|
+
|
322
|
+
routesObj = route.getRouteObj()
|
323
|
+
|
324
|
+
# puts
|
325
|
+
# puts ordersObj.to_json
|
326
|
+
# puts
|
327
|
+
# puts depotsObj.to_json
|
328
|
+
# puts
|
329
|
+
# puts routesObj.to_json
|
330
|
+
# puts
|
331
|
+
|
332
|
+
begin
|
333
|
+
contents = submitJob(ordersObj, depotsObj, routesObj, nil)
|
334
|
+
|
335
|
+
writeToFile("Mock Case 2 - #{numberOfVehicles}.json", contents)
|
336
|
+
rescue Exception => e
|
337
|
+
puts
|
338
|
+
puts e
|
339
|
+
puts
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
def mockCase3(numberOfVehicles) # 8 locations with set times that vehicles must reach (with overlapping time)
|
344
|
+
|
345
|
+
puts "Setting up orders"
|
346
|
+
|
347
|
+
@loadData.getCoordinateFromFile()
|
348
|
+
coordinate = @loadData.coordinateArrayFromFile
|
349
|
+
|
350
|
+
ord = Orders.new()
|
351
|
+
|
352
|
+
i = 0
|
353
|
+
|
354
|
+
minutesInSeconds = 1800000 # 30 minutes in milliseconds
|
355
|
+
timeWindowStart1 = 1441933200000 # Default date time: 11th Sept 2015, 9:00:00 AM in milliseconds
|
356
|
+
timeWindowEnd1 = timeWindowStart1 + minutesInSeconds # Add 30 minutes every run
|
357
|
+
|
358
|
+
countThree = 0
|
359
|
+
countFive = 0
|
360
|
+
|
361
|
+
coordinate.each do |coor|
|
362
|
+
store_name = "Store_"+i.to_s
|
363
|
+
serviceTime = 30
|
364
|
+
|
365
|
+
if ((i%3 == 0 || i%5 == 0) && (countThree < 4 || countFive < 4))
|
366
|
+
if timeWindowStart1.nil? || timeWindowStart1 >= 1441944000000
|
367
|
+
timeWindowStart1 = 1441933200000
|
368
|
+
end
|
369
|
+
|
370
|
+
if i%3 == 0 && countThree < 4
|
371
|
+
timeWindowStart1 += (minutesInSeconds*2) # Add 1 hour every run
|
372
|
+
timeWindowEnd1 = timeWindowStart1 + minutesInSeconds # Add 30 minutes every run
|
373
|
+
maxViolationTime1 = nil
|
374
|
+
countThree += 1
|
375
|
+
elsif i%5 == 0 && countFive < 4
|
376
|
+
timeWindowStart1 += (minutesInSeconds*2) # Add 1 hour every run
|
377
|
+
timeWindowEnd1 = timeWindowStart1 + minutesInSeconds # Add 30 minutes every run
|
378
|
+
maxViolationTime1 = 0
|
379
|
+
countFive += 1
|
380
|
+
end
|
381
|
+
orderAttributeObj = ord.getOrderAttributeObj(store_name, serviceTime, timeWindowStart1, timeWindowEnd1, maxViolationTime1)
|
382
|
+
else
|
383
|
+
timeWindowStart1 = nil
|
384
|
+
timeWindowEnd1 = nil
|
385
|
+
maxViolationTime1 = nil
|
386
|
+
orderAttributeObj = ord.getBasicOrderAttributeObj(store_name)
|
387
|
+
end
|
388
|
+
|
389
|
+
ord.addOrders(coor[0], coor[1], orderAttributeObj)
|
390
|
+
|
391
|
+
if i == 49
|
392
|
+
break
|
393
|
+
end
|
394
|
+
|
395
|
+
i += 1
|
396
|
+
end
|
397
|
+
|
398
|
+
ordersObj = ord.getOrderObj(nil)
|
399
|
+
|
400
|
+
puts "Setting up depots"
|
401
|
+
|
402
|
+
dep = Depots.new()
|
403
|
+
deportAttributeObj = dep.getDepotAttributeObj("depot_1")
|
404
|
+
dep.addDepots(103.848427, 1.277751, deportAttributeObj)
|
405
|
+
depotsObj = dep.getDepotObj(nil)
|
406
|
+
|
407
|
+
puts "Setting up routes"
|
408
|
+
|
409
|
+
route = Routes.new()
|
410
|
+
|
411
|
+
startDateTime = 1441933200000
|
412
|
+
for i in 1..numberOfVehicles
|
413
|
+
truck_name = "Truck_#{i}"
|
414
|
+
route.addRoutes(truck_name, "depot_1", startDateTime, startDateTime)
|
415
|
+
end
|
416
|
+
|
417
|
+
routesObj = route.getRouteObj()
|
418
|
+
|
419
|
+
# puts
|
420
|
+
# puts ordersObj.to_json
|
421
|
+
# puts
|
422
|
+
# puts depotsObj.to_json
|
423
|
+
# puts
|
424
|
+
# puts routesObj.to_json
|
425
|
+
# puts
|
426
|
+
|
427
|
+
begin
|
428
|
+
contents = submitJob(ordersObj, depotsObj, routesObj, nil)
|
429
|
+
|
430
|
+
writeToFile("Mock Case 3 - #{numberOfVehicles}.json", contents)
|
431
|
+
rescue Exception => e
|
432
|
+
puts
|
433
|
+
puts e
|
434
|
+
puts
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
def mockCase4(numberOfVehicles) # Same as use case 3 but with more overlapping times than routes available
|
439
|
+
|
440
|
+
puts "Setting up orders"
|
441
|
+
|
442
|
+
@loadData.getCoordinateFromFile()
|
443
|
+
coordinate = @loadData.coordinateArrayFromFile
|
444
|
+
|
445
|
+
ord = Orders.new()
|
446
|
+
|
447
|
+
i = 0
|
448
|
+
|
449
|
+
minutesInSeconds = 1800000 # 30 minutes in milliseconds
|
450
|
+
timeWindowStart1 = 1441933200000 # Default date time: 11th Sept 2015, 9:00:00 AM in milliseconds
|
451
|
+
timeWindowEnd1 = timeWindowStart1 + minutesInSeconds # Add 30 minutes every run
|
452
|
+
|
453
|
+
countThree = 0
|
454
|
+
countFive = 0
|
455
|
+
|
456
|
+
coordinate.each do |coor|
|
457
|
+
store_name = "Store_"+i.to_s
|
458
|
+
serviceTime = 30
|
459
|
+
|
460
|
+
if i < 6
|
461
|
+
# This if block is to set 3 with MaxViolationTime1 and 3 without
|
462
|
+
# if i < 3 && i >= 0
|
463
|
+
# maxViolationTime1 = nil
|
464
|
+
# elsif i < 6 && i >= 3
|
465
|
+
# maxViolationTime1 = 0
|
466
|
+
# end
|
467
|
+
maxViolationTime1 = 0 # This is to set all 6 with MaxViolationTime1
|
468
|
+
orderAttributeObj = ord.getOrderAttributeObj(store_name, serviceTime, timeWindowStart1, timeWindowEnd1, maxViolationTime1)
|
469
|
+
else
|
470
|
+
timeWindowStart1 = nil
|
471
|
+
timeWindowEnd1 = nil
|
472
|
+
maxViolationTime1 = nil
|
473
|
+
orderAttributeObj = ord.getBasicOrderAttributeObj(store_name)
|
474
|
+
end
|
475
|
+
|
476
|
+
ord.addOrders(coor[0], coor[1], orderAttributeObj)
|
477
|
+
|
478
|
+
if i == 49
|
479
|
+
break
|
480
|
+
end
|
481
|
+
|
482
|
+
i += 1
|
483
|
+
end
|
484
|
+
|
485
|
+
ordersObj = ord.getOrderObj(nil)
|
486
|
+
|
487
|
+
puts "Setting up depots"
|
488
|
+
|
489
|
+
dep = Depots.new()
|
490
|
+
deportAttributeObj = dep.getDepotAttributeObj("depot_1")
|
491
|
+
dep.addDepots(103.848427, 1.277751, deportAttributeObj)
|
492
|
+
depotsObj = dep.getDepotObj(nil)
|
493
|
+
|
494
|
+
puts "Setting up routes"
|
495
|
+
|
496
|
+
route = Routes.new()
|
497
|
+
|
498
|
+
startDateTime = 1441933200000
|
499
|
+
for i in 1..numberOfVehicles
|
500
|
+
truck_name = "Truck_#{i}"
|
501
|
+
route.addRoutes(truck_name, "depot_1", startDateTime, startDateTime)
|
502
|
+
end
|
503
|
+
|
504
|
+
routesObj = route.getRouteObj()
|
505
|
+
|
506
|
+
# puts
|
507
|
+
# puts ordersObj.to_json
|
508
|
+
# puts
|
509
|
+
# puts depotsObj.to_json
|
510
|
+
# puts
|
511
|
+
# puts routesObj.to_json
|
512
|
+
# puts
|
513
|
+
|
514
|
+
begin
|
515
|
+
contents = submitJob(ordersObj, depotsObj, routesObj, nil)
|
516
|
+
|
517
|
+
writeToFile("Mock Case 4 - location - 6.json", contents)
|
518
|
+
rescue Exception => e
|
519
|
+
puts
|
520
|
+
puts e
|
521
|
+
puts
|
522
|
+
end
|
523
|
+
end
|
524
|
+
|
525
|
+
def writeCoordinateToFile
|
526
|
+
@loadData.getCoordinateFromFile(true)
|
527
|
+
coordinate = @loadData.coordinateArrayFromFile
|
528
|
+
writeToFile("coordinateArray", coordinate.to_json);
|
529
|
+
end
|
530
|
+
|
531
|
+
def writeToFile(filename, contents)
|
532
|
+
puts "Writing to file #{filename}"
|
533
|
+
|
534
|
+
File.open("lib/test/sample_data/#{filename}", "w") do |file|
|
535
|
+
if contents.kind_of?(Array)
|
536
|
+
contents.each do |content|
|
537
|
+
file.puts content
|
538
|
+
file.puts
|
539
|
+
end
|
540
|
+
else
|
541
|
+
file.puts contents
|
542
|
+
file.puts
|
543
|
+
end
|
544
|
+
end
|
545
|
+
|
546
|
+
puts "Done writing to file #{filename}"
|
547
|
+
puts
|
548
|
+
end
|
549
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'net/http'
|
3
|
+
|
4
|
+
class Auth
|
5
|
+
def initialize(client_id, client_secret)
|
6
|
+
if client_id.nil? || client_id.empty? || client_secret.nil? || client_secret.empty?
|
7
|
+
raise ArgumentError, "Both Client ID & Client Secret has to be set"
|
8
|
+
end
|
9
|
+
|
10
|
+
@client_id = client_id
|
11
|
+
@client_secret = client_secret
|
12
|
+
end
|
13
|
+
|
14
|
+
def generateToken
|
15
|
+
begin
|
16
|
+
res = Net::HTTP.post_form URI('https://www.arcgis.com/sharing/rest/oauth2/token'),
|
17
|
+
f: 'json',
|
18
|
+
client_id: @client_id,
|
19
|
+
client_secret: @client_secret,
|
20
|
+
grant_type: 'client_credentials'
|
21
|
+
|
22
|
+
token = JSON.parse(res.body)['access_token']
|
23
|
+
# puts "\n"+res.body
|
24
|
+
return token
|
25
|
+
rescue => e
|
26
|
+
puts e
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
class Depots
|
2
|
+
# Longitude => xCoordinate, Latitude => yCoordinate
|
3
|
+
def addDepots (xCoordinate, yCoordinate, depotAttributeObj)
|
4
|
+
my_depot = {
|
5
|
+
:geometry => {
|
6
|
+
:x => xCoordinate,
|
7
|
+
:y => yCoordinate
|
8
|
+
},
|
9
|
+
:attributes => depotAttributeObj
|
10
|
+
}
|
11
|
+
if @depotArr.nil?
|
12
|
+
@depotArr = []
|
13
|
+
end
|
14
|
+
|
15
|
+
@depotArr.push(my_depot)
|
16
|
+
end
|
17
|
+
|
18
|
+
def getDepotArr
|
19
|
+
return @depotArr
|
20
|
+
end
|
21
|
+
|
22
|
+
def getDepotAttributeObj(depotName)
|
23
|
+
depotAttributeObj = {
|
24
|
+
:Name => depotName
|
25
|
+
}
|
26
|
+
|
27
|
+
return depotAttributeObj
|
28
|
+
end
|
29
|
+
|
30
|
+
# Get the Orders Attribute Object from the param passed in
|
31
|
+
# def getDepotAttributeObj(depotName, timeStart1, timeEnd1)
|
32
|
+
# depotAttributeObj = {
|
33
|
+
# :Name => depotName,
|
34
|
+
# :TimeWindowStart1 => timeStart1,
|
35
|
+
# :TimeWindowEnd1 => timeEnd1
|
36
|
+
# }
|
37
|
+
|
38
|
+
# return depotAttributeObj
|
39
|
+
# end
|
40
|
+
|
41
|
+
def getDepotObj (wkid)
|
42
|
+
if wkid.nil?
|
43
|
+
depotObj = {
|
44
|
+
:features => @depotArr
|
45
|
+
}
|
46
|
+
else
|
47
|
+
depotObj = {
|
48
|
+
:spatialReference => {
|
49
|
+
:wkid => wkid
|
50
|
+
},
|
51
|
+
:features => @depotArr
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
return depotObj
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
class Orders
|
2
|
+
# Longitude => xCoordinate, Latitude => yCoordinate
|
3
|
+
def addOrders (xCoordinate, yCoordinate, orderAttributeObj)
|
4
|
+
my_order = {
|
5
|
+
:geometry => {
|
6
|
+
:x => xCoordinate,
|
7
|
+
:y => yCoordinate
|
8
|
+
},
|
9
|
+
:attributes => orderAttributeObj
|
10
|
+
}
|
11
|
+
if @orderArr.nil?
|
12
|
+
@orderArr = []
|
13
|
+
end
|
14
|
+
|
15
|
+
@orderArr.push(my_order)
|
16
|
+
end
|
17
|
+
|
18
|
+
def getOrderArr
|
19
|
+
return @orderArr
|
20
|
+
end
|
21
|
+
|
22
|
+
# Get basic Orders Attribute Object from the param passed in
|
23
|
+
def getBasicOrderAttributeObj (orderName)
|
24
|
+
orderAttributeObj = {
|
25
|
+
:Name => orderName
|
26
|
+
}
|
27
|
+
|
28
|
+
return orderAttributeObj
|
29
|
+
end
|
30
|
+
|
31
|
+
# Get orders attribute object from the param passed in
|
32
|
+
# timeWindow* refers to the time that the service
|
33
|
+
def getOrderAttributeObj (orderName, serviceTime, timeWindowStart1, timeWindowEnd1, maxViolationTime1)
|
34
|
+
if maxViolationTime1.nil?
|
35
|
+
orderAttributeObj = {
|
36
|
+
:Name => orderName,
|
37
|
+
:ServiceTime => serviceTime,
|
38
|
+
:TimeWindowStart1 => timeWindowStart1,
|
39
|
+
:TimeWindowEnd1 => timeWindowEnd1
|
40
|
+
}
|
41
|
+
else
|
42
|
+
orderAttributeObj = {
|
43
|
+
:Name => orderName,
|
44
|
+
:ServiceTime => serviceTime,
|
45
|
+
:TimeWindowStart1 => timeWindowStart1,
|
46
|
+
:TimeWindowEnd1 => timeWindowEnd1,
|
47
|
+
:MaxViolationTime1 => maxViolationTime1
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
return orderAttributeObj
|
53
|
+
end
|
54
|
+
|
55
|
+
# Get the Orders Attribute Object from the param passed in
|
56
|
+
# def getOrderAttributeObj (orderName, deliveryQty, serviceTime, timeStart1, timeEnd1, maxViolationTime)
|
57
|
+
# orderAttributeObj = {
|
58
|
+
# :Name => orderName,
|
59
|
+
# :DeliveryQuantities => deliveryQty,
|
60
|
+
# :ServiceTime => serviceTime,
|
61
|
+
# :TimeWindowStart1 => timeStart1,
|
62
|
+
# :TimeWindowEnd1 => timeEnd1,
|
63
|
+
# :MaxViolationTime1 => maxViolationTime
|
64
|
+
# }
|
65
|
+
|
66
|
+
# return orderAttributeObj
|
67
|
+
# end
|
68
|
+
|
69
|
+
def getOrderObj (wkid)
|
70
|
+
if wkid.nil?
|
71
|
+
orderObj = {
|
72
|
+
:features => @orderArr
|
73
|
+
}
|
74
|
+
else
|
75
|
+
orderObj = {
|
76
|
+
:spatialReference => {
|
77
|
+
:wkid => wkid
|
78
|
+
},
|
79
|
+
:features => @orderArr
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
return orderObj
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
class Routes
|
2
|
+
def addRoutes (routeName, startDepotName, earliestStartTime, latestStartTime)
|
3
|
+
my_route = {
|
4
|
+
:attributes => {
|
5
|
+
:Name => routeName,
|
6
|
+
:StartDepotName => startDepotName, # Either this or EndDepotName must be present. This is a foreign key to the "Name" attribute in the depots param. Thus, the values MUST match
|
7
|
+
:EarliestStartTime => earliestStartTime,
|
8
|
+
:LatestStartTime => latestStartTime
|
9
|
+
}
|
10
|
+
}
|
11
|
+
|
12
|
+
if @routeArr.nil?
|
13
|
+
@routeArr = []
|
14
|
+
end
|
15
|
+
|
16
|
+
@routeArr.push(my_route)
|
17
|
+
end
|
18
|
+
|
19
|
+
# def addRoutes (routeName, startDepotName, endDepotName, startDepotServiceTime, earliestStartTime, latestStartTime, capacities, costPerUnitTime, costPerUnitDistance, maxOrderCount, maxTotalTime, maxTotalTravelTime, maxTotalDistance)
|
20
|
+
# my_route = {
|
21
|
+
# :attributes => {
|
22
|
+
# :Name => routeName,
|
23
|
+
# :StartDepotName => startDepotName, # Either this or EndDepotName must be present. This is a foreign key to the "Name" attribute in the depots param. Thus, the values MUST match
|
24
|
+
# :EndDepotName => endDepotName,
|
25
|
+
# :StartDepotServiceTime => startDepotServiceTime,
|
26
|
+
# :EarliestStartTime => earliestStartTime,
|
27
|
+
# :LatestStartTime => latestStartTime,
|
28
|
+
# :Capacities => capacities,
|
29
|
+
# :CostPerUnitTime => costPerUnitTime,
|
30
|
+
# :CostPerUnitDistance => costPerUnitDistance,
|
31
|
+
# :MaxOrderCount => maxOrderCount,
|
32
|
+
# :MaxTotalTime => maxTotalTime,
|
33
|
+
# :MaxTotalTravelTime => maxTotalTravelTime,
|
34
|
+
# :MaxTotalDistance => maxTotalDistance
|
35
|
+
# }
|
36
|
+
# }
|
37
|
+
|
38
|
+
# if @routeArr.nil?
|
39
|
+
# @routeArr = []
|
40
|
+
# end
|
41
|
+
|
42
|
+
# @routeArr.push(my_route)
|
43
|
+
# end
|
44
|
+
|
45
|
+
def getRouteArr
|
46
|
+
return @routeArr
|
47
|
+
end
|
48
|
+
|
49
|
+
def getRouteObj
|
50
|
+
routeObj = {
|
51
|
+
:features => @routeArr
|
52
|
+
}
|
53
|
+
|
54
|
+
return routeObj
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
class Data_Load
|
4
|
+
attr_accessor :coordinateArrayFromFile, :postcodeArrayFromFile, :coordinateArrayFromServer
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
# Will there be an instance where @postcodeArrayFromFile is nil?
|
8
|
+
if @postcodeArrayFromFile.nil?
|
9
|
+
@postcodeArrayFromFile = []
|
10
|
+
end
|
11
|
+
|
12
|
+
if @coordinateArrayFromServer.nil?
|
13
|
+
@coordinateArrayFromServer = []
|
14
|
+
end
|
15
|
+
|
16
|
+
if @coordinateArrayFromFile.nil?
|
17
|
+
@coordinateArrayFromFile = []
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def getCoordinateFromMapSynq
|
22
|
+
puts "Getting postcodes from file..."
|
23
|
+
|
24
|
+
getPostcodeFromFile()
|
25
|
+
|
26
|
+
print "Requesting coordinates from server..."
|
27
|
+
|
28
|
+
@postcodeArrayFromFile.each do |postcode|
|
29
|
+
print "."
|
30
|
+
|
31
|
+
mapsynqUrl = "http://www.mapsynq.com/home/search1?q=#{postcode}&lat=&lon=&page=1"
|
32
|
+
|
33
|
+
begin
|
34
|
+
url = URI.parse(mapsynqUrl)
|
35
|
+
req = Net::HTTP::Get.new(url.to_s)
|
36
|
+
res = Net::HTTP.start(url.host, url.port) {|http|
|
37
|
+
http.request(req)
|
38
|
+
}
|
39
|
+
|
40
|
+
result = JSON.parse(res.body)["result"]
|
41
|
+
|
42
|
+
unless result.empty?
|
43
|
+
yCoordinate = result[0]["true_latitude"]
|
44
|
+
xCoordinate = result[0]["true_longitude"]
|
45
|
+
coordinate = "#{xCoordinate}-#{yCoordinate}"
|
46
|
+
|
47
|
+
coordinateArrayFromServer.push(coordinate)
|
48
|
+
end
|
49
|
+
rescue
|
50
|
+
puts $!, $@
|
51
|
+
puts
|
52
|
+
next
|
53
|
+
end
|
54
|
+
end
|
55
|
+
print "\n"
|
56
|
+
puts "Done requesting coordinates from server"
|
57
|
+
puts
|
58
|
+
end
|
59
|
+
|
60
|
+
def getPostcodeFromFile
|
61
|
+
puts "Getting postcodes from file"
|
62
|
+
|
63
|
+
File.open("lib/test/sample_data/postcodes", "r") do |file|
|
64
|
+
while line = file.gets
|
65
|
+
@postcodeArrayFromFile.push(line.strip)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
puts "Done getting postcodes from file"
|
70
|
+
puts
|
71
|
+
end
|
72
|
+
|
73
|
+
def getCoordinateFromFile(convertToNumbers = nil)
|
74
|
+
puts "Getting coordinates from file"
|
75
|
+
if convertToNumbers.nil?
|
76
|
+
convertToNumbers = false
|
77
|
+
end
|
78
|
+
|
79
|
+
File.open("lib/test/sample_data/coordinates", "r") do |file|
|
80
|
+
while line = file.gets
|
81
|
+
coordinate = line.strip.split("-")
|
82
|
+
if convertToNumbers == true
|
83
|
+
if coordinate[0].is_number? && coordinate[1].is_number?
|
84
|
+
coordinate[0] = coordinate[0].to_f
|
85
|
+
coordinate[1] = coordinate[1].to_f
|
86
|
+
else
|
87
|
+
raise "Error! #{coordinate[0]} & #{coordinate[1]} is not a number!"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
@coordinateArrayFromFile.push(coordinate)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
puts "Done getting coordinates from file"
|
95
|
+
puts
|
96
|
+
end
|
97
|
+
|
98
|
+
def writeCoordinateToFile
|
99
|
+
puts "Writing coordinates to file"
|
100
|
+
|
101
|
+
File.open("lib/test/sample_data/coordinates", "w") do |file|
|
102
|
+
@coordinateArrayFromServer.each do |coordinate|
|
103
|
+
file.puts coordinate
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
puts "Done writing coordinates to file"
|
108
|
+
puts
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class String
|
113
|
+
def is_number?
|
114
|
+
true if Float(self) rescue false
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
018954
|
2
|
+
237978
|
3
|
+
098138
|
4
|
+
731678
|
5
|
+
259727
|
6
|
+
569922
|
7
|
+
560163
|
8
|
+
560448
|
9
|
+
567740
|
10
|
+
329783
|
11
|
+
588177
|
12
|
+
469661
|
13
|
+
467360
|
14
|
+
460539
|
15
|
+
470632
|
16
|
+
470744
|
17
|
+
330022
|
18
|
+
579837
|
19
|
+
569981
|
20
|
+
048508
|
21
|
+
188024
|
22
|
+
188426
|
23
|
+
650632
|
24
|
+
659841
|
25
|
+
650152
|
26
|
+
650374
|
27
|
+
677743
|
28
|
+
738099
|
29
|
+
819643
|
30
|
+
819633
|
31
|
+
486038
|
32
|
+
680624
|
33
|
+
680623
|
34
|
+
689812
|
35
|
+
689810
|
36
|
+
680533
|
37
|
+
768677
|
38
|
+
688892
|
39
|
+
208539
|
40
|
+
120451
|
41
|
+
129588
|
42
|
+
545078
|
43
|
+
528833
|
44
|
+
519498
|
45
|
+
629117
|
46
|
+
670445
|
47
|
+
258748
|
48
|
+
238884
|
49
|
+
179097
|
50
|
+
640762
|
51
|
+
380113
|
52
|
+
237994
|
53
|
+
099253
|
54
|
+
530208
|
55
|
+
538692
|
56
|
+
530684
|
57
|
+
538766
|
58
|
+
099010
|
59
|
+
188067
|
60
|
+
609601
|
61
|
+
209037
|
62
|
+
609731
|
63
|
+
608549
|
64
|
+
618640
|
65
|
+
649849
|
66
|
+
600256
|
67
|
+
649520
|
68
|
+
648886
|
69
|
+
649296
|
70
|
+
397726
|
71
|
+
760846
|
72
|
+
229899
|
73
|
+
179030
|
74
|
+
238868
|
75
|
+
510258
|
76
|
+
238863
|
77
|
+
039594
|
78
|
+
138588
|
79
|
+
569830
|
80
|
+
637331
|
81
|
+
117587
|
82
|
+
556083
|
83
|
+
188721
|
84
|
+
769098
|
85
|
+
188307
|
86
|
+
449408
|
87
|
+
519640
|
88
|
+
059108
|
89
|
+
640638
|
90
|
+
238839
|
91
|
+
350148
|
92
|
+
119963
|
93
|
+
828815
|
94
|
+
149066
|
95
|
+
149053
|
96
|
+
179103
|
97
|
+
159460
|
98
|
+
545082
|
99
|
+
797653
|
100
|
+
540118
|
101
|
+
544964
|
102
|
+
550267
|
103
|
+
555951
|
104
|
+
455871
|
105
|
+
138651
|
106
|
+
609081
|
107
|
+
757713
|
108
|
+
520513
|
109
|
+
520512
|
110
|
+
529341
|
111
|
+
529510
|
112
|
+
529284
|
113
|
+
529705
|
114
|
+
529757
|
115
|
+
168732
|
116
|
+
319515
|
117
|
+
310490
|
118
|
+
310109
|
119
|
+
319387
|
120
|
+
609971
|
121
|
+
307591
|
122
|
+
208767
|
123
|
+
126844
|
124
|
+
738931
|
125
|
+
730900
|
126
|
+
730768
|
127
|
+
758382
|
128
|
+
760293
|
129
|
+
769027
|
metadata
CHANGED
@@ -1,47 +1,67 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: arcgis_vrps
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.2.
|
4
|
+
version: 0.0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kiong
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
date: 2015-09-08 00:00:00.000000000 Z
|
12
|
-
dependencies:
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ~>
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '1.8'
|
19
|
+
name: json
|
20
|
+
prerelease: false
|
21
|
+
type: :runtime
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.8'
|
27
|
+
description: A ruby gem interface for ArcGIS (platform of Esri) Vehicle Routing Problem service REST API
|
16
28
|
email: kiong90@gmail.com
|
17
29
|
executables: []
|
18
30
|
extensions: []
|
19
|
-
extra_rdoc_files:
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README.md
|
20
33
|
files:
|
21
|
-
- lib/arcgis_vrps.rb
|
22
|
-
- lib/arcgis_vrps/
|
34
|
+
- ./lib/arcgis_vrps.rb
|
35
|
+
- ./lib/arcgis_vrps/auth.rb
|
36
|
+
- ./lib/arcgis_vrps/depots.rb
|
37
|
+
- ./lib/arcgis_vrps/orders.rb
|
38
|
+
- ./lib/arcgis_vrps/routes.rb
|
39
|
+
- ./lib/test/data_load.rb
|
40
|
+
- ./lib/test/sample_data/postcodes
|
41
|
+
- README.md
|
23
42
|
homepage: http://rubygems.org/gems/arcgis_vrps
|
24
43
|
licenses:
|
25
44
|
- MIT
|
26
|
-
metadata:
|
27
|
-
|
45
|
+
metadata:
|
46
|
+
source_code: https://github.com/tlkiong/arcgis_vrps
|
47
|
+
post_install_message:
|
28
48
|
rdoc_options: []
|
29
49
|
require_paths:
|
30
50
|
- lib
|
31
51
|
required_ruby_version: !ruby/object:Gem::Requirement
|
32
52
|
requirements:
|
33
|
-
- -
|
53
|
+
- - '>='
|
34
54
|
- !ruby/object:Gem::Version
|
35
55
|
version: '0'
|
36
56
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
37
57
|
requirements:
|
38
|
-
- -
|
58
|
+
- - '>='
|
39
59
|
- !ruby/object:Gem::Version
|
40
60
|
version: '0'
|
41
61
|
requirements: []
|
42
|
-
rubyforge_project:
|
43
|
-
rubygems_version: 2.
|
44
|
-
signing_key:
|
62
|
+
rubyforge_project:
|
63
|
+
rubygems_version: 2.1.9
|
64
|
+
signing_key:
|
45
65
|
specification_version: 4
|
46
|
-
summary:
|
66
|
+
summary: ArcGIS Vehicle Routing Problem service API
|
47
67
|
test_files: []
|