google_maps_apis 1.0.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 +7 -0
- data/.github/ruby.yml +28 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.yardopts +11 -0
- data/CHANGELOG.md +47 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +26 -0
- data/LICENSE +202 -0
- data/README.md +379 -0
- data/Rakefile +15 -0
- data/bin/console +15 -0
- data/google_maps_apis.gemspec +23 -0
- data/lib/google_maps_apis/client.rb +294 -0
- data/lib/google_maps_apis/convert.rb +176 -0
- data/lib/google_maps_apis/errors.rb +82 -0
- data/lib/google_maps_apis/polyline.rb +90 -0
- data/lib/google_maps_apis/services/directions.rb +101 -0
- data/lib/google_maps_apis/services/distance_matrix.rb +85 -0
- data/lib/google_maps_apis/services/elevation.rb +58 -0
- data/lib/google_maps_apis/services/geocoding.rb +85 -0
- data/lib/google_maps_apis/services/places.rb +20 -0
- data/lib/google_maps_apis/services/roads.rb +187 -0
- data/lib/google_maps_apis/services/time_zone.rb +41 -0
- data/lib/google_maps_apis/services.rb +6 -0
- data/lib/google_maps_apis/url.rb +64 -0
- data/lib/google_maps_apis/validator.rb +39 -0
- data/lib/google_maps_apis/version.rb +23 -0
- data/lib/google_maps_apis.rb +56 -0
- metadata +117 -0
@@ -0,0 +1,294 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
#require 'faraday/retry'
|
3
|
+
require 'multi_json'
|
4
|
+
require 'thread'
|
5
|
+
|
6
|
+
require 'google_maps_apis/errors'
|
7
|
+
require 'google_maps_apis/convert'
|
8
|
+
require 'google_maps_apis/url'
|
9
|
+
require 'google_maps_apis/services/directions'
|
10
|
+
require 'google_maps_apis/services/distance_matrix'
|
11
|
+
require 'google_maps_apis/services/elevation'
|
12
|
+
require 'google_maps_apis/services/geocoding'
|
13
|
+
require 'google_maps_apis/services/roads'
|
14
|
+
require 'google_maps_apis/services/time_zone'
|
15
|
+
require 'google_maps_apis/services/places'
|
16
|
+
|
17
|
+
module GoogleMapsApis
|
18
|
+
|
19
|
+
# Core client functionality, common across all API requests (including performing
|
20
|
+
# HTTP requests).
|
21
|
+
class Client
|
22
|
+
# Default Google Maps Web Service base endpoints
|
23
|
+
DEFAULT_BASE_URL = 'https://maps.googleapis.com'
|
24
|
+
|
25
|
+
# Errors those could be retriable.
|
26
|
+
RETRIABLE_ERRORS = [GoogleMapsApis::Error::ServerError, GoogleMapsApis::Error::RateLimitError]
|
27
|
+
|
28
|
+
include GoogleMapsApis::Services::Directions
|
29
|
+
include GoogleMapsApis::Services::DistanceMatrix
|
30
|
+
include GoogleMapsApis::Services::Elevation
|
31
|
+
include GoogleMapsApis::Services::Geocoding
|
32
|
+
include GoogleMapsApis::Services::Roads
|
33
|
+
include GoogleMapsApis::Services::TimeZone
|
34
|
+
include GoogleMapsApis::Services::Places
|
35
|
+
|
36
|
+
# Secret key for accessing Google Maps Web Service.
|
37
|
+
# Can be obtained at https://developers.google.com/maps/documentation/geocoding/get-api-key#key.
|
38
|
+
# @return [String]
|
39
|
+
attr_accessor :key
|
40
|
+
|
41
|
+
# Client id for using Maps API for Work services.
|
42
|
+
# @return [String]
|
43
|
+
attr_accessor :client_id
|
44
|
+
|
45
|
+
# Client secret for using Maps API for Work services.
|
46
|
+
# @return [String]
|
47
|
+
attr_accessor :client_secret
|
48
|
+
|
49
|
+
# Timeout across multiple retriable requests, in seconds.
|
50
|
+
# @return [Integer]
|
51
|
+
attr_accessor :retry_timeout
|
52
|
+
|
53
|
+
# Number of queries per second permitted.
|
54
|
+
# If the rate limit is reached, the client will sleep for
|
55
|
+
# the appropriate amount of time before it runs the current query.
|
56
|
+
# @return [Integer]
|
57
|
+
attr_reader :queries_per_second
|
58
|
+
|
59
|
+
# Construct Google Maps Web Service API client.
|
60
|
+
#
|
61
|
+
# You can configure `Hurley::Client` through `request_options` and `ssl_options` parameters.
|
62
|
+
# You can also directly get the `Hurley::Client` object via {#client} method.
|
63
|
+
#
|
64
|
+
# @example Setup API keys
|
65
|
+
# gmaps = GoogleMapsApis::Client.new(key: 'Add your key here')
|
66
|
+
#
|
67
|
+
# @example Setup client IDs
|
68
|
+
# gmaps = GoogleMapsApis::Client.new(
|
69
|
+
# client_id: 'Add your client id here',
|
70
|
+
# client_secret: 'Add your client secret here'
|
71
|
+
# )
|
72
|
+
#
|
73
|
+
# @example Setup time out and QPS limit
|
74
|
+
# gmaps = GoogleMapsApis::Client.new(
|
75
|
+
# key: 'Add your key here',
|
76
|
+
# retry_timeout: 20,
|
77
|
+
# queries_per_second: 10
|
78
|
+
# )
|
79
|
+
#
|
80
|
+
# @example Request behind proxy
|
81
|
+
# request_options = Hurley::RequestOptions.new
|
82
|
+
# request_options.proxy = Hurley::Url.parse 'http://user:password@proxy.example.com:3128'
|
83
|
+
#
|
84
|
+
# gmaps = GoogleMapsApis::Client.new(
|
85
|
+
# key: 'Add your key here',
|
86
|
+
# request_options: request_options
|
87
|
+
# )
|
88
|
+
#
|
89
|
+
# @example Using Excon and Http Cache
|
90
|
+
# require 'hurley-excon' # https://github.com/lostisland/hurley-excon
|
91
|
+
# require 'hurley/http_cache' # https://github.com/plataformatec/hurley-http-cache
|
92
|
+
#
|
93
|
+
# gmaps = GoogleMapsApis::Client.new(
|
94
|
+
# key: 'Add your key here',
|
95
|
+
# connection: Hurley::HttpCache.new(HurleyExcon::Connection.new)
|
96
|
+
# )
|
97
|
+
#
|
98
|
+
# @option options [String] :key Secret key for accessing Google Maps Web Service.
|
99
|
+
# Can be obtained at https://developers.google.com/maps/documentation/geocoding/get-api-key#key.
|
100
|
+
# @option options [String] :client_id Client id for using Maps API for Work services.
|
101
|
+
# @option options [String] :client_secret Client secret for using Maps API for Work services.
|
102
|
+
# @option options [Integer] :retry_timeout Timeout across multiple retriable requests, in seconds.
|
103
|
+
# @option options [Integer] :queries_per_second Number of queries per second permitted.
|
104
|
+
#
|
105
|
+
# @option options [Hurley::RequestOptions] :request_options HTTP client request options.
|
106
|
+
# See https://github.com/lostisland/hurley/blob/master/lib/hurley/options.rb.
|
107
|
+
# @option options [Hurley::SslOptions] :ssl_options HTTP client SSL options.
|
108
|
+
# See https://github.com/lostisland/hurley/blob/master/lib/hurley/options.rb.
|
109
|
+
# @option options [Object] :connection HTTP client connection.
|
110
|
+
# By default, the default Hurley's HTTP client connection (Net::Http) will be used.
|
111
|
+
# See https://github.com/lostisland/hurley/blob/master/README.md#connections.
|
112
|
+
def initialize(**options)
|
113
|
+
[:key, :client_id, :client_secret,
|
114
|
+
:retry_timeout, :queries_per_second,
|
115
|
+
:request_options, :ssl_options, :connection].each do |key|
|
116
|
+
self.instance_variable_set("@#{key}".to_sym, options[key] || GoogleMapsApis.instance_variable_get("@#{key}"))
|
117
|
+
end
|
118
|
+
|
119
|
+
initialize_query_tickets
|
120
|
+
end
|
121
|
+
|
122
|
+
# Get the current HTTP client.
|
123
|
+
# @return [Faraday::Client]
|
124
|
+
def client
|
125
|
+
@client ||= new_client
|
126
|
+
end
|
127
|
+
|
128
|
+
protected
|
129
|
+
|
130
|
+
# Initialize QPS queue. QPS queue is a "tickets" for calling API
|
131
|
+
def initialize_query_tickets
|
132
|
+
if @queries_per_second
|
133
|
+
@qps_queue = SizedQueue.new @queries_per_second
|
134
|
+
@queries_per_second.times do
|
135
|
+
@qps_queue << 0
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Create a new HTTP client.
|
141
|
+
# @return [Faraday::Connection]
|
142
|
+
def new_client
|
143
|
+
client = Faraday.new(DEFAULT_BASE_URL)
|
144
|
+
#client.request_options.query_class = Hurley::Query::Flat
|
145
|
+
#client.request_options.redirection_limit = 0
|
146
|
+
client.headers[:user_agent] = user_agent
|
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
|
+
|
152
|
+
client
|
153
|
+
end
|
154
|
+
|
155
|
+
# Build the user agent header
|
156
|
+
# @return [String]
|
157
|
+
def user_agent
|
158
|
+
sprintf('google-maps-services-ruby/%s %s',
|
159
|
+
GoogleMapsApis::VERSION,
|
160
|
+
GoogleMapsApis::OS_VERSION)
|
161
|
+
end
|
162
|
+
|
163
|
+
# Make API call.
|
164
|
+
#
|
165
|
+
# @param [String] path Url path.
|
166
|
+
# @param [String] params Request parameters.
|
167
|
+
# @param [String] base_url Base Google Maps Web Service API endpoint url.
|
168
|
+
# @param [Boolean] accepts_client_id Sign the request using API {#keys} instead of {#client_id}.
|
169
|
+
# @param [Method] custom_response_decoder Custom method to decode raw API response.
|
170
|
+
#
|
171
|
+
# @return [Object] Decoded response body.
|
172
|
+
def get(path, params, base_url: DEFAULT_BASE_URL, accepts_client_id: true, custom_response_decoder: nil)
|
173
|
+
url = base_url + generate_auth_url(path, params, accepts_client_id)
|
174
|
+
|
175
|
+
#Retriable.retriable timeout: @retry_timeout, on: RETRIABLE_ERRORS do |try|
|
176
|
+
#begin
|
177
|
+
#request_query_ticket
|
178
|
+
response = client.get url
|
179
|
+
#ensure
|
180
|
+
#release_query_ticket
|
181
|
+
#end
|
182
|
+
|
183
|
+
return custom_response_decoder.call(response) if custom_response_decoder
|
184
|
+
decode_response_body(response)
|
185
|
+
#end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Get/wait the request "ticket" if QPS is configured.
|
189
|
+
# Check for previous request time, it must be more than a second ago before calling new request.
|
190
|
+
#
|
191
|
+
# @return [void]
|
192
|
+
def request_query_ticket
|
193
|
+
if @qps_queue
|
194
|
+
elapsed_since_earliest = Time.now - @qps_queue.pop
|
195
|
+
sleep(1 - elapsed_since_earliest) if elapsed_since_earliest.to_f < 1
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# Release request "ticket".
|
200
|
+
#
|
201
|
+
# @return [void]
|
202
|
+
def release_query_ticket
|
203
|
+
@qps_queue << Time.now if @qps_queue
|
204
|
+
end
|
205
|
+
|
206
|
+
# Returns the path and query string portion of the request URL,
|
207
|
+
# first adding any necessary parameters.
|
208
|
+
#
|
209
|
+
# @param [String] path The path portion of the URL.
|
210
|
+
# @param [Hash] params URL parameters.
|
211
|
+
# @param [Boolean] accepts_client_id Sign the request using API {#keys} instead of {#client_id}.
|
212
|
+
#
|
213
|
+
# @return [String]
|
214
|
+
def generate_auth_url(path, params, accepts_client_id)
|
215
|
+
# Deterministic ordering through sorting by key.
|
216
|
+
# Useful for tests, and in the future, any caching.
|
217
|
+
if params.kind_of?(Hash)
|
218
|
+
params = params.sort
|
219
|
+
else
|
220
|
+
params = params.dup
|
221
|
+
end
|
222
|
+
|
223
|
+
if accepts_client_id and @client_id and @client_secret
|
224
|
+
params << ["client", @client_id]
|
225
|
+
|
226
|
+
path = [path, GoogleMapsApis::Url.urlencode_params(params)].join("?")
|
227
|
+
sig = GoogleMapsApis::Url.sign_hmac(@client_secret, path)
|
228
|
+
return path + "&signature=" + sig
|
229
|
+
end
|
230
|
+
|
231
|
+
if @key
|
232
|
+
params << ["key", @key]
|
233
|
+
return path + "?" + GoogleMapsApis::Url.urlencode_params(params)
|
234
|
+
end
|
235
|
+
|
236
|
+
raise ArgumentError, "Must provide API key for this API. It does not accept enterprise credentials."
|
237
|
+
end
|
238
|
+
|
239
|
+
# Extract and parse body response as hash. Throw an error if there is something wrong with the response.
|
240
|
+
#
|
241
|
+
# @param [Faraday::Response] response Web API response.
|
242
|
+
#
|
243
|
+
# @return [Hash] Response body as hash. The hash key will be symbolized.
|
244
|
+
def decode_response_body(response)
|
245
|
+
check_response_status(response)
|
246
|
+
body = MultiJson.load(response.body, :symbolize_keys => true)
|
247
|
+
check_body_error(response, body)
|
248
|
+
body
|
249
|
+
end
|
250
|
+
|
251
|
+
# Check HTTP response status code. Raise error if the status is not 2xx.
|
252
|
+
#
|
253
|
+
# @param [Faraday::Response] response Web API response.
|
254
|
+
def check_response_status(response)
|
255
|
+
case response.status
|
256
|
+
when 200..300
|
257
|
+
# Do-nothing
|
258
|
+
when 301...308
|
259
|
+
raise GoogleMapsApis::Error::RedirectError.new(response),
|
260
|
+
(GoogleMapsApis::Error::RedirectError::ERRORS_3XX[response.status.to_s] + sprintf('Redirect to %s', response.headers[:location]))
|
261
|
+
when 400...409,415,422,429
|
262
|
+
raise GoogleMapsApis::Error::ClientError.new(response),
|
263
|
+
GoogleMapsApis::Error::ClientError::ERRORS_4XX[response.status.to_s]
|
264
|
+
when 410...500
|
265
|
+
raise GoogleMapsApis::Error::ClientError.new(response), 'Invalid request'
|
266
|
+
when 500..600
|
267
|
+
raise GoogleMapsApis::Error::ServerError.new(response), 'Server error'
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
# Check response body for error status.
|
272
|
+
#
|
273
|
+
# @param [Faraday::Response] response Response object.
|
274
|
+
# @param [Hash] body Response body.
|
275
|
+
#
|
276
|
+
# @return [void]
|
277
|
+
def check_body_error(response, body)
|
278
|
+
case body[:status]
|
279
|
+
when 'OK', 'ZERO_RESULTS'
|
280
|
+
# Do-nothing
|
281
|
+
when 'OVER_QUERY_LIMIT'
|
282
|
+
raise GoogleMapsApis::Error::RateLimitError.new(response), body[:error_message]
|
283
|
+
when 'REQUEST_DENIED'
|
284
|
+
raise GoogleMapsApis::Error::RequestDeniedError.new(response), body[:error_message]
|
285
|
+
when 'INVALID_REQUEST'
|
286
|
+
raise GoogleMapsApis::Error::InvalidRequestError.new(response), body[:error_message]
|
287
|
+
when 'NOT_FOUND'
|
288
|
+
raise GoogleMapsApis::Error::NotFoundError.new(response), (body[:error_message] || 'ADDRESS NOT FOUND')
|
289
|
+
else
|
290
|
+
raise GoogleMapsApis::Error::ApiError.new(response), body[:error_message]
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
module GoogleMapsApis
|
2
|
+
|
3
|
+
# Converts Ruby types to string representations suitable for Maps API server.
|
4
|
+
module Convert
|
5
|
+
module_function
|
6
|
+
|
7
|
+
# Converts a lat/lon pair to a comma-separated string.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# >> GoogleMapsApis::Convert.latlng({"lat": -33.8674869, "lng": 151.2069902})
|
11
|
+
# => "-33.867487,151.206990"
|
12
|
+
#
|
13
|
+
# @param [Hash, Array] arg The lat/lon hash or array pair.
|
14
|
+
#
|
15
|
+
# @return [String] Comma-separated lat/lng.
|
16
|
+
#
|
17
|
+
# @raise [ArgumentError] When argument is not lat/lng hash or array.
|
18
|
+
def latlng(arg)
|
19
|
+
return "%f,%f" % normalize_latlng(arg)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Take the various lat/lng representations and return a tuple.
|
23
|
+
#
|
24
|
+
# Accepts various representations:
|
25
|
+
#
|
26
|
+
# 1. Hash with two entries - `lat` and `lng`
|
27
|
+
# 2. Array or list - e.g. `[-33, 151]`
|
28
|
+
#
|
29
|
+
# @param [Hash, Array] arg The lat/lon hash or array pair.
|
30
|
+
#
|
31
|
+
# @return [Array] Pair of lat and lng array.
|
32
|
+
def normalize_latlng(arg)
|
33
|
+
if arg.kind_of?(Hash)
|
34
|
+
lat = arg[:lat] || arg[:latitude] || arg["lat"] || arg["latitude"]
|
35
|
+
lng = arg[:lng] || arg[:longitude] || arg["lng"] || arg["longitude"]
|
36
|
+
return lat, lng
|
37
|
+
elsif arg.kind_of?(Array)
|
38
|
+
return arg[0], arg[1]
|
39
|
+
end
|
40
|
+
|
41
|
+
raise ArgumentError, "Expected a lat/lng Hash or Array, but got #{arg.class}"
|
42
|
+
end
|
43
|
+
|
44
|
+
# If arg is list-like, then joins it with sep.
|
45
|
+
#
|
46
|
+
# @param [String] sep Separator string.
|
47
|
+
# @param [Array, String] arg Value to coerce into a list.
|
48
|
+
#
|
49
|
+
# @return [String]
|
50
|
+
def join_list(sep, arg)
|
51
|
+
return as_list(arg).join(sep)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Coerces arg into a list. If arg is already list-like, returns arg.
|
55
|
+
# Otherwise, returns a one-element list containing arg.
|
56
|
+
#
|
57
|
+
# @param [Object] arg
|
58
|
+
#
|
59
|
+
# @return [Array]
|
60
|
+
def as_list(arg)
|
61
|
+
if arg.kind_of?(Array)
|
62
|
+
return arg
|
63
|
+
end
|
64
|
+
return [arg]
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
# Converts the value into a unix time (seconds since unix epoch).
|
69
|
+
#
|
70
|
+
# @example
|
71
|
+
# >> GoogleMapsApis::Convert.time(datetime.now())
|
72
|
+
# => "1409810596"
|
73
|
+
#
|
74
|
+
# @param [Time, Date, DateTime, Integer] arg The time.
|
75
|
+
#
|
76
|
+
# @return [String] String representation of epoch time
|
77
|
+
def time(arg)
|
78
|
+
if arg.kind_of?(DateTime)
|
79
|
+
arg = arg.to_time
|
80
|
+
end
|
81
|
+
return arg.to_i.to_s
|
82
|
+
end
|
83
|
+
|
84
|
+
# Converts a dict of components to the format expected by the Google Maps
|
85
|
+
# server.
|
86
|
+
#
|
87
|
+
# @example
|
88
|
+
# >> GoogleMapsApis::Convert.components({"country": "US", "postal_code": "94043"})
|
89
|
+
# => "country:US|postal_code:94043"
|
90
|
+
#
|
91
|
+
# @param [Hash] arg The component filter.
|
92
|
+
#
|
93
|
+
# @return [String]
|
94
|
+
def components(arg)
|
95
|
+
if arg.kind_of?(Hash)
|
96
|
+
arg = arg.sort.map { |k, v| "#{k}:#{v}" }
|
97
|
+
return arg.join("|")
|
98
|
+
end
|
99
|
+
|
100
|
+
raise ArgumentError, "Expected a Hash for components, but got #{arg.class}"
|
101
|
+
end
|
102
|
+
|
103
|
+
# Converts a lat/lon bounds to a comma- and pipe-separated string.
|
104
|
+
#
|
105
|
+
# Accepts two representations:
|
106
|
+
#
|
107
|
+
# 1. String: pipe-separated pair of comma-separated lat/lon pairs.
|
108
|
+
# 2. Hash with two entries - "southwest" and "northeast". See {.latlng}
|
109
|
+
# for information on how these can be represented.
|
110
|
+
#
|
111
|
+
# For example:
|
112
|
+
#
|
113
|
+
# >> sydney_bounds = {
|
114
|
+
# ?> "northeast": {
|
115
|
+
# ?> "lat": -33.4245981,
|
116
|
+
# ?> "lng": 151.3426361
|
117
|
+
# ?> },
|
118
|
+
# ?> "southwest": {
|
119
|
+
# ?> "lat": -34.1692489,
|
120
|
+
# ?> "lng": 150.502229
|
121
|
+
# ?> }
|
122
|
+
# ?> }
|
123
|
+
# >> GoogleMapsApis::Convert.bounds(sydney_bounds)
|
124
|
+
# => '-34.169249,150.502229|-33.424598,151.342636'
|
125
|
+
#
|
126
|
+
# @param [Hash] arg The bounds.
|
127
|
+
#
|
128
|
+
# @return [String]
|
129
|
+
def bounds(arg)
|
130
|
+
if arg.kind_of?(Hash)
|
131
|
+
southwest = arg[:southwest] || arg["southwest"]
|
132
|
+
northeast = arg[:northeast] || arg["northeast"]
|
133
|
+
return "#{latlng(southwest)}|#{latlng(northeast)}"
|
134
|
+
end
|
135
|
+
|
136
|
+
raise ArgumentError, "Expected a bounds (southwest/northeast) Hash, but got #{arg.class}"
|
137
|
+
end
|
138
|
+
|
139
|
+
# Converts a waypoints to the format expected by the Google Maps server.
|
140
|
+
#
|
141
|
+
# Accept two representation of waypoint:
|
142
|
+
#
|
143
|
+
# 1. String: Name of place or comma-separated lat/lon pair.
|
144
|
+
# 2. Hash/Array: Lat/lon pair.
|
145
|
+
#
|
146
|
+
# @param [Array, String, Hash] waypoint Path.
|
147
|
+
#
|
148
|
+
# @return [String]
|
149
|
+
def waypoint(waypoint)
|
150
|
+
if waypoint.kind_of?(String)
|
151
|
+
return waypoint
|
152
|
+
end
|
153
|
+
return GoogleMapsApis::Convert.latlng(waypoint)
|
154
|
+
end
|
155
|
+
|
156
|
+
# Converts an array of waypoints (path) to the format expected by the Google Maps
|
157
|
+
# server.
|
158
|
+
#
|
159
|
+
# Accept two representation of waypoint:
|
160
|
+
#
|
161
|
+
# 1. String: Name of place or comma-separated lat/lon pair.
|
162
|
+
# 2. Hash/Array: Lat/lon pair.
|
163
|
+
#
|
164
|
+
# @param [Array, String, Hash] waypoints Path.
|
165
|
+
#
|
166
|
+
# @return [String]
|
167
|
+
def waypoints(waypoints)
|
168
|
+
if waypoints.kind_of?(Array) and waypoints.length == 2 and waypoints[0].kind_of?(Numeric) and waypoints[1].kind_of?(Numeric)
|
169
|
+
waypoints = [waypoints]
|
170
|
+
end
|
171
|
+
|
172
|
+
waypoints = as_list(waypoints)
|
173
|
+
return join_list('|', waypoints.map { |k| waypoint(k) })
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module GoogleMapsApis
|
2
|
+
# Specific Google Maps Service error
|
3
|
+
module Error
|
4
|
+
# Base error, capable of wrapping another
|
5
|
+
class BaseError < StandardError
|
6
|
+
# HTTP response object
|
7
|
+
# @return [Faraday::Response]
|
8
|
+
attr_reader :response
|
9
|
+
|
10
|
+
# Initialize error
|
11
|
+
#
|
12
|
+
# @param [Faraday::Response] response HTTP response.
|
13
|
+
def initialize(response = nil)
|
14
|
+
@response = response
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# The response redirects to another URL.
|
19
|
+
class RedirectError < BaseError
|
20
|
+
ERRORS_3XX = {
|
21
|
+
'300' => 'Multiple Choices',
|
22
|
+
'301' => 'Moved Permanently',
|
23
|
+
'302' => 'Found',
|
24
|
+
'303' => 'See Other',
|
25
|
+
'304' => 'Not Modified',
|
26
|
+
'305' => 'Use Proxy',
|
27
|
+
'306' => 'Switch Proxy',
|
28
|
+
'307' => 'Temporary Redirect',
|
29
|
+
'308' => 'Permanent Redirect'
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
# A 4xx class HTTP error occurred.
|
34
|
+
# The request is invalid and should not be retried without modification.
|
35
|
+
class ClientError < BaseError
|
36
|
+
ERRORS_4XX = {
|
37
|
+
'400' => 'Bad Request',
|
38
|
+
'401' => 'Unauthorized',
|
39
|
+
'402' => 'Payment Required',
|
40
|
+
'403' => 'Forbidden',
|
41
|
+
'404' => 'Not Found',
|
42
|
+
'405' => 'Method Not Allowed',
|
43
|
+
'406' => 'Not Acceptable',
|
44
|
+
'407' => 'Proxy Authentication Required',
|
45
|
+
'408' => 'Request Timeout',
|
46
|
+
'409' => 'Conflict',
|
47
|
+
'415' => 'Unsupported Media Type',
|
48
|
+
'422' => 'Unprocessable Entity',
|
49
|
+
'429' => 'Too Many Requests'
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
# A 5xx class HTTP error occurred.
|
54
|
+
# An error occurred on the server and the request can be retried.
|
55
|
+
class ServerError < BaseError
|
56
|
+
end
|
57
|
+
|
58
|
+
# An unknown error occured.
|
59
|
+
class UnknownError < BaseError
|
60
|
+
end
|
61
|
+
|
62
|
+
# General Google Maps Web Service API error occured.
|
63
|
+
class ApiError < BaseError
|
64
|
+
end
|
65
|
+
|
66
|
+
# Requiered query is missing
|
67
|
+
class InvalidRequestError < ApiError
|
68
|
+
end
|
69
|
+
|
70
|
+
# The quota for the credential is over limit.
|
71
|
+
class RateLimitError < ApiError
|
72
|
+
end
|
73
|
+
|
74
|
+
# An unathorized error occurred. It might be caused by invalid key/secret or invalid access.
|
75
|
+
class RequestDeniedError < ApiError
|
76
|
+
end
|
77
|
+
|
78
|
+
# When an Address is not found. i.e. An address string could not be geocoded
|
79
|
+
class NotFoundError < ApiError
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'google_maps_apis/convert'
|
2
|
+
|
3
|
+
module GoogleMapsApis
|
4
|
+
|
5
|
+
# Encoder/decoder for [Google Encoded Polyline](https://developers.google.com/maps/documentation/utilities/polylinealgorithm).
|
6
|
+
module Polyline
|
7
|
+
module_function
|
8
|
+
|
9
|
+
# Decodes a Polyline string into a list of lat/lng hash.
|
10
|
+
#
|
11
|
+
# See the developer docs for a detailed description of this encoding:
|
12
|
+
# https://developers.google.com/maps/documentation/utilities/polylinealgorithm
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# encoded_path = '_p~iF~ps|U_ulLnnqC_mqNvxq`@'
|
16
|
+
# path = GoogleMapsApis::Polyline.decode(encoded_path)
|
17
|
+
# #=> [{:lat=>38.5, :lng=>-120.2}, {:lat=>40.7, :lng=>-120.95}, {:lat=>43.252, :lng=>-126.45300000000002}]
|
18
|
+
#
|
19
|
+
# @param [String] polyline An encoded polyline
|
20
|
+
#
|
21
|
+
# @return [Array] Array of hash with lat/lng keys
|
22
|
+
def decode(polyline)
|
23
|
+
points = []
|
24
|
+
index = lat = lng = 0
|
25
|
+
|
26
|
+
while index < polyline.length
|
27
|
+
result = 1
|
28
|
+
shift = 0
|
29
|
+
while true
|
30
|
+
b = polyline[index].ord - 63 - 1
|
31
|
+
index += 1
|
32
|
+
result += b << shift
|
33
|
+
shift += 5
|
34
|
+
break if b < 0x1f
|
35
|
+
end
|
36
|
+
lat += (result & 1) != 0 ? (~result >> 1) : (result >> 1)
|
37
|
+
|
38
|
+
result = 1
|
39
|
+
shift = 0
|
40
|
+
while true
|
41
|
+
b = polyline[index].ord - 63 - 1
|
42
|
+
index += 1
|
43
|
+
result += b << shift
|
44
|
+
shift += 5
|
45
|
+
break if b < 0x1f
|
46
|
+
end
|
47
|
+
lng += (result & 1) != 0 ? ~(result >> 1) : (result >> 1)
|
48
|
+
|
49
|
+
points << {lat: lat * 1e-5, lng: lng * 1e-5}
|
50
|
+
end
|
51
|
+
|
52
|
+
points
|
53
|
+
end
|
54
|
+
|
55
|
+
# Encodes a list of points into a polyline string.
|
56
|
+
#
|
57
|
+
# See the developer docs for a detailed description of this encoding:
|
58
|
+
# https://developers.google.com/maps/documentation/utilities/polylinealgorithm
|
59
|
+
#
|
60
|
+
# @param [Array<Hash>, Array<Array>] points A list of lat/lng pairs.
|
61
|
+
#
|
62
|
+
# @return [String]
|
63
|
+
def encode(points)
|
64
|
+
last_lat = last_lng = 0
|
65
|
+
result = ""
|
66
|
+
|
67
|
+
points.each do |point|
|
68
|
+
ll = GoogleMapsApis::Convert.normalize_latlng(point)
|
69
|
+
lat = (ll[0] * 1e5).round.to_i
|
70
|
+
lng = (ll[1] * 1e5).round.to_i
|
71
|
+
d_lat = lat - last_lat
|
72
|
+
d_lng = lng - last_lng
|
73
|
+
|
74
|
+
[d_lat, d_lng].each do |v|
|
75
|
+
v = (v < 0) ? ~(v << 1) : (v << 1)
|
76
|
+
while v >= 0x20
|
77
|
+
result += ((0x20 | (v & 0x1f)) + 63).chr
|
78
|
+
v >>= 5
|
79
|
+
end
|
80
|
+
result += (v + 63).chr
|
81
|
+
end
|
82
|
+
|
83
|
+
last_lat = lat
|
84
|
+
last_lng = lng
|
85
|
+
end
|
86
|
+
|
87
|
+
result
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|