GoogleMapsJuice
Drink Google Maps Services with ease! 🍹 🌎
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, Time Zone, Directions, etc. with some key ideas:
- Allowing "standard" requests, meaning: sending the same params documented by Google.
- Allowing "smart" requests, meaning: with more "developer-friendly" params, and/or improved error handling.
- Return full Google responses, but also provide methods to easily inspect the most relevant info.
- Provide error handling.
GoogleMapsJuice
currently covers:
- Geocoding
- Time Zone
- Directions
Contributors are welcome!
Installation
Add this line to your application's Gemfile:
gem 'google_maps_juice'
And then execute:
$ bundle
Or install it yourself as:
$ gem install google_maps_juice
Google API Key
You can set your Google API key with the following one-liner:
GoogleMapsJuice.configure { |c| c.api_key = 'my-api-key' }`
In a Rails application, that would typically go in an initializer.
Multiple API keys
If you need to use multiple API keys, you have two options:
- a) pass an
api_key
named param to the endpoint class method, e.g.
GoogleMapsJuice::Geocoding.geocode(params, api_key: 'my-api-key')
- b) create your own
GoogleMapsJuice::Client
instance(s) and use it to create your endpoint object(s), e.g.
client = GoogleMapsJuice::Client.new(api_key: 'my-api-key')
geocoding = GoogleMapsJuice::Geocoding.new(client)
response = geocoding.geocode(params)
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.
Error Handling
If Google servers respond with a non-successful HTTP status code, i.e. 4xx
or 5xx
, a GoogleMapsJuice::ResponseError
is raised with a message of the form 'HTTP 503 - Error details as returned by the server'
.
API errors are also handled, based on the status
attribute of Google's JSON response, and the optional error_message
attribute.
-
GoogleMapsJuice::ZeroResults
is raised whenstatus
is'ZERO_RESULTS'
-
GoogleMapsJuice::ApiLimitError
is raised whenstatus
is'OVER_DAILY_LIMIT'
or'OVER_QUERY_LIMIT'
-
GoogleMapsJuice::ResponseError
is raised whenstatus
is notOK
with a message of the formAPI <status> - <error_message>
Geocoding
Standard Geocoding
The simplest geocoding requests accept an address:
response = GoogleMapsJuice::Geocoding.geocode(address: '8955 Lantana Rd, Lake Worth, FL 33467, USA')
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. GoogleMapsJuice
will raise an ArgumentError
if some unsupported param is passed, or when none of the required params are passed.
Smart Geocoding
Motivation
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.
Purpose of i_geocode
method is twofold:
- Providing a simpler method interface for leveraging Google's
components
param - Providing an approximate geocoding result when some address component is wrong
Here an example call with all supported params:
response = GoogleMapsJuice::Geocoding.i_geocode(
{
address: '8955 Lantana Rd',
locality: 'Lake Worth',
administrative_area: 'FL',
postal_code: '33467',
country: 'US'
}, sleep_before_retry: 0.15
)
Accepted params:
- At least one between
address
andcountry
is required -
locality
,administrative_area
,postal_code
andcountry
expect the same content as described in Component Filtering -
address
can also include more info than street number and name, as long as they do not contrast with other params passed - An optional
sleep_before_retry
param sets seconds between geocoding attempts (see below); defaults to zero. -
GoogleMapsJuice
will raise anArgumentError
if some unsupported param is passed, or when none of the required params are passed.
How it works
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:
postal_code
address
locality
administrative_area
As a consequence:
- In the best case,
i_geocode
will send 1 request to Google API - In the worst case,
i_geocode
will send 4 requests to Google API
Geocoding Response
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:
-
latitude
,longitude
: geographic coordinates asfloat
numbers -
street_number
,route
,locality
,postal_code
,administrative_area_level_1
,country
: all of these methods return aHash
with 2 keys:'short_name'
and'long_name'
-
partial_match?
: boolean,true
if some param (of the last geocoding attempt) partially matched -
precision
: can be one of:'street_number'
,'route'
,'locality'
,'postal_code'
,'administrative_area_level_1'
,'country'
and represents the most-specific matching component
Time Zone
Google's Time Zone API 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.
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
.
Accepted params:
- Both
latitude
andlongitude
are mandatory -
timestamp
is optional and defaults toTime.now
-
language
is optional
Time Zone Response
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:
-
timezone_id
: unique name as defined in IANA Time Zone Database -
timezone_name
: the long form name of the time zone -
raw_offset
: the offset from UTC in seconds -
dst_offset
: the offset for daylight-savings time in seconds
Directions
Google's Directions API returns the possible routes of given origin and destination geographic locations; Google's API accepts address, textual latitude/longitude value, or place ID of which you wish to calculate directions. Currently this gem implements only latitude/longitude mode.
GoogleMapsJuice
will raise an ArgumentError
if some unsupported param is passed, or when none of the required params are passed.
response = GoogleMapsJuice::Directions.find(origin: '41.8892732,12.4921921', destination: '41.9016488,12.4583003')
Compared to Google's raw API request, it provides validation of both origin and destination, in order to avoid sending requests when they would fail for sure - to learn more see spec/unit/directions_spec.rb
.
Accepted params:
- Both
origin
anddestination
are mandatory -
origin
is composed bylatitude
andlongitude
, comma separated float values -
destination
same format asorigin
Directions Response
The find
method returns a GoogleMapsJuice::Directions::Response
. It's a Hash
representation of Google's JSON response. However, it also provides a few useful methods:
-
results
: theHash
raw result -
routes
: anArray
ofGoogleMapsJuice::Directions::Response::Route
objects -
first
: the firstRoute
of theroutes
List
As described in Google's Directions API, the response contains all possible routes. Each route has some attributes and one or more legs, wich in turn have one or more steps.
If no waypoints are passed, the route response will contain a single leg. Since GoogleMapsJuice::Directions
doesn't handles waypoints yet, only the first leg is considered for each route.
The GoogleMapsJuice::Directions::Response::Route
is a representation of a response route and provides methods to access all route's attributes:
-
summary
: a brief description of the route -
legs
: all legs of the route, generally a single one -
steps
: all steps of the first route's leg -
duration
: time duration of the first route's leg -
distance
: distance between origin and destination of first route's leg -
start_location
:latitude
/longitude
of the origin first route's leg -
end_location
:latitude
/longitude
of the destination first route's leg -
start_address
: address of the origin first route's leg -
end_address
: address of the destination first route's leg
Development
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
.
Run rake spec
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
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.
Implementing a new endpoint
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
.
All new endpoints' methods must return subclasses of GoogleMapsJuice::Endpoint::Response
as their response objects, since it contains methods needed for error handling.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/algonauti/google_maps_juice.
License
The gem is available as open source under the terms of the MIT License.