traveltime-api 1.4.1
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.
- data/lib/traveltime_api.rb +264 -0
- metadata +79 -0
@@ -0,0 +1,264 @@
|
|
1
|
+
require 'rest-client'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
# Class for using TravelTime API service. Learn more about TravelTime at
|
5
|
+
# www.traveltimeapp.com
|
6
|
+
#
|
7
|
+
# ## Defined types ##
|
8
|
+
#
|
9
|
+
# * TransportationMode - string with one of following values:
|
10
|
+
# "walking", "driving", "walking_train", "walking_bus", "public_transport"
|
11
|
+
#
|
12
|
+
# * EdgeType - describes type of the edge. String with one of following values:
|
13
|
+
# "car", "walk", "train", "bus", "cable_car", "plane", "ship"
|
14
|
+
#
|
15
|
+
# ## Thrown errors ##
|
16
|
+
#
|
17
|
+
# Each method that does a call to the server can raise TravelTimeAPI::BadRequest
|
18
|
+
# or TravelTimeAPI::ServerError errors.
|
19
|
+
#
|
20
|
+
class TravelTimeAPI
|
21
|
+
DEFAULT_URL = "http://api.traveltimeapp.com"
|
22
|
+
OUT_OF_REACH_CODE = "out-of-reach"
|
23
|
+
NO_DATA_CODE = "no-data"
|
24
|
+
|
25
|
+
# Class representing authentification data for TravelTime API.
|
26
|
+
class Auth < Struct.new(:app_id, :app_key)
|
27
|
+
def to_h
|
28
|
+
{:app_id => app_id, :app_key => app_key}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Raised if API user made bad request.
|
33
|
+
class BadRequest < RuntimeError
|
34
|
+
# Server provided String code for this error.
|
35
|
+
attr_reader :code
|
36
|
+
# Server provided String explanation of error.
|
37
|
+
attr_reader :details
|
38
|
+
|
39
|
+
def initialize(code, details)
|
40
|
+
@code = code
|
41
|
+
@details = details
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_s
|
45
|
+
"TravelTimeAPI::BadRequest[#{@code}: #{details}]"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Raised if API server has encountered an error.
|
50
|
+
class ServerError < RuntimeError
|
51
|
+
# Human readable error message.
|
52
|
+
attr_reader :error
|
53
|
+
|
54
|
+
def initialize(error)
|
55
|
+
@error = error
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_s
|
59
|
+
"TravelTimeAPI::ServerError[#{@error}]"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Represents that API does has a dataset for this point/region but cannot
|
64
|
+
# calculate any useful data from this point.
|
65
|
+
class OutOfReach < RuntimeError; end
|
66
|
+
|
67
|
+
# Represents that API does not have data for this point/region.
|
68
|
+
class NoData < RuntimeError; end
|
69
|
+
|
70
|
+
# Result of #time_filter method for one mode.
|
71
|
+
#
|
72
|
+
# Properties is a Hash that can have (if you requested them) following pairs:
|
73
|
+
# * "time" => Fixnum - time from origin to point in seconds.
|
74
|
+
# * "distance" => Fixnum - distance from origin to point in meters.
|
75
|
+
#
|
76
|
+
# @param points [Hash] {"id1" => Properties, ...}
|
77
|
+
# @param accuracy [Symbol] :exact or :approx. Decrease travel time to switch
|
78
|
+
# from approximate to exact accuracy.
|
79
|
+
class TimeFilterResult < Struct.new(:points, :accuracy); end
|
80
|
+
|
81
|
+
# Result of #time_map method.
|
82
|
+
#
|
83
|
+
# @param shape [Array] array of polylines that represent areas reachable by given time.
|
84
|
+
# [Cluster, Cluster, ...], where Cluster is [[lat, lon], [lat, lon], ...]
|
85
|
+
# @param accuracy [Symbol] :exact or :approx. Decrease travel time to switch
|
86
|
+
# from approximate to exact accuracy.
|
87
|
+
class TimeMapResult < Struct.new(:shape, :accuracy); end
|
88
|
+
|
89
|
+
# Result of #routes method.
|
90
|
+
#
|
91
|
+
# * RouteDescriptionPart is a structure describing one part of the route:
|
92
|
+
# {
|
93
|
+
# "mode": EdgeType,
|
94
|
+
# # Array of coordinates for this route part. Can be used to draw a polyline
|
95
|
+
# # on a map.
|
96
|
+
# "coords": [[lat, lon], [lat, lon], [lat, lon], ...],
|
97
|
+
# "directions: String, # textual description in english of this route part
|
98
|
+
# "distance": Int, # distance covered in metres for this route part
|
99
|
+
# "time": Int # time taken to travel through this route part in seconds
|
100
|
+
# }
|
101
|
+
#
|
102
|
+
# @param routes [Hash] {"id1" -> [RouteDescriptionPart, RouteDescriptionPart, ...], ...}
|
103
|
+
class RoutesResult < Struct.new(:routes); end
|
104
|
+
|
105
|
+
# Initializes API. You need to specify your auth here.
|
106
|
+
#
|
107
|
+
# Example:
|
108
|
+
# api = TravelTimeAPI.new(TravelTimeAPI::Auth.new(app_id, app_key))
|
109
|
+
#
|
110
|
+
def initialize(auth, url=DEFAULT_URL)
|
111
|
+
@auth = auth
|
112
|
+
@url = url
|
113
|
+
end
|
114
|
+
|
115
|
+
# Given latitude and longtitude of a point return whether TravelTime has
|
116
|
+
# data for those coordinates or not.
|
117
|
+
#
|
118
|
+
# WARNING: even though ending in ? this method does not return a boolean
|
119
|
+
# value and you cannot use it directly in boolean expressions!
|
120
|
+
#
|
121
|
+
# This is due to the fact that is needs to return 3 possible outcomes:
|
122
|
+
# - :data - we have data for this point
|
123
|
+
# - :out_of_reach - we have a data set for this point but we couldn't attach
|
124
|
+
# that point exactly to any significant roads or routes.
|
125
|
+
# - :no_data - we do not have any data for this point.
|
126
|
+
#
|
127
|
+
# @param lat [Float]
|
128
|
+
# @param lng [Float]
|
129
|
+
# @return [Symbol]
|
130
|
+
def has_data?(lat, lng)
|
131
|
+
request = {:coords => [lat, lng]}
|
132
|
+
response = raw_post("/has_data", request)
|
133
|
+
if response["has"] == true
|
134
|
+
:data
|
135
|
+
elsif response["code"] == OUT_OF_REACH_CODE
|
136
|
+
:out_of_reach
|
137
|
+
elsif response["code"] == NO_DATA_CODE
|
138
|
+
:no_data
|
139
|
+
else
|
140
|
+
json_error(response)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Takes input parameters and returns how long does it take to reach each point
|
145
|
+
# in chosen mode of transportation (in seconds).
|
146
|
+
#
|
147
|
+
# Points whose travel times which exceed _travel_time_ are not included in the
|
148
|
+
# result.
|
149
|
+
#
|
150
|
+
# @param start_time [Time] Time moment when we start our search.
|
151
|
+
# @param travel_time [Int] Max time in seconds to the point.
|
152
|
+
# @param modes [Array] Array of transportation modes you want calculated.
|
153
|
+
# See class documentation for transportation mode info.
|
154
|
+
# @param origin [Array] [latitude, longtitude]
|
155
|
+
# @param points [Hash] Points th{"id1" => [latitude, longtitude], ...}
|
156
|
+
# @param properties [Array] array of requested point properties. Valid values: :time,
|
157
|
+
# :distance. :distance cannot be used with any mode that
|
158
|
+
# involves public transport as the results will be 0.
|
159
|
+
# @return [Hash] {mode => TravelTimeAPI::TimeFilterResult, ...}
|
160
|
+
def time_filter(start_time, travel_time, modes, origin, points, properties=[:time])
|
161
|
+
modes = Array(modes)
|
162
|
+
request = {
|
163
|
+
:start_time => format_time(start_time),
|
164
|
+
:travel_time => travel_time,
|
165
|
+
:modes => modes,
|
166
|
+
:origin => origin,
|
167
|
+
:points => points,
|
168
|
+
:properties => properties
|
169
|
+
}
|
170
|
+
|
171
|
+
response = post("/v2/time_filter", request)
|
172
|
+
response.inject({}) do |hash, (mode, ranked)|
|
173
|
+
hash[mode] = TimeFilterResult.new(ranked["times"], ranked["accuracy"].to_sym)
|
174
|
+
hash
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Takes input parameters and returns polylines for each cluster that you can
|
179
|
+
# reach by given time.
|
180
|
+
#
|
181
|
+
# @param start_time [Time] Time moment when we start our search.
|
182
|
+
# @param travel_time [Int] Max time in seconds to the point.
|
183
|
+
# @param mode [String] Transportation mode. See class documentation for info.
|
184
|
+
# @param origin [Array] [latitude, longtitude]
|
185
|
+
# @param smooth [Boolean] Shall the returned shaped be smoothed?
|
186
|
+
# @param max_points [Fixnum] Maximum number of points in returned polygons. nil
|
187
|
+
# for no limit, Fixnum for limit. Minimum of 4 points required!
|
188
|
+
# @return [TravelTimeAPI::TimeMapResult]
|
189
|
+
def time_map(start_time, travel_time, mode, origin, smooth, max_points=nil)
|
190
|
+
raise ArgumentError.new("At least 4 points are required in max_points!") \
|
191
|
+
unless max_points.nil? || max_points >= 4
|
192
|
+
|
193
|
+
request = {
|
194
|
+
:start_time => format_time(start_time),
|
195
|
+
:travel_time => travel_time,
|
196
|
+
:mode => mode,
|
197
|
+
:origin => origin,
|
198
|
+
:smooth => smooth,
|
199
|
+
:max_points => max_points
|
200
|
+
}
|
201
|
+
|
202
|
+
response = post("/v2/time_map", request)
|
203
|
+
TimeMapResult.new(response["shape"], response["accuracy"].to_sym)
|
204
|
+
end
|
205
|
+
|
206
|
+
# Takes input parameters and returns routes to each of the points within
|
207
|
+
# travel_time.
|
208
|
+
#
|
209
|
+
# Input parameters are the same as #travel_time.
|
210
|
+
# @return [TravelTimeAPI::RoutesResult]
|
211
|
+
def routes(start_time, travel_time, mode, origin, points)
|
212
|
+
request = {
|
213
|
+
:start_time => format_time(start_time),
|
214
|
+
:travel_time => travel_time,
|
215
|
+
:mode => mode,
|
216
|
+
:origin => origin,
|
217
|
+
:points => points
|
218
|
+
}
|
219
|
+
|
220
|
+
response = post("/v2/routes", request)
|
221
|
+
RoutesResult.new(response)
|
222
|
+
end
|
223
|
+
|
224
|
+
private
|
225
|
+
|
226
|
+
def format_time(time)
|
227
|
+
time.strftime("%FT%T%:z")
|
228
|
+
end
|
229
|
+
|
230
|
+
def raw_post(action, request)
|
231
|
+
response = RestClient.post(
|
232
|
+
@url + action,
|
233
|
+
request.merge(@auth.to_h).to_json,
|
234
|
+
:content_type => :json
|
235
|
+
)
|
236
|
+
JSON.parse(response)
|
237
|
+
rescue RestClient::ExceptionWithResponse => e
|
238
|
+
raise ServerError.new(
|
239
|
+
"server error, http_status: #{e.http_code}, http_body: #{e.http_body}"
|
240
|
+
)
|
241
|
+
rescue JSON::ParserError => e
|
242
|
+
raise ServerError.new("Cannot parse server response as JSON: #{response}")
|
243
|
+
end
|
244
|
+
|
245
|
+
def post(action, request)
|
246
|
+
json = raw_post(action, request)
|
247
|
+
|
248
|
+
case json["code"]
|
249
|
+
when OUT_OF_REACH_CODE
|
250
|
+
raise OutOfReach
|
251
|
+
when NO_DATA_CODE
|
252
|
+
raise NoData
|
253
|
+
when nil
|
254
|
+
json
|
255
|
+
else
|
256
|
+
json_error(json)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def json_error(json)
|
261
|
+
raise ServerError.new("code: #{json["code"]}, details: #{json["details"]}")
|
262
|
+
end
|
263
|
+
|
264
|
+
end
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: traveltime-api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.4.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Artūras Šlajus
|
9
|
+
- IGeolise
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2013-04-15 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rest-client
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.6.7
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ! '>='
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: 1.6.7
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: json
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ! '>='
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: 1.7.0
|
39
|
+
type: :runtime
|
40
|
+
prerelease: false
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 1.7.0
|
47
|
+
description: ''
|
48
|
+
email:
|
49
|
+
- arturas@igeolise.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- lib/traveltime_api.rb
|
55
|
+
homepage: http://traveltimeapp.com
|
56
|
+
licenses: []
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options: []
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ! '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ! '>='
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
requirements: []
|
74
|
+
rubyforge_project:
|
75
|
+
rubygems_version: 1.8.23
|
76
|
+
signing_key:
|
77
|
+
specification_version: 3
|
78
|
+
summary: Simple wrapper for TravelTime REST API.
|
79
|
+
test_files: []
|