google-maps 2.2.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +20 -0
- data/.travis.yml +6 -1
- data/Gemfile +3 -1
- data/{LICENSE.mkd → LICENSE.md} +0 -0
- data/README.md +216 -0
- data/Rakefile +5 -3
- data/google-maps.gemspec +24 -21
- data/lib/google_maps.rb +48 -0
- data/lib/google_maps/api.rb +104 -0
- data/lib/google_maps/configuration.rb +90 -0
- data/lib/google_maps/location.rb +34 -0
- data/lib/{google-maps → google_maps}/logger.rb +5 -4
- data/lib/{google-maps → google_maps}/place.rb +21 -18
- data/lib/google_maps/result.rb +11 -0
- data/lib/google_maps/route.rb +45 -0
- data/lib/google_maps/version.rb +7 -0
- data/spec/google_maps/api_spec.rb +82 -0
- data/spec/google_maps/logger_spec.rb +17 -0
- data/spec/google_maps/place_details_spec.rb +40 -0
- data/spec/google_maps/place_spec.rb +47 -0
- data/spec/google_maps/route_spec.rb +53 -0
- data/spec/google_maps_spec.rb +174 -0
- data/spec/spec_helper.rb +5 -6
- metadata +97 -57
- data/README.mkd +0 -39
- data/lib/google-maps.rb +0 -44
- data/lib/google-maps/api.rb +0 -89
- data/lib/google-maps/configuration.rb +0 -70
- data/lib/google-maps/location.rb +0 -25
- data/lib/google-maps/route.rb +0 -38
- data/lib/google-maps/version.rb +0 -5
- data/spec/google-maps/api_spec.rb +0 -103
- data/spec/google-maps/logger_spec.rb +0 -15
- data/spec/google-maps/place_details_spec.rb +0 -32
- data/spec/google-maps/place_spec.rb +0 -45
- data/spec/google-maps/route_spec.rb +0 -52
- data/spec/google-maps_spec.rb +0 -103
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Google
|
4
|
+
module Maps
|
5
|
+
class InvalidConfigurationError < StandardError; end
|
6
|
+
# Defines constants and methods related to configuration
|
7
|
+
module Configuration
|
8
|
+
# An array of valid keys in the options hash when configuring an {Google::Maps::API}
|
9
|
+
VALID_OPTIONS_KEYS = %i[
|
10
|
+
end_point authentication_mode client_id client_secret format
|
11
|
+
directions_service places_service geocode_service
|
12
|
+
api_key default_language place_details_service default_params
|
13
|
+
].freeze
|
14
|
+
|
15
|
+
API_KEY = 'api_key'.freeze
|
16
|
+
DIGITAL_SIGNATURE = 'digital_signature'.freeze
|
17
|
+
|
18
|
+
# By default, set "https://maps.googleapis.com/maps/api/" as the server
|
19
|
+
DEFAULT_END_POINT = 'https://maps.googleapis.com/maps/api/'.freeze
|
20
|
+
|
21
|
+
DEFAULT_DIRECTIONS_SERVICE = 'directions'.freeze
|
22
|
+
DEFAULT_PLACES_SERVICE = 'place/autocomplete'.freeze
|
23
|
+
DEFAULT_PLACE_DETAILS_SERVICE = 'place/details'.freeze
|
24
|
+
DEFAULT_GEOCODE_SERVICE = 'geocode'.freeze
|
25
|
+
|
26
|
+
DEFAULT_FORMAT = 'json'.freeze
|
27
|
+
|
28
|
+
# default language
|
29
|
+
DEFAULT_LANGUAGE = :en
|
30
|
+
|
31
|
+
# params to send which each request configured per service
|
32
|
+
# ie.: {places_service: {location: "52.0910,5.1220", radius: 300000}}
|
33
|
+
DEFAULT_PARAMS = {}.freeze
|
34
|
+
|
35
|
+
# @private
|
36
|
+
attr_accessor(*VALID_OPTIONS_KEYS)
|
37
|
+
|
38
|
+
# When this module is extended, set all configuration options to their default values
|
39
|
+
def self.extended(base)
|
40
|
+
base.reset
|
41
|
+
end
|
42
|
+
|
43
|
+
# Convenience method to allow configuration options to be set in a block
|
44
|
+
def configure
|
45
|
+
yield self
|
46
|
+
validate_config
|
47
|
+
end
|
48
|
+
|
49
|
+
def validate_config
|
50
|
+
return validate_api_key if authentication_mode == API_KEY
|
51
|
+
return validate_digital_signature if authentication_mode == DIGITAL_SIGNATURE
|
52
|
+
|
53
|
+
raise Google::Maps::InvalidConfigurationError, 'No valid authentication mode provided'
|
54
|
+
end
|
55
|
+
|
56
|
+
def validate_api_key
|
57
|
+
raise Google::Maps::InvalidConfigurationError, 'No API key provided' unless api_key.present?
|
58
|
+
end
|
59
|
+
|
60
|
+
def validate_digital_signature
|
61
|
+
raise Google::Maps::InvalidConfigurationError, 'No client id provided' unless client_id.present?
|
62
|
+
raise Google::Maps::InvalidConfigurationError, 'No client secret provided' unless client_secret.present?
|
63
|
+
end
|
64
|
+
|
65
|
+
# Create a hash of options and their values
|
66
|
+
def options
|
67
|
+
VALID_OPTIONS_KEYS.inject({}) do |option, key|
|
68
|
+
option.merge!(key => send(key))
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Reset all configuration options to defaults
|
73
|
+
def reset
|
74
|
+
self.end_point = DEFAULT_END_POINT
|
75
|
+
self.format = DEFAULT_FORMAT
|
76
|
+
self.directions_service = DEFAULT_DIRECTIONS_SERVICE
|
77
|
+
self.places_service = DEFAULT_PLACES_SERVICE
|
78
|
+
self.place_details_service = DEFAULT_PLACE_DETAILS_SERVICE
|
79
|
+
self.geocode_service = DEFAULT_GEOCODE_SERVICE
|
80
|
+
self.default_language = DEFAULT_LANGUAGE
|
81
|
+
self.default_params = DEFAULT_PARAMS
|
82
|
+
self.authentication_mode = nil
|
83
|
+
self.api_key = nil
|
84
|
+
self.client_id = nil
|
85
|
+
self.client_secret = nil
|
86
|
+
self
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require File.expand_path('api', __dir__)
|
4
|
+
|
5
|
+
module Google
|
6
|
+
module Maps
|
7
|
+
class Location
|
8
|
+
attr_reader :address, :latitude, :longitude
|
9
|
+
alias to_s address
|
10
|
+
|
11
|
+
def initialize(address, latitude, longitude)
|
12
|
+
@address = address
|
13
|
+
@latitude = latitude
|
14
|
+
@longitude = longitude
|
15
|
+
end
|
16
|
+
|
17
|
+
def lat_lng
|
18
|
+
[latitude, longitude]
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.find(address, language = :en)
|
22
|
+
args = { language: language, address: address }
|
23
|
+
|
24
|
+
API.query(:geocode_service, args).results.map do |result|
|
25
|
+
Location.new(
|
26
|
+
result.formatted_address,
|
27
|
+
result.geometry.location.lat,
|
28
|
+
result.geometry.location.lng
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -1,17 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'logger'
|
2
4
|
|
3
5
|
module Google
|
4
6
|
module Maps
|
5
7
|
module Logger
|
6
|
-
|
7
8
|
attr_accessor :logger
|
8
|
-
|
9
|
+
|
9
10
|
def log_file=(file)
|
10
11
|
self.logger = ::Logger.new(file)
|
11
12
|
end
|
12
|
-
|
13
|
+
|
13
14
|
def self.extended(base)
|
14
|
-
base.log_file = RUBY_PLATFORM.index(/mswin(?!ce)|mingw|cygwin|bccwin/) ?
|
15
|
+
base.log_file = RUBY_PLATFORM.index(/mswin(?!ce)|mingw|cygwin|bccwin/) ? 'nul' : '/dev/null'
|
15
16
|
end
|
16
17
|
end
|
17
18
|
end
|
@@ -1,24 +1,23 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require File.expand_path('api', __dir__)
|
2
4
|
|
3
5
|
module Google
|
4
6
|
module Maps
|
5
|
-
|
6
7
|
class Place
|
7
8
|
attr_reader :text, :html, :keyword, :place_id
|
8
|
-
alias
|
9
|
-
alias
|
10
|
-
|
9
|
+
alias to_s text
|
10
|
+
alias to_html html
|
11
|
+
|
11
12
|
def initialize(data, keyword)
|
12
13
|
@text = data.description
|
13
14
|
@place_id = data.place_id
|
14
15
|
@html = highligh_keywords(data, keyword)
|
15
16
|
end
|
16
|
-
|
17
|
-
def self.find(keyword, language=:en)
|
18
|
-
args = {:language => language, :input => keyword }
|
19
|
-
args.merge!(key: Google::Maps.api_key) unless Google::Maps.api_key.nil?
|
20
17
|
|
21
|
-
|
18
|
+
def self.find(keyword, language = :en)
|
19
|
+
args = { language: language, input: keyword }
|
20
|
+
API.query(:places_service, args).predictions.map { |prediction| Place.new(prediction, keyword) }
|
22
21
|
end
|
23
22
|
|
24
23
|
private
|
@@ -57,16 +56,14 @@ module Google
|
|
57
56
|
def address
|
58
57
|
@data.formatted_address
|
59
58
|
end
|
60
|
-
alias
|
59
|
+
alias to_s address
|
61
60
|
|
62
61
|
def address_components
|
63
62
|
AddressComponentsProxy.new(@data.address_components)
|
64
63
|
end
|
65
64
|
|
66
|
-
def self.find(place_id, language
|
67
|
-
args = {:
|
68
|
-
args.merge!(key: Google::Maps.api_key) unless Google::Maps.api_key.nil?
|
69
|
-
|
65
|
+
def self.find(place_id, language = :en)
|
66
|
+
args = { language: language, placeid: place_id }
|
70
67
|
PlaceDetails.new(API.query(:place_details_service, args).result)
|
71
68
|
end
|
72
69
|
|
@@ -75,12 +72,18 @@ module Google
|
|
75
72
|
@address_components = address_components
|
76
73
|
end
|
77
74
|
|
78
|
-
def method_missing(
|
75
|
+
def method_missing(method_name, *args)
|
79
76
|
raise ArgumentError unless args.empty?
|
80
77
|
|
81
78
|
@address_components.find do |component|
|
82
|
-
component.types.first ==
|
83
|
-
end
|
79
|
+
component.types.first == method_name.to_s
|
80
|
+
end || super
|
81
|
+
end
|
82
|
+
|
83
|
+
def respond_to_missing?(method_name, include_private = false)
|
84
|
+
@address_components.any? do |component|
|
85
|
+
component.types.first == method_name.to_s
|
86
|
+
end || super
|
84
87
|
end
|
85
88
|
end
|
86
89
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require File.expand_path('api', __dir__)
|
4
|
+
|
5
|
+
module Google
|
6
|
+
module Maps
|
7
|
+
class Route
|
8
|
+
attr_accessor :from, :to, :options
|
9
|
+
|
10
|
+
def initialize(from, to, options = {})
|
11
|
+
options = { language: options } unless options.is_a? Hash
|
12
|
+
@from = from
|
13
|
+
@to = to
|
14
|
+
@options = { language: :en }.merge(options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_missing(method_name, *args, &block)
|
18
|
+
if route.legs.first.key?(method_name)
|
19
|
+
route.legs.first.send(method_name)
|
20
|
+
else
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def respond_to_missing?(method_name, include_private = false)
|
26
|
+
route.legs.first.key?(method_name) || super
|
27
|
+
end
|
28
|
+
|
29
|
+
def origin_latlong
|
30
|
+
"#{start_location.lat},#{start_location.lng}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def destination_latlong
|
34
|
+
"#{end_location.lat},#{end_location.lng}"
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def route
|
40
|
+
# default to the first returned route (the most efficient one)
|
41
|
+
@route ||= API.query(:directions_service, @options.merge(origin: from, destination: to)).routes.first
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require File.expand_path('../spec_helper', __dir__)
|
4
|
+
|
5
|
+
describe Google::Maps::API do
|
6
|
+
it 'should raise a custom exception when the query fails by net' do
|
7
|
+
HTTPClient.any_instance.unstub(:get_content)
|
8
|
+
|
9
|
+
Google::Maps.end_point = 'http://unknown.tld/'
|
10
|
+
expect { Google::Maps.distance('Amsterdam', 'Deventer') }.to raise_error(Google::Maps::InvalidResponseException)
|
11
|
+
Google::Maps.end_point = 'http://unknown-domain-asdasdasdas123123zxcasd.com/'
|
12
|
+
expect { Google::Maps.distance('Amsterdam', 'Deventer') }.to raise_error(Google::Maps::InvalidResponseException)
|
13
|
+
Google::Maps.end_point = 'http://www.google.com/404'
|
14
|
+
expect { Google::Maps.distance('Amsterdam', 'Deventer') }.to raise_error(Google::Maps::InvalidResponseException)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should raise a custom exception when the query fails by Google' do
|
18
|
+
stub_response('over_query_limit.json')
|
19
|
+
expect { Google::Maps.distance('Amsterdam', 'Deventer') }.to raise_error(Google::Maps::InvalidResponseException)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should raise a custom exception when there are no results' do
|
23
|
+
stub_response('zero-results.json')
|
24
|
+
expect { Google::Maps.distance('Blah blah', 'Jalala') }.to raise_error(Google::Maps::ZeroResultsException)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should raise a custom exception that is rescue-able' do
|
28
|
+
stub_response('zero-results.json')
|
29
|
+
begin
|
30
|
+
Google::Maps.distance('Blah blah', 'Jalala')
|
31
|
+
rescue StandardError => error
|
32
|
+
@error = error
|
33
|
+
ensure
|
34
|
+
expect(@error).not_to be_nil
|
35
|
+
expect(@error).to be_a_kind_of StandardError
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe 'authentication' do
|
40
|
+
context 'with digital signature' do
|
41
|
+
before do
|
42
|
+
Google::Maps.configure do |config|
|
43
|
+
config.authentication_mode = Google::Maps::Configuration::DIGITAL_SIGNATURE
|
44
|
+
config.client_id = 'clientID'
|
45
|
+
config.client_secret = 'vNIXE0xscrmjlyV-12Nj_BvUPaw='
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should sign the url parameters when a client id and premier key is set' do
|
50
|
+
stub_response(
|
51
|
+
'place_details.json',
|
52
|
+
'https://maps.googleapis.com/maps/api/geocode/json?address=New+York&client=clientID&signature=chaRF2hTJKOScPr-RQCEhZbSzIE='
|
53
|
+
)
|
54
|
+
# http://code.google.com/apis/maps/documentation/webservices/index.html#URLSigning
|
55
|
+
|
56
|
+
# Example:
|
57
|
+
# Private Key: vNIXE0xscrmjlyV-12Nj_BvUPaw=
|
58
|
+
# Signature: chaRF2hTJKOScPr-RQCEhZbSzIE=
|
59
|
+
# Client ID: clientID
|
60
|
+
# URL: http://maps.googleapis.com/maps/api/geocode/json?address=New+York&client=clientID
|
61
|
+
Google::Maps::API.query(:geocode_service, address: 'New York')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'with api key' do
|
66
|
+
before do
|
67
|
+
Google::Maps.configure do |config|
|
68
|
+
config.authentication_mode = Google::Maps::Configuration::API_KEY
|
69
|
+
config.api_key = 'api_key123'
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should sign the url parameters when a client id and premier key is set' do
|
74
|
+
stub_response(
|
75
|
+
'place_details.json',
|
76
|
+
'https://maps.googleapis.com/maps/api/geocode/json?address=New+York&api_key=api_key123'
|
77
|
+
)
|
78
|
+
Google::Maps::API.query(:geocode_service, address: 'New York')
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require File.expand_path('../spec_helper', __dir__)
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
describe Google::Maps::Logger do
|
7
|
+
it 'should be able to log messages when a log output is set' do
|
8
|
+
# fake an exception
|
9
|
+
HTTPClient.any_instance.expects(:get_content).raises('test exception')
|
10
|
+
|
11
|
+
# expect the logger to be called once
|
12
|
+
Logger.any_instance.expects(:error).at_least_once
|
13
|
+
|
14
|
+
# trigger the exception
|
15
|
+
expect { Google::Maps.distance('Amsterdam', 'Deventer') }.to raise_error(Google::Maps::InvalidResponseException)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require File.expand_path('../spec_helper', __dir__)
|
4
|
+
|
5
|
+
describe Google::Maps::PlaceDetails do
|
6
|
+
let(:place_id) do
|
7
|
+
'CpQBiAAAAGs4XDizjQoVk9NjuY3ll3aLBLafpDxaFPSJSO7icOj07' \
|
8
|
+
'IRHO4KjjcRIbKEmeSVTcG75kIvwqE7VzA8D7BFvWp8OPwgAiKMveQ' \
|
9
|
+
'QUsTGfJrRG5EVd7J34hY8e5JDbaXEPOMUPIWLfiugwUfQqAImvWQC' \
|
10
|
+
'GrMG1iyOpZfaW22NNhornssEg90uxrLbwLJ7QZhwGIRIQSBc_BlD7' \
|
11
|
+
'mILqQaixzTqE1BoUbNrhbmsZYkIurvK4l9exKBryfKk'
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'given a canned response' do
|
15
|
+
before(:each) do
|
16
|
+
stub_response('place_details.json')
|
17
|
+
@details = Google::Maps::PlaceDetails.find(place_id, :nl)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should have a place_id' do
|
21
|
+
expect(@details.place_id).to eq(place_id)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should have a latlong' do
|
25
|
+
expect(@details.latitude).to eq('-33.866975')
|
26
|
+
expect(@details.longitude).to eq('151.195677')
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'has data containing at least address components' do
|
30
|
+
expect(@details.data.address_components).not_to be_empty
|
31
|
+
end
|
32
|
+
|
33
|
+
context '#address_components' do
|
34
|
+
it 'allows easy access by type' do
|
35
|
+
expect(@details.address_components.postal_code.long_name).to eq '2009'
|
36
|
+
expect(@details.address_components.locality.long_name).to eq 'Pyrmont'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require File.expand_path('../spec_helper', __dir__)
|
4
|
+
|
5
|
+
describe Google::Maps::Place do
|
6
|
+
describe '.find' do
|
7
|
+
subject { Google::Maps::Place.find(keyword, country).first }
|
8
|
+
|
9
|
+
context ':nl' do
|
10
|
+
before { stub_response('deventer-nl.json') }
|
11
|
+
|
12
|
+
let(:keyword) { 'Deventer' }
|
13
|
+
let(:country) { :nl }
|
14
|
+
|
15
|
+
its(:text) { should eq 'Deventer, Nederland' }
|
16
|
+
its(:html) { should eq '<strong>Deventer</strong>, Nederland' }
|
17
|
+
|
18
|
+
context 'keyword with escapeable characters' do
|
19
|
+
let(:keyword) { 'Deventer \\' }
|
20
|
+
let(:country) { :nl }
|
21
|
+
|
22
|
+
its(:text) { should eq 'Deventer, Nederland' }
|
23
|
+
its(:html) { should eq '<strong>Deventer</strong>, Nederland' }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context ':en' do
|
28
|
+
before { stub_response('deventer-en.json') }
|
29
|
+
|
30
|
+
let(:keyword) { 'Deventer' }
|
31
|
+
let(:country) { :en }
|
32
|
+
|
33
|
+
its(:text) { should eq 'Deventer, The Netherlands' }
|
34
|
+
its(:html) { should eq '<strong>Deventer</strong>, The Netherlands' }
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'only highlights words' do
|
38
|
+
before { stub_response 'den-haag-nl.json' }
|
39
|
+
|
40
|
+
let(:keyword) { 'Den . * { } Haag \\' }
|
41
|
+
let(:country) { :nl }
|
42
|
+
|
43
|
+
its(:text) { should eq 'Den Haag, Nederland' }
|
44
|
+
its(:html) { should eq '<strong>Den</strong> <strong>Haag</strong>, Nederland' }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|