google_maps_juice 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 81dd523f6c0a801f8b8e15d0589525cd683359f4b85d0427f9b697d1ade77f5d
4
+ data.tar.gz: 2fb1b05f6a92b3770771a5ae74de3ccd09f411f66dabde16d38160f151f48d66
5
+ SHA512:
6
+ metadata.gz: cdc4a09d044ecd8549b5a7851c1fda06be8a1ff4677df0e3173dfdabadfbb9b791789a3fe4a4a8d1dbc1d353f2382812ddc6dbb912915b679598502dd0254212
7
+ data.tar.gz: 646b96bb8275a1230eb16a19d6472e56bd0f31218e0580976f1e9a4c88f738604885a156d92927fcb5060e54ab0ea077bc66c77e4f351eb12384ef63b3ac5260
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+ Gemfile.lock
13
+ .env
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.5.0
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.5.0
5
+
6
+ before_install: gem install bundler -v 1.16.4
7
+
8
+ install: bundle install
9
+
10
+ script: bundle exec rspec
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in google_maps_juice.gemspec
6
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Algonauti srl
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,207 @@
1
+ # GoogleMapsJuice
2
+
3
+ **Drink Google Maps Services with ease!** :tropical_drink: :earth_americas:
4
+
5
+ [![Build Status](https://travis-ci.org/algonauti/google_maps_juice.svg?branch=master)](https://travis-ci.org/algonauti/google_maps_juice)
6
+ [![Coverage Status](https://coveralls.io/repos/github/algonauti/google_maps_juice/badge.svg?branch=master)](https://coveralls.io/github/algonauti/google_maps_juice?branch=master)
7
+
8
+ This gem aims at progressively covering a fair amount of those widely-used services that are part of the Google Maps Platform, such as: [Geocoding](https://developers.google.com/maps/documentation/geocoding/intro), [Time Zone](https://developers.google.com/maps/documentation/timezone/intro), [Directions](https://developers.google.com/maps/documentation/directions/intro), etc. with some key ideas:
9
+
10
+ 1. Allowing "standard" requests, meaning: sending the same params documented by Google.
11
+ 2. Allowing "smart" requests, meaning: with more "developer-friendly" params, and/or improved error handling.
12
+ 3. Return full Google responses, but also provide methods to easily inspect the most relevant info.
13
+ 4. Provide error handling.
14
+
15
+
16
+ `GoogleMapsJuice` currently covers:
17
+
18
+ * Geocoding
19
+ * Time Zone
20
+
21
+ Contributors are welcome!
22
+
23
+
24
+ ## Installation
25
+
26
+ Add this line to your application's Gemfile:
27
+
28
+ ```ruby
29
+ gem 'google_maps_juice'
30
+ ```
31
+
32
+ And then execute:
33
+
34
+ $ bundle
35
+
36
+ Or install it yourself as:
37
+
38
+ $ gem install google_maps_juice
39
+
40
+
41
+ ## Google API Key
42
+
43
+ You can set your Google API key with the following one-liner:
44
+
45
+ ```ruby
46
+ GoogleMapsJuice.configure { |c| c.api_key = 'my-api-key' }`
47
+ ```
48
+
49
+ In a Rails application, that would typically go in an initializer.
50
+
51
+
52
+ ### Multiple API keys
53
+
54
+ If you need to use multiple API keys, you have two options:
55
+
56
+ * a) pass an `api_key` named param to the endpoint class method, e.g.
57
+
58
+ ```ruby
59
+ GoogleMapsJuice::Geocoding.geocode(params, api_key: 'my-api-key')
60
+ ```
61
+
62
+ * b) create your own `GoogleMapsJuice::Client` instance(s) and use it to create your endpoint object(s), e.g.
63
+
64
+ ```ruby
65
+ client = GoogleMapsJuice::Client.new(api_key: 'my-api-key')
66
+ geocoding = GoogleMapsJuice::Geocoding.new(client)
67
+ response = geocoding.geocode(params)
68
+ ```
69
+
70
+ This is especially useful in some "hybrid" scenario, where an API key is shared by a group of requests, but another group uses a different key: a `client` object would then be instantiated and reused for each group.
71
+
72
+
73
+ ## Error Handling
74
+
75
+ If Google servers respond with a non-successful HTTP status code, i.e. `4xx` or `5xx`, a `GoogleMapsJuice::Error` is raised with a message of the form `'HTTP 503 - Error details as returned by the server'`.
76
+
77
+ API errors are also handled, based on the `status` attribute of Google's JSON response, and the optional `error_message` attribute.
78
+
79
+ * `GoogleMapsJuice::ZeroResults` is raised when `status` is `'ZERO_RESULTS'`
80
+ * `GoogleMapsJuice::ApiLimitError` is raised when `status` is `'OVER_DAILY_LIMIT'` or `'OVER_QUERY_LIMIT'`
81
+ * `GoogleMapsJuice::Error` is raised when `status` is not `OK` with a message of the form `API <status> - <error_message>`
82
+
83
+
84
+ ## Geocoding
85
+
86
+ ### Standard Geocoding
87
+
88
+ The simplest geocoding requests accept an address:
89
+
90
+ ```ruby
91
+ response = GoogleMapsJuice::Geocoding.geocode(address: '8955 Lantana Rd, Lake Worth, FL 33467, USA')
92
+ ```
93
+
94
+ Supported params are the ones accepted by Google's endpoint: `address`, `components`, `bounds`, `language`, `region`; at least one between `address` and `components` is required. Learn more [here](https://developers.google.com/maps/documentation/geocoding/intro#geocoding). `GoogleMapsJuice` will raise an `ArgumentError` if some unsupported param is passed, or when none of the required params are passed.
95
+
96
+
97
+ ### Smart Geocoding
98
+
99
+ **Motivation**
100
+
101
+ For best geocoding results, the `address` param should be formatted according to the local language. This is often a hard task for an application that needs to geocode addresses stored as separate fields. Luckily, Google offers the `components` param which accepts individual address fields; however, it's annoying to build it and it's not fault tolerant. For example, an error on `postal_code` makes geocoding of a whole address fail.
102
+
103
+ Purpose of `i_geocode` method is twofold:
104
+
105
+ 1. Providing a simpler method interface for leveraging Google's `components` param
106
+ 2. Providing an approximate geocoding result when some address component is wrong
107
+
108
+ Here an example call with all supported params:
109
+
110
+ ```ruby
111
+ response = GoogleMapsJuice::Geocoding.i_geocode(
112
+ {
113
+ address: '8955 Lantana Rd',
114
+ locality: 'Lake Worth',
115
+ administrative_area: 'FL',
116
+ postal_code: '33467',
117
+ country: 'US'
118
+ }, sleep_before_retry: 0.15
119
+ )
120
+ ```
121
+
122
+ **Accepted params:**
123
+
124
+ * At least one between `address` and `country` is required
125
+ * `locality`, `administrative_area`, `postal_code` and `country` expect the same content as described in [Component Filtering](https://developers.google.com/maps/documentation/geocoding/intro#ComponentFiltering)
126
+ * `address` can also include more info than street number and name, as long as they do not contrast with other params passed
127
+ * An optional `sleep_before_retry` param sets seconds between geocoding attempts (see below); defaults to zero.
128
+ * `GoogleMapsJuice` will raise an `ArgumentError` if some unsupported param is passed, or when none of the required params are passed.
129
+
130
+ **How it works**
131
+
132
+ On its 1st attempt, `i_geocode` sends all received params to Google's endpoint, properly formatted. If a `GoogleMapsJuice::ZeroResults` is raised, it removes a param and retries until no error is raised. Params are removed in the following order:
133
+
134
+ * `postal_code`
135
+ * `address`
136
+ * `locality`
137
+ * `administrative_area`
138
+
139
+ As a consequence:
140
+
141
+ * In the best case, `i_geocode` will send 1 request to Google API
142
+ * In the worst case, `i_geocode` will send 4 requests to Google API
143
+
144
+
145
+ ### Geocoding Response
146
+
147
+ Both `geocode` and `i_geocode` methods return a `GoogleMapsJuice::Geocoding::Response`. It's a `Hash` representation of Google's JSON response. However, it also provides many useful methods:
148
+
149
+ * `latitude`, `longitude`: geographic coordinates as `float` numbers
150
+
151
+ * `street_number`, `route`, `locality`, `postal_code`, `administrative_area_level_1`, `country`: all of these methods return a `Hash` with 2 keys: `'short_name'` and `'long_name'`
152
+
153
+ * `partial_match?`: boolean, `true` if some param (of the last geocoding attempt) partially matched
154
+
155
+ * `precision`: can be one of: `'street_number'`, `'route'`, `'locality'`, `'postal_code'`, `'administrative_area_level_1'`, `'country'` and represents the most-specific matching component
156
+
157
+
158
+ ## Time Zone
159
+
160
+ [Google's Time Zone API](https://developers.google.com/maps/documentation/timezone/intro#Requests) returns the time zone of a given geographic location; it also accepts a timestamp, in order to determine whether DST should be applied or not.
161
+
162
+ GoogleMapsJuice provides the `GoogleMapsJuice::Timezone.by_location` method. Compared to Google's raw API request, it provides simpler params and some validations, in order to avoid sending requests when they would fail for sure (and then save money!) - to learn more see `spec/unit/timezone_spec.rb`.
163
+
164
+ **Accepted params:**
165
+
166
+ * Both `latitude` and `longitude` are mandatory
167
+ * `timestamp` is optional and defaults to `Time.now`
168
+ * `language` is optional
169
+
170
+
171
+ ### Time Zone Response
172
+
173
+ The `by_location` method returns a `GoogleMapsJuice::Timezone::Response`. It's a `Hash` representation of Google's JSON response. However, it also provides a few useful methods:
174
+
175
+ * `timezone_id`: unique name as defined in [IANA Time Zone Database](https://www.iana.org/time-zones)
176
+
177
+ * `timezone_name`: the long form name of the time zone
178
+
179
+ * `raw_offset`: the offset from UTC in seconds
180
+
181
+ * `dst_offset`: the offset for daylight-savings time in seconds
182
+
183
+
184
+ ## Development
185
+
186
+ After checking out the repo, run `bin/setup` to install dependencies. Create a `.env` file and save your Google API key there; if you want to use a different key for testing, put it in `.env.test` and it will override the one in `.env`.
187
+
188
+ Run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
189
+
190
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags and push the `.gem` file to [rubygems.org](https://rubygems.org).
191
+
192
+
193
+ ### Implementing a new endpoint
194
+
195
+ All endpoints must be subclasses of `GoogleMapsJuice::Endpoint`; methods that implement "standard" Google API calls have a common structure, described by the `invoke` method in the `SomeEndpoint` test class in `spec/unit/endpoint_spec.rb`.
196
+
197
+ All new endpoints' methods must return subclasses of `GoogleMapsJuice::Endpoint::Response` as their response objects, since it contains methods needed for error handling.
198
+
199
+
200
+ ## Contributing
201
+
202
+ Bug reports and pull requests are welcome on GitHub at https://github.com/algonauti/google_maps_juice.
203
+
204
+
205
+ ## License
206
+
207
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "google_maps_juice"
5
+ require 'dotenv/load'
6
+
7
+ Dotenv.overload('.env', '.env.development')
8
+
9
+ GoogleMapsJuice.configure do |config|
10
+ config.api_key = ENV.fetch('API_KEY')
11
+ end
12
+
13
+ # You can add fixtures and/or initialization code here to make experimenting
14
+ # with your gem easier. You can also use a different console, if you like.
15
+
16
+ # (If you use this, don't forget to add pry to your Gemfile!)
17
+ # require "pry"
18
+ # Pry.start
19
+
20
+ require "irb"
21
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/env-example ADDED
@@ -0,0 +1 @@
1
+ API_KEY=
@@ -0,0 +1,35 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "google_maps_juice/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "google_maps_juice"
8
+ spec.version = GoogleMapsJuice::VERSION
9
+ spec.authors = ["Algonauti srl"]
10
+ spec.email = ["info@algonauti.com"]
11
+
12
+ spec.summary = "Client for popular Google Maps API Services: Geocoding, Time Zones"
13
+ spec.description = "Put Google Maps APIs in a spin-dryer and drink their juice: Geocoding, Time Zones, ...and more upcoming!"
14
+ spec.homepage = "https://github.com/algonauti/google_maps_juice"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+
21
+ spec.bindir = "exe"
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.add_dependency 'activesupport', '~> 5.2'
26
+ spec.add_dependency 'excon'
27
+
28
+ spec.add_development_dependency 'bundler', '~> 1.16'
29
+ spec.add_development_dependency 'rake', '~> 12.3'
30
+ spec.add_development_dependency 'rspec', '~> 3.5'
31
+ spec.add_development_dependency 'webmock', '~> 3.4'
32
+ spec.add_development_dependency 'dotenv', '~> 2.5'
33
+ spec.add_development_dependency 'vcr', '~> 4.0'
34
+ spec.add_development_dependency 'coveralls', '~> 0.8'
35
+ end
@@ -0,0 +1,21 @@
1
+ require 'active_support/core_ext/object'
2
+ require 'active_support/dependencies/autoload'
3
+
4
+ require 'excon'
5
+ require 'json'
6
+
7
+ require 'google_maps_juice/configuration'
8
+ require 'google_maps_juice/version'
9
+
10
+ module GoogleMapsJuice
11
+ extend ActiveSupport::Autoload
12
+
13
+ autoload :Client, 'google_maps_juice/client'
14
+ autoload :Endpoint, 'google_maps_juice/endpoint'
15
+ autoload :Geocoding, 'google_maps_juice/geocoding'
16
+ autoload :Timezone, 'google_maps_juice/timezone'
17
+
18
+ class Error < Exception; end
19
+ class ApiLimitError < Exception; end
20
+ class ZeroResults < Exception; end
21
+ end
@@ -0,0 +1,39 @@
1
+ module GoogleMapsJuice
2
+ class Client
3
+ attr_reader :api_key, :connection
4
+
5
+ class << self
6
+ def get(endpoint, params, api_key: GoogleMapsJuice.config.api_key)
7
+ self.new(api_key: api_key).get(endpoint, params)
8
+ end
9
+ end
10
+
11
+ API_HOST = 'https://maps.googleapis.com'
12
+ API_PATH = '/maps/api'
13
+
14
+ def initialize(api_key: GoogleMapsJuice.config.api_key)
15
+ @api_key = api_key
16
+ @connection = Excon.new(API_HOST)
17
+ end
18
+
19
+ def get(endpoint, params)
20
+ full_params = (params || Hash.new).merge({ key: api_key })
21
+ response = connection.get(
22
+ path: "#{API_PATH}#{endpoint}",
23
+ query: full_params
24
+ )
25
+ if healthy_http_status?(response.status)
26
+ response.body
27
+ else
28
+ msg = "HTTP #{response.status}"
29
+ msg += " - #{response.body}" if response.body.present?
30
+ raise GoogleMapsJuice::Error, msg
31
+ end
32
+ end
33
+
34
+ def healthy_http_status?(status)
35
+ /^[123]\d{2}$/.match?("#{status}")
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,5 @@
1
+ require 'active_support/configurable'
2
+
3
+ module GoogleMapsJuice
4
+ include ActiveSupport::Configurable
5
+ end
@@ -0,0 +1,86 @@
1
+ module GoogleMapsJuice
2
+ class Endpoint
3
+
4
+ def initialize(client)
5
+ @client = client
6
+ end
7
+
8
+ protected
9
+
10
+ def detect_errors(response)
11
+ raise ArgumentError, 'GoogleMapsJuice::Endpoint::Response argument expected' unless response.is_a?(Response)
12
+ if response.zero_results?
13
+ raise GoogleMapsJuice::ZeroResults
14
+ elsif response.limit_error?
15
+ raise GoogleMapsJuice::ApiLimitError, build_error_message(response)
16
+ elsif response.error?
17
+ raise GoogleMapsJuice::Error, "API #{build_error_message(response)}"
18
+ else
19
+ response
20
+ end
21
+ end
22
+
23
+ def build_error_message(response)
24
+ msg = response.status
25
+ msg += " - #{response.error_message}" if response.error_message.present?
26
+ end
27
+
28
+ def validate_supported_params(params, supported_keys)
29
+ unsupported_params = params.keys.select do |key|
30
+ !supported_keys.include?(key.to_s)
31
+ end
32
+ if unsupported_params.present?
33
+ raise ArgumentError, "The following params are not supported: #{unsupported_params.join(', ')}"
34
+ end
35
+ end
36
+
37
+ def validate_required_params(params, required_keys, check_mode)
38
+ required_params_present = required_keys.send("#{check_mode}?") do |key|
39
+ params.keys.map(&:to_s).include?(key)
40
+ end
41
+ unless required_params_present
42
+ verb = check_mode == 'one' ? 'is' : 'are'
43
+ raise ArgumentError, "#{check_mode.capitalize} of the following params #{verb} required: #{required_keys.join(', ')}"
44
+ end
45
+ end
46
+
47
+
48
+ %w( any one all ).each do |check_mode|
49
+
50
+ define_method "validate_#{check_mode}_required_params" do |params, required_keys|
51
+ validate_required_params(params, required_keys, check_mode)
52
+ end
53
+
54
+ end
55
+
56
+
57
+ class Response < Hash
58
+
59
+ def status
60
+ self['status']
61
+ end
62
+
63
+ def error?
64
+ status.upcase != 'OK'
65
+ end
66
+
67
+ def zero_results?
68
+ status.upcase == 'ZERO_RESULTS'
69
+ end
70
+
71
+ def limit_error?
72
+ %w( OVER_DAILY_LIMIT OVER_QUERY_LIMIT ).include?(status.upcase)
73
+ end
74
+
75
+ def error_message
76
+ self['error_message']
77
+ end
78
+
79
+ def results
80
+ self['results']
81
+ end
82
+
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,99 @@
1
+ module GoogleMapsJuice
2
+ class Geocoding < Endpoint
3
+
4
+ ENDPOINT = '/geocode'
5
+
6
+ autoload :Response, 'google_maps_juice/geocoding/response'
7
+
8
+ class << self
9
+ def geocode(params, api_key: GoogleMapsJuice.config.api_key)
10
+ client = GoogleMapsJuice::Client.new(api_key: api_key)
11
+ self.new(client).geocode(params)
12
+ end
13
+
14
+ def i_geocode(params, sleep_before_retry: 0, api_key: GoogleMapsJuice.config.api_key)
15
+ client = GoogleMapsJuice::Client.new(api_key: api_key)
16
+ self.new(client).i_geocode(params, sleep_before_retry: sleep_before_retry)
17
+ end
18
+ end
19
+
20
+
21
+ def geocode(params)
22
+ validate_geocode_params(params)
23
+ response_text = @client.get("#{ENDPOINT}/json", params)
24
+ response = JSON.parse(response_text, object_class: Response)
25
+ detect_errors(response)
26
+ end
27
+
28
+ def i_geocode(params, sleep_before_retry: 0)
29
+ validate_i_geocode_params(params)
30
+ response = nil
31
+ removable_keys = [:administrative_area, :locality, :address, :postal_code]
32
+ begin
33
+ request_params = build_request_params(params)
34
+ response = geocode(request_params)
35
+ rescue ZeroResults => e
36
+ deleted_param = nil
37
+ while removable_keys.present? && deleted_param.nil?
38
+ key = removable_keys.pop
39
+ deleted_param = params.delete(key)
40
+ end
41
+ if deleted_param.present?
42
+ sleep sleep_before_retry
43
+ retry
44
+ else
45
+ raise e
46
+ end
47
+ end
48
+ response
49
+ end
50
+
51
+ def validate_geocode_params(params)
52
+ raise ArgumentError, 'Hash argument expected' unless params.is_a?(Hash)
53
+
54
+ supported_keys = %w( address components bounds language region )
55
+ validate_supported_params(params, supported_keys)
56
+
57
+ required_keys = %w( address components )
58
+ validate_any_required_params(params, required_keys)
59
+ end
60
+
61
+ def validate_i_geocode_params(params)
62
+ raise ArgumentError, 'Hash argument expected' unless params.is_a?(Hash)
63
+
64
+ supported_keys = %w( address locality postal_code administrative_area country language)
65
+ validate_supported_params(params, supported_keys)
66
+
67
+ required_keys = %w( address country )
68
+ validate_any_required_params(params, required_keys)
69
+ end
70
+
71
+ def build_request_params(i_geocode_params)
72
+ req_params = Hash.new
73
+
74
+ [:address, :language].each do |key|
75
+ if i_geocode_params[key].present?
76
+ req_params[key] = i_geocode_params[key]
77
+ end
78
+ end
79
+
80
+ components = build_components_param(i_geocode_params, keys:
81
+ [:locality, :postal_code, :administrative_area, :country]
82
+ )
83
+ if components.present?
84
+ req_params[:components] = components
85
+ end
86
+
87
+ req_params
88
+ end
89
+
90
+ def build_components_param(params, keys: [])
91
+ keys.map do |key|
92
+ if params[key].present?
93
+ "#{key}:#{params[key]}"
94
+ end
95
+ end.compact.join('|')
96
+ end
97
+
98
+ end
99
+ end
@@ -0,0 +1,70 @@
1
+ require 'active_support/core_ext/hash/slice'
2
+
3
+ module GoogleMapsJuice
4
+ class Geocoding::Response < GoogleMapsJuice::Endpoint::Response
5
+
6
+ class << self
7
+ def precisions
8
+ %w( street_number route postal_code locality administrative_area_level_1
9
+ administrative_area_level_2 administrative_area_level_3 country )
10
+ end
11
+ end
12
+
13
+ def latitude
14
+ location['lat']
15
+ end
16
+
17
+ def longitude
18
+ location['lng']
19
+ end
20
+
21
+ def location
22
+ result.dig('geometry', 'location')
23
+ end
24
+
25
+ def partial_match?
26
+ result['partial_match'] == true
27
+ end
28
+
29
+ def result
30
+ results.first
31
+ end
32
+
33
+ def address_components
34
+ result['address_components']
35
+ end
36
+
37
+ def precision
38
+ self.class.precisions.find do |type|
39
+ address_components.any? do |comp|
40
+ comp['types'].include?(type)
41
+ end
42
+ end
43
+ end
44
+
45
+
46
+ %w( street_number
47
+ route
48
+ locality
49
+ sublocality
50
+ postal_code
51
+ administrative_area_level_1
52
+ administrative_area_level_2
53
+ administrative_area_level_3
54
+ country ).each do |type|
55
+
56
+ define_method(type) do
57
+ addr_component_by(type: type)&.slice('long_name', 'short_name')
58
+ end
59
+
60
+ end
61
+
62
+
63
+ private
64
+
65
+ def addr_component_by(type: '')
66
+ address_components.find { |ac| ac['types'].include?(type) }
67
+ end
68
+
69
+ end
70
+ end
@@ -0,0 +1,82 @@
1
+ module GoogleMapsJuice
2
+ class Timezone < Endpoint
3
+
4
+ ENDPOINT = '/timezone'
5
+
6
+ class << self
7
+ def by_location(params, api_key: GoogleMapsJuice.config.api_key)
8
+ client = GoogleMapsJuice::Client.new(api_key: api_key)
9
+ self.new(client).by_location(params)
10
+ end
11
+ end
12
+
13
+ def by_location(params)
14
+ validate_params(params)
15
+ response_text = @client.get("#{ENDPOINT}/json", build_request_params(params))
16
+ response = JSON.parse(response_text, object_class: Response)
17
+ detect_errors(response)
18
+ end
19
+
20
+ def validate_params(params)
21
+ raise ArgumentError, 'Hash argument expected' unless params.is_a?(Hash)
22
+
23
+ supported_keys = %w( latitude longitude timestamp language )
24
+ validate_supported_params(params, supported_keys)
25
+
26
+ required_keys = %w( latitude longitude )
27
+ validate_all_required_params(params, required_keys)
28
+
29
+ validate_timestamp_param(params)
30
+ validate_location_params(params)
31
+ end
32
+
33
+ def validate_timestamp_param(params)
34
+ if params.has_key?(:timestamp)
35
+ raise ArgumentError, 'Timestamp must be a Time instance' unless params[:timestamp].is_a?(Time)
36
+ end
37
+ end
38
+
39
+ def validate_location_params(params)
40
+ if params[:latitude].abs > 90
41
+ raise ArgumentError, 'Wrong latitude value'
42
+ end
43
+ if params[:longitude].abs > 180
44
+ raise ArgumentError, 'Wrong longitude value'
45
+ end
46
+ end
47
+
48
+ def build_request_params(params)
49
+ seconds_since_epoch = (params[:timestamp] || Time.now).to_i
50
+ {
51
+ location: "#{params[:latitude]},#{params[:longitude]}",
52
+ timestamp: seconds_since_epoch
53
+ }.tap do |req_params|
54
+ if params[:language]
55
+ req_params[:language] = params[:language]
56
+ end
57
+ end
58
+ end
59
+
60
+
61
+ class Response < GoogleMapsJuice::Endpoint::Response
62
+
63
+ def timezone_id
64
+ self['timeZoneId']
65
+ end
66
+
67
+ def timezone_name
68
+ self['timeZoneName']
69
+ end
70
+
71
+ def raw_offset
72
+ self['rawOffset']
73
+ end
74
+
75
+ def dst_offset
76
+ self['dstOffset']
77
+ end
78
+
79
+ end
80
+
81
+ end
82
+ end
@@ -0,0 +1,3 @@
1
+ module GoogleMapsJuice
2
+ VERSION = "1.0.0"
3
+ end
metadata ADDED
@@ -0,0 +1,191 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: google_maps_juice
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Algonauti srl
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-10-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: excon
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.16'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.16'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '12.3'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '12.3'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.5'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.5'
83
+ - !ruby/object:Gem::Dependency
84
+ name: webmock
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.4'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.4'
97
+ - !ruby/object:Gem::Dependency
98
+ name: dotenv
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '2.5'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '2.5'
111
+ - !ruby/object:Gem::Dependency
112
+ name: vcr
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '4.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '4.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: coveralls
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '0.8'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '0.8'
139
+ description: 'Put Google Maps APIs in a spin-dryer and drink their juice: Geocoding,
140
+ Time Zones, ...and more upcoming!'
141
+ email:
142
+ - info@algonauti.com
143
+ executables: []
144
+ extensions: []
145
+ extra_rdoc_files: []
146
+ files:
147
+ - ".gitignore"
148
+ - ".rspec"
149
+ - ".ruby-version"
150
+ - ".travis.yml"
151
+ - Gemfile
152
+ - LICENSE.txt
153
+ - README.md
154
+ - Rakefile
155
+ - bin/console
156
+ - bin/setup
157
+ - env-example
158
+ - google_maps_juice.gemspec
159
+ - lib/google_maps_juice.rb
160
+ - lib/google_maps_juice/client.rb
161
+ - lib/google_maps_juice/configuration.rb
162
+ - lib/google_maps_juice/endpoint.rb
163
+ - lib/google_maps_juice/geocoding.rb
164
+ - lib/google_maps_juice/geocoding/response.rb
165
+ - lib/google_maps_juice/timezone.rb
166
+ - lib/google_maps_juice/version.rb
167
+ homepage: https://github.com/algonauti/google_maps_juice
168
+ licenses:
169
+ - MIT
170
+ metadata: {}
171
+ post_install_message:
172
+ rdoc_options: []
173
+ require_paths:
174
+ - lib
175
+ required_ruby_version: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - ">="
178
+ - !ruby/object:Gem::Version
179
+ version: '0'
180
+ required_rubygems_version: !ruby/object:Gem::Requirement
181
+ requirements:
182
+ - - ">="
183
+ - !ruby/object:Gem::Version
184
+ version: '0'
185
+ requirements: []
186
+ rubyforge_project:
187
+ rubygems_version: 2.7.3
188
+ signing_key:
189
+ specification_version: 4
190
+ summary: 'Client for popular Google Maps API Services: Geocoding, Time Zones'
191
+ test_files: []