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.
Files changed (2) hide show
  1. data/lib/traveltime_api.rb +264 -0
  2. 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: []