googlemaps-services 1.2.5 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 18a00c19fa244488e45d19b1072506587f597e57
4
- data.tar.gz: 1d4bf5cef38c7d6be03bd438d2e30278eef31ee3
3
+ metadata.gz: f9af6339c46954459057f1f720aa53f4275158c2
4
+ data.tar.gz: e167756c771106d77778d36284960c429cb9105f
5
5
  SHA512:
6
- metadata.gz: 99aa38b29add2f487377ceef2a6cba9e9881defa8c2b6c3a0303a69bdbee8c2cfa4fd94414dda0d40fa2a4bd6b3f5a15b155dea9e66121862cd8d252a4aad270
7
- data.tar.gz: da2efaedd9b47501bc815b005ae47e7a2f97552fcfba47bf61c89b84d5c8e64309ad4b99426cf6b30eedf6fbfc38b77cf8190a6e90abc3fc20b70c1b7b59f781
6
+ metadata.gz: dba8b6e7c27f3ddd0e90ef28d981930ab97c148a7202b3b98862a0bd18d8c80467b15df9751bb17827f596dbc3592eca30a0c988e5e7fdb31f1ce989f13d5c22
7
+ data.tar.gz: ab24f530bae29cf059851bfcea3682766b693909addb9dfa16e37e7d1379be0c4f8a59eecb1c271bb2cef2b0fee89ab5e5c28b2439b0ee9f5c7c5895c6f58b9e
@@ -1,9 +1,11 @@
1
+ # coding: utf-8
1
2
  require 'googlemaps/services/exceptions'
2
3
  require 'googlemaps/services/version'
3
4
  require 'googlemaps/services/util'
4
5
  require 'nokogiri'
5
- require 'net/http'
6
+ require 'base64'
6
7
  require 'json'
8
+ require 'http'
7
9
 
8
10
  # Core functionality, common across all API requests.
9
11
  #
@@ -13,28 +15,33 @@ module GoogleMaps
13
15
  #
14
16
  # @since 1.0.0
15
17
  module Services
16
- USER_AGENT = 'GoogleMapsRubyClient/' + VERSION
17
- DEFAULT_BASE_URL = 'https://maps.googleapis.com'
18
- RETRIABLE_STATUSES = [500, 503, 504]
19
18
 
20
19
  # Performs requests to the Google Maps API web services.
21
20
  class GoogleClient
21
+ USER_AGENT = 'GoogleMapsRubyClient/' + VERSION
22
+ DEFAULT_BASE_URL = 'https://maps.googleapis.com'
23
+ RETRIABLE_STATUSES = [500, 503, 504]
24
+
22
25
  include GoogleMaps::Services::Exceptions
23
26
 
24
27
  # @return [Symbol] API key. Required, unless "client_id" and "client_secret" are set.
25
28
  attr_accessor :key
26
- # @return [Symbol] timeout Combined connect and read timeout for HTTP requests, in seconds.
27
- attr_accessor :timeout
29
+ # @return [Symbol] Write timeout for the HTTP request, in seconds.
30
+ attr_accessor :write_timeout
31
+ # @return [Symbol] Connect timeout for the HTTP request, in seconds.
32
+ attr_accessor :connect_timeout
33
+ # @return [Symbol] Read timeout for the HTTP request, in seconds.
34
+ attr_accessor :read_timeout
28
35
  # @return [Symbol] Client ID (for Maps API for Work).
29
36
  attr_accessor :client_id
30
- # @return [Symbol] base64-encoded client secret (for Maps API for Work).
37
+ # @return [Symbol] Base64-encoded client secret (for Maps API for Work).
31
38
  attr_accessor :client_secret
32
- # @return [Symbol] attribute used for tracking purposes. Can only be used with a Client ID.
39
+ # @return [Symbol] Attribute used for tracking purposes. Can only be used with a Client ID.
33
40
  attr_accessor :channel
34
- # @return [Symbol] timeout across multiple retriable requests, in seconds.
41
+ # @return [Symbol] Timeout across multiple retriable requests, in seconds.
35
42
  attr_accessor :retry_timeout
36
- # @return [Symbol] extra options for Net::HTTP client.
37
- attr_accessor :request_opts
43
+ # @return [Symbol] HTTP headers per request.
44
+ attr_accessor :request_headers
38
45
  # @return [Symbol] number of queries per second permitted. If the rate limit is reached, the client will sleep for the appropriate amout of time before it runs the current query.
39
46
  attr_accessor :queries_per_second
40
47
  # @return [Symbol] keeps track of sent queries.
@@ -42,10 +49,10 @@ module GoogleMaps
42
49
  # @return [Symbol] Response format. Either :json or :xml
43
50
  attr_accessor :response_format
44
51
 
45
- def initialize(key:, client_id: nil, client_secret: nil, timeout: nil,
46
- connect_timeout: nil, read_timeout: nil,retry_timeout: 60, request_opts: {},
52
+ def initialize(key: nil, client_id: nil, client_secret: nil, write_timeout: 1,
53
+ connect_timeout: 1, read_timeout: 1,retry_timeout: 60, request_headers: {},
47
54
  queries_per_second: 10, channel: nil, response_format: :json)
48
- if !key && !(client_secret && client_id)
55
+ unless key || (client_secret && client_id)
49
56
  raise StandardError, 'Must provide API key or enterprise credentials when creationg client.'
50
57
  end
51
58
 
@@ -54,9 +61,8 @@ module GoogleMaps
54
61
  end
55
62
 
56
63
  if channel
57
- unless client_id
58
- raise StandardError, 'The channel argument must be used with a client ID.'
59
- end
64
+ raise StandardError, 'The channel argument must be used with a client ID.' unless client_id
65
+
60
66
 
61
67
  unless /^[a-zA-Z0-9._-]*$/.match(channel)
62
68
  raise StandardError, 'The channel argument must be an ASCII alphanumeric string. The period (.), underscore (_) and hyphen (-) characters are allowed.'
@@ -65,25 +71,16 @@ module GoogleMaps
65
71
 
66
72
  self.key = key
67
73
 
68
- if timeout && (connect_timeout || read_timeout)
69
- raise StandardError, 'Specify either timeout, or connect_timeout and read_timeout.'
70
- end
71
-
72
- if connect_timeout && read_timeout
73
- self.timeout = { :connect_timeout => connect_timeout, :read_timeout => read_timeout }
74
- else
75
- self.timeout = timeout
76
- end
74
+ # Set the timeout for write/connect/read calls
75
+ self.write_timeout = write_timeout
76
+ self.connect_timeout = connect_timeout
77
+ self.read_timeout = read_timeout
77
78
 
78
79
  self.client_id = client_id
79
80
  self.client_secret = client_secret
80
81
  self.channel = channel
81
82
  self.retry_timeout = retry_timeout
82
- self.request_opts = request_opts.merge({
83
- :headers => {'User-Agent' => USER_AGENT},
84
- :timeout => self.timeout,
85
- :verify => true
86
- })
83
+ self.request_headers = request_headers.merge({ 'User-Agent' => USER_AGENT })
87
84
  self.queries_per_second = queries_per_second
88
85
  self.sent_times = Array.new
89
86
 
@@ -93,7 +90,7 @@ module GoogleMaps
93
90
  end
94
91
  end
95
92
 
96
- # Performs HTTP GET requests with credentials, returning the body as JSON or XML
93
+ # Performs HTTP GET requests with credentials, returning the body as JSON or XML.
97
94
  #
98
95
  # @param [String] url URL path for the request. Should begin with a slash.
99
96
  # @param [Hash] params HTTP GET parameters.
@@ -102,12 +99,12 @@ module GoogleMaps
102
99
  # @param [String] base_url The base URL for the request. Defaults to the Google Maps API server. Should not have a trailing slash.
103
100
  # @param [TrueClass, FalseClass] accepts_clientid Flag whether this call supports the client/signature params. Some APIs require API keys (e.g. Roads).
104
101
  # @param [Proc] extract_body A function that extracts the body from the request. If the request was not successful, the function should raise a
105
- # GoogleMaps::Services::Exceptions::HTTËrror or GoogleMaps::Services::Exceptions::APIError as appropriate.
106
- # @param [Hash] request_opts Additional options for the Net::HTTP client.
102
+ # GoogleMaps::Services::Exceptions::HTTPError or GoogleMaps::Services::Exceptions::APIError as appropriate.
103
+ # @param [Hash] request_headers HTTP headers per request.
107
104
  #
108
- # @return [Hash, Array] response body, either in JSON or XML.
105
+ # @return [Hash, Array, nil] response body (either in JSON or XML) or nil.
109
106
  def get(url:, params:, first_request_time: nil, retry_counter: nil, base_url: DEFAULT_BASE_URL,
110
- accepts_clientid: true, extract_body: nil, request_opts: nil)
107
+ accepts_clientid: true, extract_body: nil, request_headers: nil)
111
108
  unless first_request_time
112
109
  first_request_time = Util.current_time
113
110
  end
@@ -128,35 +125,21 @@ module GoogleMaps
128
125
 
129
126
  authed_url = generate_auth_url(url, params, accepts_clientid)
130
127
 
131
- # Default to the client-level self.request_opts, with method-level
132
- # request_opts arg overriding.
133
- request_opts = self.request_opts.merge(request_opts || {})
128
+ # Default to the client-level self.request_headers, with method-level
129
+ # request_headers arg overriding.
130
+ request_headers = self.request_headers.merge(request_headers || {})
134
131
 
135
132
  # Construct the Request URI
136
- uri = URI.parse(base_url + authed_url)
137
-
138
- # Add request headers
139
- req = Net::HTTP::Get.new(uri.to_s)
140
-
141
- request_opts[:headers].each { |header,value| req.add_field(header, value) }
133
+ uri = HTTP::URI.parse(base_url + authed_url)
142
134
 
143
- http = Net::HTTP.new(uri.host, uri.port)
144
- http.use_ssl = (uri.scheme == 'https')
145
- # Get HTTP response
146
- resp = http.request(req)
147
-
148
- # Handle response errors
149
- case resp
150
- when Net::HTTPRequestTimeOut
151
- raise Timeout
152
- when Exception
153
- raise TransportError, 'HTTP GET request failed.'
154
- end
135
+ # Create the request, add the headers and make the GET request
136
+ resp = HTTP.headers(request_headers)
137
+ .timeout(:write => self.write_timeout, :connect => self.connect_timeout, :read => self.read_timeout)
138
+ .get(uri.to_s)
155
139
 
156
140
  if RETRIABLE_STATUSES.include? resp.code.to_i
157
141
  # Retry request
158
- self.get(url, params, first_request_time, retry_counter + 1,
159
- base_url, accepts_clientid, extract_body)
142
+ self.get(url, params, first_request_time, retry_counter + 1, base_url, accepts_clientid, extract_body)
160
143
  end
161
144
 
162
145
  # Check if the time of the nth previous query (where n is queries_per_second)
@@ -173,11 +156,15 @@ module GoogleMaps
173
156
  if extract_body
174
157
  result = extract_body.call(resp)
175
158
  else
176
- case self.response_format
177
- when :xml # XML response
159
+ case resp.content_type.mime_type
160
+ when 'application/xml'
178
161
  result = get_xml_body(resp)
179
- else # JSON response
162
+ when 'application/json'
180
163
  result = get_json_body(resp)
164
+ when 'text/html'
165
+ result = get_redirection_url(resp)
166
+ else
167
+ result = get_map_image(resp)
181
168
  end
182
169
  end
183
170
  self.sent_times.push(Util.current_time)
@@ -188,6 +175,18 @@ module GoogleMaps
188
175
  end
189
176
  end
190
177
 
178
+ # Returns the redirection URL from the Response in case of 3XX status code.
179
+ #
180
+ # @private
181
+ #
182
+ # @param [Net::HTTPResponse] resp HTTP response object.
183
+ #
184
+ # @return [String] Redirection URL.
185
+ def get_redirection_url(resp)
186
+ status_code = resp.code.to_i
187
+ (status_code >= 300 && status_code < 400) ? resp['location'] : nil
188
+ end
189
+
191
190
  # Extracts the JSON body of the HTTP response.
192
191
  #
193
192
  # @private
@@ -197,15 +196,12 @@ module GoogleMaps
197
196
  # @return [Hash, Array] Valid JSON response.
198
197
  def get_json_body(resp)
199
198
  status_code = resp.code.to_i
200
- if status_code >= 300 && status_code < 400
201
- return resp['location']
202
- end
203
199
 
204
200
  if status_code != 200
205
- raise HTTPError.new(resp.code)
201
+ raise HTTPError.new(status_code)
206
202
  end
207
203
 
208
- # Parse the response body
204
+ # Parse the JSON response body
209
205
  begin
210
206
  body = JSON.parse(resp.body)
211
207
  rescue JSON::ParserError
@@ -237,16 +233,14 @@ module GoogleMaps
237
233
  # @return [Nokogiri::XML::Document] Valid XML document.
238
234
  def get_xml_body(resp)
239
235
  status_code = resp.code.to_i
240
- if status_code >= 300 && status_code < 400
241
- return resp['location']
242
- end
243
236
 
244
237
  if status_code != 200
245
- raise HTTPError.new(resp.code)
238
+ raise HTTPError.new(status_code)
246
239
  end
247
240
 
241
+ # Parse the XML response body
248
242
  begin
249
- doc = Nokogiri::XML.parse(resp.body)
243
+ doc = Nokogiri::XML(resp.body) { |config| config.strict }
250
244
  rescue
251
245
  raise APIError.new(status_code), 'Received a malformed XML response.'
252
246
  end
@@ -268,6 +262,27 @@ module GoogleMaps
268
262
  end
269
263
  end
270
264
 
265
+ # Extracts the static map image from the HTTP response.
266
+ #
267
+ # @private
268
+ #
269
+ # @param [Net::HTTPResponse] resp HTTP response object.
270
+ #
271
+ # @return [Hash] Hash with image URL, MIME type and its base64-encoded value.
272
+ def get_map_image(resp)
273
+ status_code = resp.code.to_i
274
+
275
+ if status_code != 200
276
+ raise HTTPError.new(status_code)
277
+ end
278
+
279
+ {
280
+ :url => resp.uri.to_s,
281
+ :mime_type => resp.content_type.mime_type,
282
+ :image_data => Base64.encode64(resp.body).gsub(/\n/, '')
283
+ }
284
+ end
285
+
271
286
  # Returns the path and query string portion of the request URL, first adding any necessary parameters.
272
287
  #
273
288
  # @private
@@ -297,7 +312,7 @@ module GoogleMaps
297
312
  raise StandardError, 'Must provide API key for this API. It does not accept enterprise credentials.'
298
313
  end
299
314
 
300
- private :get_json_body, :get_xml_body, :generate_auth_url
315
+ private :get_json_body, :get_xml_body, :get_map_image, :get_redirection_url, :generate_auth_url
301
316
  end
302
317
 
303
318
  end
@@ -2,14 +2,15 @@ require 'googlemaps/services/util'
2
2
 
3
3
  module GoogleMaps
4
4
  module Services
5
- TRAVEL_MODES = %w(driving walking bicycling transit)
6
-
7
5
  # Performs requests to the Google Maps Directions API.
8
6
  #
9
7
  # @example
10
8
  # directions = GoogleMaps::Services::Directions.new(client)
11
9
  # result = directions.query(origin: "Brussels", destination: {:lat => 52.520645, :lng => 13.409779})
12
10
  class Directions
11
+ TRAVEL_MODES = %w(driving walking bicycling transit)
12
+ AVOID_FEATURES = %w(tolls highways ferries indoor)
13
+
13
14
  # @return [Symbol] The HTTP client.
14
15
  attr_accessor :client
15
16
 
@@ -72,6 +73,9 @@ module GoogleMaps
72
73
  end
73
74
 
74
75
  if avoid
76
+ unless ArrayBox.contains_all?(AVOID_FEATURES, avoid)
77
+ raise StandardError, 'invalid avoid feature.'
78
+ end
75
79
  params['avoid'] = Convert.join_array('|', avoid)
76
80
  end
77
81
 
@@ -114,8 +118,10 @@ module GoogleMaps
114
118
  case self.client.response_format
115
119
  when :xml
116
120
  self.client.get(url: '/maps/api/directions/xml', params: params).xpath('//route')
117
- else
121
+ when :json
118
122
  self.client.get(url: '/maps/api/directions/json', params: params)['routes']
123
+ else
124
+ raise StandardError, 'Unsupported response format. Should be either :json or :xml.'
119
125
  end
120
126
  end
121
127
  end
@@ -2,14 +2,15 @@ require 'googlemaps/services/util'
2
2
 
3
3
  module GoogleMaps
4
4
  module Services
5
- AVOIDS = %w(tolls highways ferries)
6
-
7
5
  # Performs requests to the Google Maps Distance Matrix API.
8
6
  #
9
7
  # @example
10
- # distancematrix = GoogleMaps::Services::DistanceMatrix(client)
8
+ # distancematrix = GoogleMaps::Services::DistanceMatrix.new(client)
11
9
  # result = distancematrix.query(origins: ["Brussels", "Ghent"], destinations: ["Bruges"])
12
10
  class DistanceMatrix
11
+ AVOIDS = %w(tolls highways ferries)
12
+ TRAVEL_MODES = %w(driving walking bicycling transit)
13
+
13
14
  # @return [Symbol] the HTTP client.
14
15
  attr_accessor :client
15
16
 
@@ -7,9 +7,11 @@ module GoogleMaps
7
7
  # Performs requests to the Google Maps Elevation API.
8
8
  #
9
9
  # @example
10
- # elevation = GoogleMaps::Services::Elevation(client)
10
+ # elevation = GoogleMaps::Services::Elevation.new(client)
11
11
  # result = elevation.query(locations: [{:lat => 52.520645, :lng => 13.409779}, "Brussels"])
12
12
  class Elevation
13
+
14
+ # @return [Symbol] The HTTP client.
13
15
  attr_accessor :client
14
16
 
15
17
  def initialize(client)
@@ -25,22 +27,17 @@ module GoogleMaps
25
27
  # @param [Integer] samples The number of sample points along a path for which to return elevation data.
26
28
  #
27
29
  # @return [Array, Nokogiri::XML::NodeSet] Valid JSON or XML response.
28
- def query(locations: [], path: nil, samples: 0)
30
+ def query(locations: nil, path: nil, samples: 0)
29
31
  params = {}
30
32
 
31
- if path && locations
32
- raise StandardError, 'Should not specify both path and locations.'
33
- end
34
-
35
33
  if locations
36
34
  params['locations'] = Convert.shortest_path(locations)
37
35
  end
38
36
 
39
37
  if path
40
- case path.class
41
- when String
38
+ if path.instance_of? String
42
39
  path = "enc:#{path}"
43
- when Array
40
+ elsif path.instance_of? Array
44
41
  path = Convert.shortest_path(path)
45
42
  else
46
43
  raise TypeError, 'Path should be either a String or an Array.'
@@ -49,11 +46,17 @@ module GoogleMaps
49
46
  params = {'path' => path, 'samples' => samples }
50
47
  end
51
48
 
49
+ if path && locations
50
+ raise StandardError, 'Should not specify both path and locations.'
51
+ end
52
+
52
53
  case self.client.response_format
53
54
  when :xml
54
55
  self.client.get(url: '/maps/api/elevation/xml', params: params).xpath('//result')
55
- else
56
+ when :json
56
57
  self.client.get(url: '/maps/api/elevation/json', params: params)['results']
58
+ else
59
+ raise StandardError, 'Unsupported response format. Should be either :json or :xml.'
57
60
  end
58
61
  end
59
62
  end
@@ -56,8 +56,10 @@ module GoogleMaps
56
56
  case self.client.response_format
57
57
  when :xml
58
58
  self.client.get(url: '/maps/api/geocode/xml', params: params).xpath('//result')
59
- else
59
+ when :json
60
60
  self.client.get(url: '/maps/api/geocode/json', params: params)['results']
61
+ else
62
+ raise StandardError, 'Unsupported response format. Should be either :json or :xml.'
61
63
  end
62
64
  end
63
65
  end
@@ -85,7 +87,7 @@ module GoogleMaps
85
87
  def query(latlng:, result_type: nil, location_type: nil, language: nil)
86
88
  # Check if latlng param is a place_id string.
87
89
  # 'place_id' strings do not contain commas; latlng strings do.
88
- if latlng.is_a?(String) && !latlng.include?("'")
90
+ if latlng.is_a?(String) && !latlng.include?(",")
89
91
  params = {'place_id' => latlng}
90
92
  else
91
93
  params = {'latlng' => Convert.to_latlng(latlng)}
@@ -106,8 +108,10 @@ module GoogleMaps
106
108
  case self.client.response_format
107
109
  when :xml
108
110
  self.client.get(url: '/maps/api/geocode/xml', params: params).xpath('//result')
109
- else
111
+ when :json
110
112
  self.client.get(url: '/maps/api/geocode/json', params: params)['results']
113
+ else
114
+ raise StandardError, 'Unsupported response format. Should be either :json or :xml.'
111
115
  end
112
116
  end
113
117
  end
@@ -62,7 +62,7 @@ module GoogleMaps
62
62
  type: type, page_token: page_token)
63
63
  end
64
64
 
65
- # Performs radar search for places
65
+ # Performs radar search for places.
66
66
  #
67
67
  # @param [String, Hash] location The latitude/longitude value for which you wish to obtain the closest, human-readable address.
68
68
  # @param [Integer] radius Distance in meters within which to bias results.
@@ -71,14 +71,12 @@ module GoogleMaps
71
71
  # @param [Integer] max_price Restricts results to only those places with no greater than this price level. Valid values are in the range from 0 (most affordable) to 4 (most expensive).
72
72
  # @param [Array] name One or more terms to be matched against the names of places.
73
73
  # @param [TrueClass, FalseClass] open_now Return only those places that are open for business at the time the query is sent.
74
- # @param [String] type: Restricts the results to places matching the specified type. The full list of supported types is available here: https://developers.google.com/places/supported_types
74
+ # @param [String] type Restricts the results to places matching the specified type. The full list of supported types is available here: https://developers.google.com/places/supported_types
75
75
  #
76
76
  # @return [Hash, Nokogiri::XML::Document] Valid JSON or XML response.
77
77
  def radar(location:, radius:, keyword: nil, min_price: nil,
78
78
  max_price: nil, name: nil, open_now: false, type: nil)
79
- unless keyword || name || type
80
- raise StandardError, 'either a keyword, name, or type arg is required.'
81
- end
79
+ raise StandardError, 'either a keyword, name, or type arg is required.' unless (keyword || name || type)
82
80
 
83
81
  _places(url_part: 'radar', location: location, radius: radius,
84
82
  keyword: keyword, min_price: min_price, max_price: max_price,
@@ -152,15 +150,13 @@ module GoogleMaps
152
150
 
153
151
  # Downloads a photo from the Places API.
154
152
  #
155
- # @param [String] photo_reference: A string identifier that uniquely identifies a photo, as provided by either a Places search or Places detail request.
156
- # @param [Integer] max_width: Specifies the maximum desired width, in pixels.
157
- # @param [Integer] max_height: Specifies the maximum desired height, in pixels.
153
+ # @param [String] photo_reference A string identifier that uniquely identifies a photo, as provided by either a Places search or Places detail request.
154
+ # @param [Integer] max_width Specifies the maximum desired width, in pixels.
155
+ # @param [Integer] max_height Specifies the maximum desired height, in pixels.
158
156
  #
159
157
  # @return [String] URL of the photo.
160
158
  def place_photo(photo_reference:, max_width: nil, max_height: nil)
161
- unless max_width || max_height
162
- raise StandardError, 'a max_width or max_height arg is required'
163
- end
159
+ raise StandardError, 'a max_width or max_height arg is required' unless (max_width || max_height)
164
160
 
165
161
  params = {'photoreference' => photo_reference}
166
162
 
@@ -238,8 +234,10 @@ module GoogleMaps
238
234
  case self.client.response_format
239
235
  when :xml
240
236
  self.client.get(url: "/maps/api/place/#{url_part}autocomplete/xml", params: params).xpath('//prediction')
241
- else
237
+ when :json
242
238
  self.client.get(url: "/maps/api/place/#{url_part}autocomplete/json", params: params)['predictions']
239
+ else
240
+ raise StandardError, 'Unsupported response format. Should be either :json or :xml.'
243
241
  end
244
242
  end
245
243
 
@@ -10,36 +10,6 @@ module GoogleMaps
10
10
  class Roads
11
11
  include GoogleMaps::Services::Exceptions
12
12
 
13
- # Extracts a result from a Roads API HTTP response.
14
- @@_roads_extract = Proc.new { |resp|
15
- status_code = resp.code.to_i
16
- begin
17
- body = JSON.parse(resp.body)
18
- rescue JSON::ParserError
19
- raise APIError.new(status_code), 'Received malformed response.'
20
- end
21
-
22
- if body.key?('error')
23
- error = body['error']
24
- status = error['status']
25
-
26
- if status == 'RESOURCE_EXHAUSTED'
27
- raise RetriableRequest
28
- end
29
-
30
- if error.key?('message')
31
- raise APIError.new(status), error['message']
32
- else
33
- raise APIError.new(status)
34
- end
35
- end
36
-
37
- if status_code != 200
38
- raise HTTPError.new(status_code)
39
- end
40
- body
41
- }
42
-
43
13
  # @return [Symbol] the HTTP client.
44
14
  attr_accessor :client
45
15
 
@@ -65,7 +35,7 @@ module GoogleMaps
65
35
  end
66
36
 
67
37
  self.client.get(url: '/v1/snapToRoads', params: params, base_url: ROADS_BASE_URL,
68
- accepts_clientid: false, extract_body: @@_roads_extract)['snappedPoints']
38
+ accepts_clientid: false, extract_body: lambda(&method(:_roads_extract)))['snappedPoints']
69
39
  end
70
40
 
71
41
  # Returns the posted speed limit (in km/h) for given road segments.
@@ -80,7 +50,7 @@ module GoogleMaps
80
50
  params = {'placeId' => place_ids}
81
51
 
82
52
  self.client.get(url: '/v1/speedLimits', params: params, base_url: ROADS_BASE_URL,
83
- accepts_clientid: false, extract_body: @@_roads_extract)['speedLimits']
53
+ accepts_clientid: false, extract_body: lambda(&method(:_roads_extract)))['speedLimits']
84
54
  end
85
55
 
86
56
  # Returns the posted speed limit (in km/h) for given road segments.
@@ -93,7 +63,7 @@ module GoogleMaps
93
63
  params = {'path' => Convert.piped_location(path)}
94
64
 
95
65
  self.client.get(url: '/v1/speedLimits', params: params, base_url: ROADS_BASE_URL,
96
- accepts_clientid: false, extract_body: @@_roads_extract)
66
+ accepts_clientid: false, extract_body: lambda(&method(:_roads_extract)))
97
67
  end
98
68
 
99
69
  # Find the closest road segments for each point.
@@ -107,9 +77,46 @@ module GoogleMaps
107
77
  params = {'points' => Convert.piped_location(points)}
108
78
 
109
79
  self.client.get(url: '/v1/nearestRoads', params: params, base_url: ROADS_BASE_URL,
110
- accepts_clientid: false, extract_body: @@_roads_extract)['snappedPoints']
80
+ accepts_clientid: false, extract_body: lambda(&method(:_roads_extract)))['snappedPoints']
111
81
  end
112
- end
113
82
 
83
+ # Extracts a result from a Roads API HTTP response.
84
+ #
85
+ # @private
86
+ #
87
+ # @param [Net::HTTPResponse] resp HTTP response object.
88
+ #
89
+ # @return [Hash, Array] Valid JSON response.
90
+ def _roads_extract(resp)
91
+ status_code = resp.code.to_i
92
+ begin
93
+ body = JSON.parse(resp.body)
94
+ rescue JSON::ParserError
95
+ raise APIError.new(status_code), 'Received malformed response.'
96
+ end
97
+
98
+ if body.key?('error')
99
+ error = body['error']
100
+ status = error['status']
101
+
102
+ if status == 'RESOURCE_EXHAUSTED'
103
+ raise RetriableRequest
104
+ end
105
+
106
+ if error.respond_to?(:key?) && error.key?('message')
107
+ raise APIError.new(status), error['message']
108
+ else
109
+ raise APIError.new(status)
110
+ end
111
+ end
112
+
113
+ if status_code != 200
114
+ raise HTTPError.new(status_code)
115
+ end
116
+ body
117
+ end
118
+
119
+ private :_roads_extract
120
+ end
114
121
  end
115
122
  end
@@ -0,0 +1,99 @@
1
+ require 'googlemaps/services/util'
2
+
3
+ module GoogleMaps
4
+ module Services
5
+ ALLOWED_SCALES = [2, 4]
6
+ SUPPORTED_IMG_FORMATS = ["png32", "gif", "jpg", "jpg-baseline"]
7
+ SUPPORTED_MAP_TYPES = ["satellite", "hybrid", "terrain"]
8
+
9
+ # Performs requests to the Google Static Map API.
10
+ #
11
+ # @example Get static map image
12
+ # staticmap = GoogleMaps::Services::StaticMap.new(client)
13
+ # map_img = staticmap.query(size: {:length => 640, :width => 400},
14
+ # center: "50.8449925,4.362961",
15
+ # maptype: "hybrid",
16
+ # zoom: 16)
17
+ # # {
18
+ # # :url => "https://maps.googleapis.com/maps/api/staticmap?size=640x400&center=50.8449925%2C4.362961&zoom=16&maptype=hybrid",
19
+ # # :mime_type => "image/png",
20
+ # # :image_data => "iVBORw0KGgoAAAANSUhEUgAAAoAAAAGQCAMAAAAJLSEXAAADAFBMVEU..."
21
+ # # }
22
+ class StaticMap
23
+
24
+ # @return [Symbol] The HTTP client.
25
+ attr_accessor :client
26
+
27
+ def initialize(client)
28
+ self.client = client
29
+ end
30
+
31
+ # Get the static map image.
32
+ #
33
+ # @param [Hash] size The rectangular dimensions of the map image.
34
+ # @param [String, Hash] center The address or lat/lng value of the map's center.
35
+ # @param [Integer] zoom The magnification level of the map.
36
+ # @param [Integer] scale The scale of the map. This affects the number of pixels that are returned.
37
+ # @param [String] format The format of the resulting image. Defaults to "png8" or "png".
38
+ # @param [String] maptype The type of map to construct. Defaults to "roadmap".
39
+ # @param [String] language The language to use for display of labels on map tiles.
40
+ # @param [String] region The region code specified as a two-character ccTLD ('top-level domain') value.
41
+ # @param [String, Array] markers One or more markers to attach to the image at specified locations.
42
+ # @param [String, Array] path The single path of two or more connected points to overlay in the image at the specified locations.
43
+ # @param [String, Array] visible One or more locations that should remain visible on the map.
44
+ # @param [String] style A custom style to alter the presentation of a specific feature (roads, parks, and other features) of the map.
45
+ def query(size:, center: nil, zoom: nil, scale: 1, format: "png", maptype: "roadmap",
46
+ language: nil, region: nil, markers: nil, path: nil, visible: nil, style: nil)
47
+ params = { 'size' => Convert.rectangular_dimensions(size) }
48
+
49
+ if markers
50
+ params['markers'] = markers
51
+ else
52
+ raise StandardError, "both center and zoom are required if markers not present." unless (center && zoom)
53
+
54
+ params['center'] = Convert.to_latlng(center)
55
+ params['zoom'] = zoom
56
+ end
57
+
58
+ if scale != 1
59
+ raise StandardError, "invalid scale value." unless ALLOWED_SCALES.include? scale
60
+ params['scale'] = scale
61
+ end
62
+
63
+ if format != "png"
64
+ raise StandardError, "invalid image format." unless SUPPORTED_IMG_FORMATS.include? format
65
+ params['format'] = format
66
+ end
67
+
68
+ if maptype != "roadmap"
69
+ raise StandardError, "invalid maptype value." unless SUPPORTED_MAP_TYPES.include? maptype
70
+ params['maptype'] = maptype
71
+ end
72
+
73
+ if language
74
+ params['language'] = language
75
+ end
76
+
77
+ if region
78
+ params['region'] = region
79
+ end
80
+
81
+ if path
82
+ params['path'] = path
83
+ end
84
+
85
+ if visible
86
+ params['visible'] = visible
87
+ end
88
+
89
+ if style
90
+ params['style'] = style
91
+ end
92
+
93
+ self.client.get(url: "/maps/api/staticmap", params: params)
94
+ end
95
+
96
+ end
97
+
98
+ end
99
+ end
@@ -6,7 +6,7 @@ module GoogleMaps
6
6
  # Performs requests to the Google Maps Timezone API.
7
7
  #
8
8
  # @example
9
- # timezone = GoogleMaps::Services::Timezone(client)
9
+ # timezone = GoogleMaps::Services::Timezone.new(client)
10
10
  # result = timezone.query(location: "38.908133,-77.047119")
11
11
  class Timezone
12
12
  # @return [Symbol] The HTTP client.
@@ -1,4 +1,3 @@
1
- require 'net/http'
2
1
  require 'openssl'
3
2
  require 'base64'
4
3
  require 'date'
@@ -8,12 +7,14 @@ module GoogleMaps
8
7
  module Services
9
8
 
10
9
  module HashDot
11
- def method_missing(meth, *args, &block)
12
- if has_key?(meth.to_s)
13
- self[meth.to_s]
14
- else
15
- raise NoMethodError, "undefined method #{meth} for #{self}"
10
+ def method_missing(method, *opts)
11
+ m = method.to_s
12
+ if self.has_key?(m)
13
+ return self[m]
14
+ elsif self.has_key?(m.to_sym)
15
+ return self[m.to_sym]
16
16
  end
17
+ super
17
18
  end
18
19
  end
19
20
 
@@ -37,6 +38,25 @@ module GoogleMaps
37
38
  [object]
38
39
  end
39
40
  end
41
+
42
+ # Determines if one array contains all elements of another array.
43
+ #
44
+ # @param [Array] arr target array.
45
+ # @param [Array] other array to look for in the target array.
46
+ # @example
47
+ # myarr = ["hello", "world"]
48
+ # ArrayBox.contains_all? myarr, ["hello"] # true
49
+ #
50
+ # @return [TrueClass, FalseClass] a boolean.
51
+ def self.contains_all?(arr, other)
52
+ h = arr.inject(Hash.new(0)) {|h, i| h[i] += 1; h}
53
+ other.each do |i|
54
+ return false unless h.has_key?(i)
55
+ return false if h[i].zero?
56
+ h[i] -= 1
57
+ end
58
+ return true
59
+ end
40
60
  end
41
61
 
42
62
  # Set of utility methods.
@@ -295,6 +315,28 @@ module GoogleMaps
295
315
  unencoded = piped_location(locations)
296
316
  encoded.length < unencoded.length ? encoded : unencoded
297
317
  end
318
+
319
+ # Returns the MIME type from the given header value.
320
+ #
321
+ # @param [String] content_type The Content-Type header value.
322
+ #
323
+ # @return [String] the MIME type value.
324
+ def self.get_mime_type(content_type)
325
+ content_type.split(';').first
326
+ end
327
+
328
+ # Returns the rectangular dimensions in the form {horizontal_value}x{vertical_value}.
329
+ #
330
+ # @example
331
+ # Convert.rectangular_dimensions({:length => 500, :width => 400}) # "500x400"
332
+ #
333
+ # @param [Hash] size The size hash.
334
+ #
335
+ # @return [String] a string value in the form "lengthxwidth".
336
+ def self.rectangular_dimensions(size)
337
+ raise TypeError, "#{__method__.to_s} expected a Hash." unless size.is_a? Hash
338
+ "#{size[:length]}x#{size[:width]}"
339
+ end
298
340
  end
299
341
 
300
342
  end
@@ -1,5 +1,5 @@
1
1
  module GoogleMaps
2
2
  module Services
3
- VERSION = '1.2.5'
3
+ VERSION = '1.3.0'.freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: googlemaps-services
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.5
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Faissal Elamraoui
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-11-01 00:00:00.000000000 Z
11
+ date: 2017-01-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -16,20 +16,40 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.6'
19
+ version: '1.7'
20
20
  - - ">="
21
21
  - !ruby/object:Gem::Version
22
- version: 1.6.8
22
+ version: 1.7.0.1
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - "~>"
28
28
  - !ruby/object:Gem::Version
29
- version: '1.6'
29
+ version: '1.7'
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: 1.6.8
32
+ version: 1.7.0.1
33
+ - !ruby/object:Gem::Dependency
34
+ name: http
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.1'
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 2.1.0
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '2.1'
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 2.1.0
33
53
  - !ruby/object:Gem::Dependency
34
54
  name: bundler
35
55
  requirement: !ruby/object:Gem::Requirement
@@ -88,6 +108,7 @@ files:
88
108
  - lib/googlemaps/services/geocoding.rb
89
109
  - lib/googlemaps/services/places.rb
90
110
  - lib/googlemaps/services/roads.rb
111
+ - lib/googlemaps/services/staticmap.rb
91
112
  - lib/googlemaps/services/timezone.rb
92
113
  - lib/googlemaps/services/util.rb
93
114
  - lib/googlemaps/services/version.rb