google_maps_service 0.3.0 → 0.4.0
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/.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
|