google_maps_service 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +11 -0
- data/CHANGELOG.md +34 -0
- data/README.md +299 -56
- data/google_maps_service.gemspec +3 -2
- data/lib/google_maps_service.rb +42 -9
- data/lib/google_maps_service/apis.rb +6 -0
- data/lib/google_maps_service/{directions.rb → apis/directions.rb} +38 -25
- data/lib/google_maps_service/{distance_matrix.rb → apis/distance_matrix.rb} +40 -21
- data/lib/google_maps_service/{elevation.rb → apis/elevation.rb} +22 -21
- data/lib/google_maps_service/apis/geocoding.rb +85 -0
- data/lib/google_maps_service/{roads.rb → apis/roads.rb} +68 -38
- data/lib/google_maps_service/{time_zone.rb → apis/time_zone.rb} +12 -7
- data/lib/google_maps_service/client.rb +175 -158
- data/lib/google_maps_service/convert.rb +19 -79
- data/lib/google_maps_service/errors.rb +13 -4
- data/lib/google_maps_service/polyline.rb +90 -0
- data/lib/google_maps_service/url.rb +64 -0
- data/lib/google_maps_service/validator.rb +16 -0
- data/lib/google_maps_service/version.rb +21 -1
- metadata +28 -25
- data/bin/console +0 -14
- data/bin/setup +0 -7
- data/lib/google_maps_service/geocoding.rb +0 -58
@@ -1,8 +1,8 @@
|
|
1
1
|
require 'multi_json'
|
2
2
|
|
3
|
-
module GoogleMapsService
|
3
|
+
module GoogleMapsService::Apis
|
4
4
|
|
5
|
-
# Performs requests to the Google Maps Roads API.
|
5
|
+
# Performs requests to the Google Maps Roads API.
|
6
6
|
module Roads
|
7
7
|
|
8
8
|
# Base URL of Google Maps Roads API
|
@@ -14,28 +14,37 @@ module GoogleMapsService
|
|
14
14
|
# set of data with the points snapped to the most likely roads the vehicle
|
15
15
|
# was traveling along.
|
16
16
|
#
|
17
|
-
# @
|
18
|
-
#
|
17
|
+
# @example Single point snap
|
18
|
+
# results = client.snap_to_roads([40.714728, -73.998672])
|
19
19
|
#
|
20
|
-
# @
|
20
|
+
# @example Multi points snap
|
21
|
+
# path = [
|
22
|
+
# [-33.8671, 151.20714],
|
23
|
+
# [-33.86708, 151.20683000000002],
|
24
|
+
# [-33.867070000000005, 151.20674000000002],
|
25
|
+
# [-33.86703, 151.20625]
|
26
|
+
# ]
|
27
|
+
# results = client.snap_to_roads(path, interpolate: true)
|
28
|
+
#
|
29
|
+
# @param [Array] path The path to be snapped. Array of latitude/longitude pairs.
|
30
|
+
# @param [Boolean] interpolate Whether to interpolate a path to include all points
|
21
31
|
# forming the full road-geometry. When true, additional interpolated
|
22
32
|
# points will also be returned, resulting in a path that smoothly
|
23
33
|
# follows the geometry of the road, even around corners and through
|
24
34
|
# tunnels. Interpolated paths may contain more points than the
|
25
35
|
# original path.
|
26
|
-
# :type interpolate: bool
|
27
36
|
#
|
28
|
-
#
|
29
|
-
def snap_to_roads(path
|
37
|
+
# @return [Array] Array of snapped points.
|
38
|
+
def snap_to_roads(path, interpolate: false)
|
30
39
|
path = GoogleMapsService::Convert.waypoints(path)
|
31
40
|
|
32
41
|
params = {
|
33
42
|
path: path
|
34
43
|
}
|
35
44
|
|
36
|
-
params[:interpolate] =
|
45
|
+
params[:interpolate] = 'true' if interpolate
|
37
46
|
|
38
|
-
return get(
|
47
|
+
return get('/v1/snapToRoads', params,
|
39
48
|
base_url: ROADS_BASE_URL,
|
40
49
|
accepts_client_id: false,
|
41
50
|
custom_response_decoder: method(:extract_roads_body))[:snappedPoints]
|
@@ -43,37 +52,53 @@ module GoogleMapsService
|
|
43
52
|
|
44
53
|
# Returns the posted speed limit (in km/h) for given road segments.
|
45
54
|
#
|
55
|
+
# @example Multi places snap
|
56
|
+
# place_ids = [
|
57
|
+
# 'ChIJ0wawjUCuEmsRgfqC5Wd9ARM',
|
58
|
+
# 'ChIJ6cs2kkCuEmsRUfqC5Wd9ARM'
|
59
|
+
# ]
|
60
|
+
# results = client.speed_limits(place_ids)
|
61
|
+
#
|
46
62
|
# @param [String, Array<String>] place_ids The Place ID of the road segment. Place IDs are returned
|
47
63
|
# by the snap_to_roads function. You can pass up to 100 Place IDs.
|
48
|
-
#
|
49
|
-
|
50
|
-
|
64
|
+
#
|
65
|
+
# @return [Array] Array of speed limits.
|
66
|
+
def speed_limits(place_ids)
|
67
|
+
params = GoogleMapsService::Convert.as_list(place_ids).map { |place_id| ['placeId', place_id] }
|
51
68
|
|
52
|
-
return get(
|
69
|
+
return get('/v1/speedLimits', params,
|
53
70
|
base_url: ROADS_BASE_URL,
|
54
71
|
accepts_client_id: false,
|
55
72
|
custom_response_decoder: method(:extract_roads_body))[:speedLimits]
|
56
73
|
end
|
57
74
|
|
58
|
-
|
59
75
|
# Returns the posted speed limit (in km/h) for given road segments.
|
60
76
|
#
|
61
77
|
# The provided points will first be snapped to the most likely roads the
|
62
78
|
# vehicle was traveling along.
|
63
79
|
#
|
80
|
+
# @example Multi points snap
|
81
|
+
# path = [
|
82
|
+
# [-33.8671, 151.20714],
|
83
|
+
# [-33.86708, 151.20683000000002],
|
84
|
+
# [-33.867070000000005, 151.20674000000002],
|
85
|
+
# [-33.86703, 151.20625]
|
86
|
+
# ]
|
87
|
+
# results = client.snapped_speed_limits(path)
|
88
|
+
#
|
64
89
|
# @param [Hash, Array] path The path of points to be snapped. A list of (or single)
|
65
90
|
# latitude/longitude tuples.
|
66
91
|
#
|
67
|
-
# @return [Hash]
|
92
|
+
# @return [Hash] A hash with both a list of speed limits and a list of the snapped
|
68
93
|
# points.
|
69
|
-
def snapped_speed_limits(path
|
94
|
+
def snapped_speed_limits(path)
|
70
95
|
path = GoogleMapsService::Convert.waypoints(path)
|
71
96
|
|
72
97
|
params = {
|
73
98
|
path: path
|
74
99
|
}
|
75
100
|
|
76
|
-
return get(
|
101
|
+
return get('/v1/speedLimits', params,
|
77
102
|
base_url: ROADS_BASE_URL,
|
78
103
|
accepts_client_id: false,
|
79
104
|
custom_response_decoder: method(:extract_roads_body))
|
@@ -88,33 +113,38 @@ module GoogleMapsService
|
|
88
113
|
unless response.status_code == 200
|
89
114
|
check_response_status_code(response)
|
90
115
|
end
|
91
|
-
raise GoogleMapsService::Error::ApiError.new(response),
|
116
|
+
raise GoogleMapsService::Error::ApiError.new(response), 'Received a malformed response.'
|
92
117
|
end
|
93
118
|
|
94
|
-
|
95
|
-
error = body[:error]
|
96
|
-
status = error[:status]
|
97
|
-
|
98
|
-
case status
|
99
|
-
when 'INVALID_ARGUMENT'
|
100
|
-
if error[:message] == 'The provided API key is invalid.'
|
101
|
-
raise GoogleMapsService::Error::RequestDeniedError.new(response), error[:message]
|
102
|
-
end
|
103
|
-
raise GoogleMapsService::Error::InvalidRequestError.new(response), error[:message]
|
104
|
-
when 'PERMISSION_DENIED'
|
105
|
-
raise GoogleMapsService::Error::RequestDeniedError.new(response), error[:message]
|
106
|
-
when 'RESOURCE_EXHAUSTED'
|
107
|
-
raise GoogleMapsService::Error::RateLimitError.new(response), error[:message]
|
108
|
-
else
|
109
|
-
raise GoogleMapsService::Error::ApiError.new(response), error[:message]
|
110
|
-
end
|
111
|
-
end
|
119
|
+
check_roads_body_error(response, body)
|
112
120
|
|
113
121
|
unless response.status_code == 200
|
114
122
|
raise GoogleMapsService::Error::ApiError.new(response)
|
115
123
|
end
|
116
|
-
|
117
124
|
return body
|
118
125
|
end
|
126
|
+
|
127
|
+
# Check response body for error status.
|
128
|
+
#
|
129
|
+
# @param [Hurley::Response] body Response object.
|
130
|
+
# @param [Hash] body Response body.
|
131
|
+
def check_roads_body_error(response, body)
|
132
|
+
error = body[:error]
|
133
|
+
return unless error
|
134
|
+
|
135
|
+
case error[:status]
|
136
|
+
when 'INVALID_ARGUMENT'
|
137
|
+
if error[:message] == 'The provided API key is invalid.'
|
138
|
+
raise GoogleMapsService::Error::RequestDeniedError.new(response), error[:message]
|
139
|
+
end
|
140
|
+
raise GoogleMapsService::Error::InvalidRequestError.new(response), error[:message]
|
141
|
+
when 'PERMISSION_DENIED'
|
142
|
+
raise GoogleMapsService::Error::RequestDeniedError.new(response), error[:message]
|
143
|
+
when 'RESOURCE_EXHAUSTED'
|
144
|
+
raise GoogleMapsService::Error::RateLimitError.new(response), error[:message]
|
145
|
+
else
|
146
|
+
raise GoogleMapsService::Error::ApiError.new(response), error[:message]
|
147
|
+
end
|
148
|
+
end
|
119
149
|
end
|
120
150
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'date'
|
2
2
|
|
3
|
-
module GoogleMapsService
|
3
|
+
module GoogleMapsService::Apis
|
4
4
|
|
5
5
|
# Performs requests to the Google Maps TimeZone API."""
|
6
6
|
module TimeZone
|
@@ -8,20 +8,25 @@ module GoogleMapsService
|
|
8
8
|
# Get time zone for a location on the earth, as well as that location's
|
9
9
|
# time offset from UTC.
|
10
10
|
#
|
11
|
+
# @example Current time zone
|
12
|
+
# timezone = client.timezone([39.603481, -119.682251])
|
13
|
+
#
|
14
|
+
# @example Time zone at certain time
|
15
|
+
# timezone = client.timezone([39.603481, -119.682251], timestamp: Time.at(1608))
|
16
|
+
#
|
11
17
|
# @param [Hash, Array] location The latitude/longitude value representing the location to
|
12
18
|
# look up.
|
13
19
|
# @param [Integer, DateTime] timestamp Timestamp specifies the desired time as seconds since
|
14
20
|
# midnight, January 1, 1970 UTC. The Time Zone API uses the timestamp to
|
15
21
|
# determine whether or not Daylight Savings should be applied. Times
|
16
22
|
# before 1970 can be expressed as negative values. Optional. Defaults to
|
17
|
-
#
|
23
|
+
# `Time.now`.
|
18
24
|
# @param [String] language The language in which to return results.
|
19
25
|
#
|
20
|
-
# @return [Hash]
|
21
|
-
def timezone(location: nil
|
22
|
-
timestamp: nil, language: nil)
|
26
|
+
# @return [Hash] Time zone object.
|
27
|
+
def timezone(location, timestamp: Time.now, language: nil)
|
23
28
|
location = GoogleMapsService::Convert.latlng(location)
|
24
|
-
timestamp = GoogleMapsService::Convert.time(timestamp
|
29
|
+
timestamp = GoogleMapsService::Convert.time(timestamp)
|
25
30
|
|
26
31
|
params = {
|
27
32
|
location: location,
|
@@ -30,7 +35,7 @@ module GoogleMapsService
|
|
30
35
|
|
31
36
|
params[:language] = language if language
|
32
37
|
|
33
|
-
return get(
|
38
|
+
return get('/maps/api/timezone/json', params)
|
34
39
|
end
|
35
40
|
end
|
36
41
|
end
|
@@ -1,48 +1,52 @@
|
|
1
|
-
require 'uri'
|
2
1
|
require 'hurley'
|
3
2
|
require 'multi_json'
|
4
3
|
require 'retriable'
|
5
4
|
require 'thread'
|
6
5
|
|
6
|
+
require 'google_maps_service/errors'
|
7
|
+
require 'google_maps_service/convert'
|
8
|
+
require 'google_maps_service/url'
|
9
|
+
require 'google_maps_service/apis/directions'
|
10
|
+
require 'google_maps_service/apis/distance_matrix'
|
11
|
+
require 'google_maps_service/apis/elevation'
|
12
|
+
require 'google_maps_service/apis/geocoding'
|
13
|
+
require 'google_maps_service/apis/roads'
|
14
|
+
require 'google_maps_service/apis/time_zone'
|
15
|
+
|
7
16
|
module GoogleMapsService
|
17
|
+
|
18
|
+
# Core client functionality, common across all API requests (including performing
|
19
|
+
# HTTP requests).
|
8
20
|
class Client
|
9
|
-
|
10
|
-
DEFAULT_BASE_URL =
|
21
|
+
# Default Google Maps Web Service base endpoints
|
22
|
+
DEFAULT_BASE_URL = 'https://maps.googleapis.com'
|
23
|
+
|
24
|
+
# Errors those could be retriable.
|
11
25
|
RETRIABLE_ERRORS = [GoogleMapsService::Error::ServerError, GoogleMapsService::Error::RateLimitError]
|
12
26
|
|
13
|
-
include GoogleMapsService::Directions
|
14
|
-
include GoogleMapsService::DistanceMatrix
|
15
|
-
include GoogleMapsService::Elevation
|
16
|
-
include GoogleMapsService::Geocoding
|
17
|
-
include GoogleMapsService::Roads
|
18
|
-
include GoogleMapsService::TimeZone
|
27
|
+
include GoogleMapsService::Apis::Directions
|
28
|
+
include GoogleMapsService::Apis::DistanceMatrix
|
29
|
+
include GoogleMapsService::Apis::Elevation
|
30
|
+
include GoogleMapsService::Apis::Geocoding
|
31
|
+
include GoogleMapsService::Apis::Roads
|
32
|
+
include GoogleMapsService::Apis::TimeZone
|
19
33
|
|
20
34
|
# Secret key for accessing Google Maps Web Service.
|
21
|
-
# Can be obtained at https://developers.google.com/maps/documentation/geocoding/get-api-key#key
|
35
|
+
# Can be obtained at https://developers.google.com/maps/documentation/geocoding/get-api-key#key.
|
22
36
|
# @return [String]
|
23
|
-
|
37
|
+
attr_accessor :key
|
24
38
|
|
25
39
|
# Client id for using Maps API for Work services.
|
26
40
|
# @return [String]
|
27
|
-
|
41
|
+
attr_accessor :client_id
|
28
42
|
|
29
43
|
# Client secret for using Maps API for Work services.
|
30
44
|
# @return [String]
|
31
|
-
|
32
|
-
|
33
|
-
# Connection timeout for HTTP requests, in seconds.
|
34
|
-
# You should specify read_timeout in addition to this option.
|
35
|
-
# @return [Integer]
|
36
|
-
attr_reader :connect_timeout
|
37
|
-
|
38
|
-
# Read timeout for HTTP requests, in seconds.
|
39
|
-
# You should specify connect_timeout in addition to this
|
40
|
-
# @return [Integer]
|
41
|
-
attr_reader :read_timeout
|
45
|
+
attr_accessor :client_secret
|
42
46
|
|
43
47
|
# Timeout across multiple retriable requests, in seconds.
|
44
48
|
# @return [Integer]
|
45
|
-
|
49
|
+
attr_accessor :retry_timeout
|
46
50
|
|
47
51
|
# Number of queries per second permitted.
|
48
52
|
# If the rate limit is reached, the client will sleep for
|
@@ -50,25 +54,77 @@ module GoogleMapsService
|
|
50
54
|
# @return [Integer]
|
51
55
|
attr_reader :queries_per_second
|
52
56
|
|
53
|
-
|
57
|
+
# Construct Google Maps Web Service API client.
|
58
|
+
#
|
59
|
+
# This gem uses [Hurley](https://github.com/lostisland/hurley) as internal HTTP client.
|
60
|
+
# You can configure `Hurley::Client` through `request_options` and `ssl_options` parameters.
|
61
|
+
# You can also directly get the `Hurley::Client` object via {#client} method.
|
62
|
+
#
|
63
|
+
# @example Setup API keys
|
64
|
+
# gmaps = GoogleMapsService::Client.new(key: 'Add your key here')
|
65
|
+
#
|
66
|
+
# @example Setup client IDs
|
67
|
+
# gmaps = GoogleMapsService::Client.new(
|
68
|
+
# client_id: 'Add your client id here',
|
69
|
+
# client_secret: 'Add your client secret here'
|
70
|
+
# )
|
71
|
+
#
|
72
|
+
# @example Setup time out and QPS limit
|
73
|
+
# gmaps = GoogleMapsService::Client.new(
|
74
|
+
# key: 'Add your key here',
|
75
|
+
# retry_timeout: 20,
|
76
|
+
# queries_per_second: 10
|
77
|
+
# )
|
78
|
+
#
|
79
|
+
# @example Request behind proxy
|
80
|
+
# request_options = Hurley::RequestOptions.new
|
81
|
+
# request_options.proxy = Hurley::Url.parse 'http://user:password@proxy.example.com:3128'
|
82
|
+
#
|
83
|
+
# gmaps = GoogleMapsService::Client.new(
|
84
|
+
# key: 'Add your key here',
|
85
|
+
# request_options: request_options
|
86
|
+
# )
|
87
|
+
#
|
88
|
+
# @example Using Excon and Http Cache
|
89
|
+
# require 'hurley-excon' # https://github.com/lostisland/hurley-excon
|
90
|
+
# require 'hurley/http_cache' # https://github.com/plataformatec/hurley-http-cache
|
91
|
+
#
|
92
|
+
# gmaps = GoogleMapsService::Client.new(
|
93
|
+
# key: 'Add your key here',
|
94
|
+
# connection: Hurley::HttpCache.new(HurleyExcon::Connection.new)
|
95
|
+
# )
|
96
|
+
#
|
97
|
+
#
|
98
|
+
#
|
99
|
+
# @option options [String] :key Secret key for accessing Google Maps Web Service.
|
100
|
+
# Can be obtained at https://developers.google.com/maps/documentation/geocoding/get-api-key#key.
|
101
|
+
# @option options [String] :client_id Client id for using Maps API for Work services.
|
102
|
+
# @option options [String] :client_secret Client secret for using Maps API for Work services.
|
103
|
+
# @option options [Integer] :retry_timeout Timeout across multiple retriable requests, in seconds.
|
104
|
+
# @option options [Integer] :queries_per_second Number of queries per second permitted.
|
105
|
+
#
|
106
|
+
# @option options [Hurley::RequestOptions] :request_options HTTP client request options.
|
107
|
+
# See https://github.com/lostisland/hurley/blob/master/lib/hurley/options.rb.
|
108
|
+
# @option options [Hurley::SslOptions] :ssl_options HTTP client SSL options.
|
109
|
+
# See https://github.com/lostisland/hurley/blob/master/lib/hurley/options.rb.
|
110
|
+
# @option options [Object] :connection HTTP client connection.
|
111
|
+
# By default, the default Hurley's HTTP client connection (Net::Http) will be used.
|
112
|
+
# See https://github.com/lostisland/hurley/blob/master/README.md#connections.
|
113
|
+
def initialize(**options)
|
54
114
|
@key = options[:key] || GoogleMapsService.key
|
55
115
|
@client_id = options[:client_id] || GoogleMapsService.client_id
|
56
116
|
@client_secret = options[:client_secret] || GoogleMapsService.client_secret
|
57
|
-
@connect_timeout = options[:connect_timeout] || GoogleMapsService.connect_timeout
|
58
|
-
@read_timeout = options[:read_timeout] || GoogleMapsService.read_timeout
|
59
117
|
@retry_timeout = options[:retry_timeout] || GoogleMapsService.retry_timeout || 60
|
60
118
|
@queries_per_second = options[:queries_per_second] || GoogleMapsService.queries_per_second
|
119
|
+
@request_options = options[:request_options] || GoogleMapsService.request_options
|
120
|
+
@ssl_options = options[:ssl_options] || GoogleMapsService.ssl_options
|
121
|
+
@connection = options[:connection] || GoogleMapsService.connection
|
61
122
|
|
62
|
-
#
|
63
|
-
if @queries_per_second
|
64
|
-
@sent_times = SizedQueue.new @queries_per_second
|
65
|
-
@queries_per_second.times do
|
66
|
-
@sent_times << 0
|
67
|
-
end
|
68
|
-
end
|
123
|
+
#
|
124
|
+
initialize_qps if @queries_per_second
|
69
125
|
end
|
70
126
|
|
71
|
-
# Get the current HTTP client
|
127
|
+
# Get the current HTTP client.
|
72
128
|
# @return [Hurley::Client]
|
73
129
|
def client
|
74
130
|
@client ||= new_client
|
@@ -76,92 +132,68 @@ module GoogleMapsService
|
|
76
132
|
|
77
133
|
protected
|
78
134
|
|
79
|
-
#
|
135
|
+
# Initialize QPS queue. QPS queue is a "tickets" for calling API
|
136
|
+
def initialize_qps
|
137
|
+
@qps_queue = SizedQueue.new @queries_per_second
|
138
|
+
@queries_per_second.times do
|
139
|
+
@qps_queue << 0
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Create a new HTTP client.
|
80
144
|
# @return [Hurley::Client]
|
81
145
|
def new_client
|
82
146
|
client = Hurley::Client.new
|
147
|
+
|
148
|
+
client.connection = @connection if @connection
|
149
|
+
@request_options.each_pair {|key, value| client.request_options[key] = value } if @request_options
|
150
|
+
@ssl_options.each_pair {|key, value| client.ssl_options[key] = value } if @ssl_options
|
151
|
+
|
83
152
|
client.request_options.query_class = Hurley::Query::Flat
|
84
|
-
client.
|
85
|
-
client.request_options.open_timeout = @connect_timeout if @connect_timeout
|
86
|
-
client.header[:user_agent] = USER_AGENT
|
153
|
+
client.header[:user_agent] = user_agent
|
87
154
|
client
|
88
155
|
end
|
89
156
|
|
157
|
+
# Build the user agent header
|
158
|
+
# @return [String]
|
159
|
+
def user_agent
|
160
|
+
sprintf('google-maps-services-ruby/%s %s',
|
161
|
+
GoogleMapsService::VERSION,
|
162
|
+
GoogleMapsService::OS_VERSION)
|
163
|
+
end
|
164
|
+
|
165
|
+
# Make API call.
|
166
|
+
#
|
167
|
+
# @param [String] path Url path.
|
168
|
+
# @param [String] params Request parameters.
|
169
|
+
# @param [String] base_url Base Google Maps Web Service API endpoint url.
|
170
|
+
# @param [Boolean] accepts_client_id Sign the request using API {#keys} instead of {#client_id}.
|
171
|
+
# @param [Method] custom_response_decoder Custom method to decode raw API response.
|
172
|
+
#
|
173
|
+
# @return [Object] Decoded response body.
|
90
174
|
def get(path, params, base_url: DEFAULT_BASE_URL, accepts_client_id: true, custom_response_decoder: nil)
|
91
175
|
url = base_url + generate_auth_url(path, params, accepts_client_id)
|
92
176
|
|
93
177
|
Retriable.retriable timeout: @retry_timeout, on: RETRIABLE_ERRORS do |try|
|
94
178
|
# Get/wait the request "ticket" if QPS is configured
|
95
179
|
# Check for previous request time, it must be more than a second ago before calling new request
|
96
|
-
if @
|
97
|
-
elapsed_since_earliest = Time.now - @
|
180
|
+
if @qps_queue
|
181
|
+
elapsed_since_earliest = Time.now - @qps_queue.pop
|
98
182
|
sleep(1 - elapsed_since_earliest) if elapsed_since_earliest.to_f < 1
|
99
183
|
end
|
100
184
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
185
|
+
begin
|
186
|
+
response = client.get url
|
187
|
+
ensure
|
188
|
+
# Release request "ticket"
|
189
|
+
@qps_queue << Time.now if @qps_queue
|
190
|
+
end
|
105
191
|
|
106
192
|
return custom_response_decoder.call(response) if custom_response_decoder
|
107
193
|
decode_response_body(response)
|
108
194
|
end
|
109
195
|
end
|
110
196
|
|
111
|
-
# Extract and parse body response as hash. Throw an error if there is something wrong with the response.
|
112
|
-
#
|
113
|
-
# @param [Hurley::Response] response Web API response.
|
114
|
-
#
|
115
|
-
# @return [Hash] Response body as hash. The hash key will be symbolized.
|
116
|
-
#
|
117
|
-
# @raise [GoogleMapsService::Error::RedirectError] The response redirects to another URL.
|
118
|
-
# @raise [GoogleMapsService::Error::RequestDeniedError] The credential (key or client id pair) is not valid.
|
119
|
-
# @raise [GoogleMapsService::Error::ClientError] The request is invalid and should not be retried without modification.
|
120
|
-
# @raise [GoogleMapsService::Error::ServerError] An error occurred on the server and the request can be retried.
|
121
|
-
# @raise [GoogleMapsService::Error::TransmissionError] Unknown response status code.
|
122
|
-
# @raise [GoogleMapsService::Error::RateLimitError] The quota for the credential is already pass the limit.
|
123
|
-
# @raise [GoogleMapsService::Error::ApiError] The Web API error.
|
124
|
-
def decode_response_body(response)
|
125
|
-
check_response_status_code(response)
|
126
|
-
|
127
|
-
body = MultiJson.load(response.body, :symbolize_keys => true)
|
128
|
-
|
129
|
-
case body[:status]
|
130
|
-
when 'OK', 'ZERO_RESULTS'
|
131
|
-
return body
|
132
|
-
when 'OVER_QUERY_LIMIT'
|
133
|
-
raise GoogleMapsService::Error::RateLimitError.new(response), body[:error_message]
|
134
|
-
when 'REQUEST_DENIED'
|
135
|
-
raise GoogleMapsService::Error::RequestDeniedError.new(response), body[:error_message]
|
136
|
-
when 'INVALID_REQUEST'
|
137
|
-
raise GoogleMapsService::Error::InvalidRequestError.new(response), body[:error_message]
|
138
|
-
else
|
139
|
-
raise GoogleMapsService::Error::ApiError.new(response), body[:error_message]
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
def check_response_status_code(response)
|
144
|
-
case response.status_code
|
145
|
-
when 200..300
|
146
|
-
# Do-nothing
|
147
|
-
when 301, 302, 303, 307
|
148
|
-
message = sprintf('Redirect to %s', response.header[:location])
|
149
|
-
raise GoogleMapsService::Error::RedirectError.new(response), message
|
150
|
-
when 401
|
151
|
-
message = 'Unauthorized'
|
152
|
-
raise GoogleMapsService::Error::ClientError.new(response), message
|
153
|
-
when 304, 400, 402...500
|
154
|
-
message = 'Invalid request'
|
155
|
-
raise GoogleMapsService::Error::ClientError.new(response), message
|
156
|
-
when 500..600
|
157
|
-
message = 'Server error'
|
158
|
-
raise GoogleMapsService::Error::ServerError.new(response), message
|
159
|
-
else
|
160
|
-
message = 'Unknown error'
|
161
|
-
raise GoogleMapsService::Error::Error.new(response), message
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
197
|
# Returns the path and query string portion of the request URL,
|
166
198
|
# first adding any necessary parameters.
|
167
199
|
#
|
@@ -181,83 +213,68 @@ module GoogleMapsService
|
|
181
213
|
if accepts_client_id and @client_id and @client_secret
|
182
214
|
params << ["client", @client_id]
|
183
215
|
|
184
|
-
path = [path,
|
185
|
-
sig =
|
216
|
+
path = [path, GoogleMapsService::Url.urlencode_params(params)].join("?")
|
217
|
+
sig = GoogleMapsService::Url.sign_hmac(@client_secret, path)
|
186
218
|
return path + "&signature=" + sig
|
187
219
|
end
|
188
220
|
|
189
221
|
if @key
|
190
222
|
params << ["key", @key]
|
191
|
-
return path + "?" +
|
223
|
+
return path + "?" + GoogleMapsService::Url.urlencode_params(params)
|
192
224
|
end
|
193
225
|
|
194
226
|
raise ArgumentError, "Must provide API key for this API. It does not accept enterprise credentials."
|
195
227
|
end
|
196
228
|
|
197
|
-
#
|
229
|
+
# Extract and parse body response as hash. Throw an error if there is something wrong with the response.
|
198
230
|
#
|
199
|
-
# @param [
|
200
|
-
# @param [String] payload The payload to sign.
|
231
|
+
# @param [Hurley::Response] response Web API response.
|
201
232
|
#
|
202
|
-
# @return [
|
203
|
-
def
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
secret = secret.encode('ASCII')
|
209
|
-
payload = payload.encode('ASCII')
|
210
|
-
|
211
|
-
# Decode the private key
|
212
|
-
raw_key = Base64.urlsafe_decode64(secret)
|
213
|
-
|
214
|
-
# Create a signature using the private key and the URL
|
215
|
-
sha1 = HMAC::SHA1.new(raw_key)
|
216
|
-
sha1 << payload
|
217
|
-
raw_signature = sha1.digest()
|
218
|
-
|
219
|
-
# Encode the signature into base64 for url use form.
|
220
|
-
signature = Base64.urlsafe_encode64(raw_signature)
|
221
|
-
return signature
|
233
|
+
# @return [Hash] Response body as hash. The hash key will be symbolized.
|
234
|
+
def decode_response_body(response)
|
235
|
+
check_response_status_code(response)
|
236
|
+
body = MultiJson.load(response.body, :symbolize_keys => true)
|
237
|
+
check_body_error(response, body)
|
238
|
+
body
|
222
239
|
end
|
223
240
|
|
224
|
-
#
|
225
|
-
#
|
226
|
-
# @
|
227
|
-
def
|
228
|
-
|
241
|
+
# Check HTTP response status code. Raise error if the status is not 2xx.
|
242
|
+
#
|
243
|
+
# @param [Hurley::Response] response Web API response.
|
244
|
+
def check_response_status_code(response)
|
245
|
+
case response.status_code
|
246
|
+
when 200..300
|
247
|
+
# Do-nothing
|
248
|
+
when 301, 302, 303, 307
|
249
|
+
raise GoogleMapsService::Error::RedirectError.new(response), sprintf('Redirect to %s', response.header[:location])
|
250
|
+
when 401
|
251
|
+
raise GoogleMapsService::Error::ClientError.new(response), 'Unauthorized'
|
252
|
+
when 304, 400, 402...500
|
253
|
+
raise GoogleMapsService::Error::ClientError.new(response), 'Invalid request'
|
254
|
+
when 500..600
|
255
|
+
raise GoogleMapsService::Error::ServerError.new(response), 'Server error'
|
256
|
+
else
|
257
|
+
raise ArgumentError, 'Invalid response status code'
|
258
|
+
end
|
229
259
|
end
|
230
260
|
|
231
|
-
#
|
232
|
-
UNRESERVED_SET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"
|
233
|
-
|
234
|
-
# Un-escape any percent-escape sequences in a URI that are unreserved
|
235
|
-
# characters. This leaves all reserved, illegal and non-ASCII bytes encoded.
|
261
|
+
# Check response body for error status.
|
236
262
|
#
|
237
|
-
# @param [
|
238
|
-
#
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
else
|
252
|
-
parts[i] = "%#{parts[i]}"
|
253
|
-
end
|
254
|
-
else
|
255
|
-
parts[i] = "%#{parts[i]}"
|
256
|
-
end
|
263
|
+
# @param [Hurley::Response] body Response object.
|
264
|
+
# @param [Hash] body Response body.
|
265
|
+
def check_body_error(response, body)
|
266
|
+
case body[:status]
|
267
|
+
when 'OK', 'ZERO_RESULTS'
|
268
|
+
# Do-nothing
|
269
|
+
when 'OVER_QUERY_LIMIT'
|
270
|
+
raise GoogleMapsService::Error::RateLimitError.new(response), body[:error_message]
|
271
|
+
when 'REQUEST_DENIED'
|
272
|
+
raise GoogleMapsService::Error::RequestDeniedError.new(response), body[:error_message]
|
273
|
+
when 'INVALID_REQUEST'
|
274
|
+
raise GoogleMapsService::Error::InvalidRequestError.new(response), body[:error_message]
|
275
|
+
else
|
276
|
+
raise GoogleMapsService::Error::ApiError.new(response), body[:error_message]
|
257
277
|
end
|
258
|
-
|
259
|
-
return parts.join
|
260
278
|
end
|
261
|
-
|
262
279
|
end
|
263
280
|
end
|