geocoder-sgonyea 1.1.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. data/.gitignore +5 -0
  2. data/.travis.yml +23 -0
  3. data/CHANGELOG.md +298 -0
  4. data/LICENSE +20 -0
  5. data/README.md +656 -0
  6. data/Rakefile +25 -0
  7. data/bin/geocode +5 -0
  8. data/examples/autoexpire_cache.rb +28 -0
  9. data/gemfiles/Gemfile.mongoid-2.4.x +15 -0
  10. data/lib/generators/geocoder/config/config_generator.rb +14 -0
  11. data/lib/generators/geocoder/config/templates/initializer.rb +21 -0
  12. data/lib/geocoder.rb +55 -0
  13. data/lib/geocoder/cache.rb +85 -0
  14. data/lib/geocoder/calculations.rb +319 -0
  15. data/lib/geocoder/cli.rb +114 -0
  16. data/lib/geocoder/configuration.rb +130 -0
  17. data/lib/geocoder/configuration_hash.rb +11 -0
  18. data/lib/geocoder/exceptions.rb +21 -0
  19. data/lib/geocoder/lookup.rb +82 -0
  20. data/lib/geocoder/lookups/base.rb +250 -0
  21. data/lib/geocoder/lookups/bing.rb +47 -0
  22. data/lib/geocoder/lookups/freegeoip.rb +47 -0
  23. data/lib/geocoder/lookups/geocoder_ca.rb +54 -0
  24. data/lib/geocoder/lookups/google.rb +62 -0
  25. data/lib/geocoder/lookups/google_premier.rb +47 -0
  26. data/lib/geocoder/lookups/mapquest.rb +43 -0
  27. data/lib/geocoder/lookups/maxmind.rb +88 -0
  28. data/lib/geocoder/lookups/nominatim.rb +45 -0
  29. data/lib/geocoder/lookups/ovi.rb +52 -0
  30. data/lib/geocoder/lookups/test.rb +38 -0
  31. data/lib/geocoder/lookups/yahoo.rb +84 -0
  32. data/lib/geocoder/lookups/yandex.rb +54 -0
  33. data/lib/geocoder/models/active_record.rb +46 -0
  34. data/lib/geocoder/models/base.rb +42 -0
  35. data/lib/geocoder/models/mongo_base.rb +60 -0
  36. data/lib/geocoder/models/mongo_mapper.rb +26 -0
  37. data/lib/geocoder/models/mongoid.rb +32 -0
  38. data/lib/geocoder/query.rb +103 -0
  39. data/lib/geocoder/railtie.rb +26 -0
  40. data/lib/geocoder/request.rb +23 -0
  41. data/lib/geocoder/results/base.rb +67 -0
  42. data/lib/geocoder/results/bing.rb +48 -0
  43. data/lib/geocoder/results/freegeoip.rb +45 -0
  44. data/lib/geocoder/results/geocoder_ca.rb +60 -0
  45. data/lib/geocoder/results/google.rb +106 -0
  46. data/lib/geocoder/results/google_premier.rb +6 -0
  47. data/lib/geocoder/results/mapquest.rb +51 -0
  48. data/lib/geocoder/results/maxmind.rb +136 -0
  49. data/lib/geocoder/results/nominatim.rb +94 -0
  50. data/lib/geocoder/results/ovi.rb +62 -0
  51. data/lib/geocoder/results/test.rb +16 -0
  52. data/lib/geocoder/results/yahoo.rb +55 -0
  53. data/lib/geocoder/results/yandex.rb +80 -0
  54. data/lib/geocoder/sql.rb +106 -0
  55. data/lib/geocoder/stores/active_record.rb +259 -0
  56. data/lib/geocoder/stores/base.rb +120 -0
  57. data/lib/geocoder/stores/mongo_base.rb +85 -0
  58. data/lib/geocoder/stores/mongo_mapper.rb +13 -0
  59. data/lib/geocoder/stores/mongoid.rb +13 -0
  60. data/lib/geocoder/version.rb +3 -0
  61. data/lib/hash_recursive_merge.rb +74 -0
  62. data/lib/oauth_util.rb +112 -0
  63. data/lib/tasks/geocoder.rake +25 -0
  64. data/test/active_record_test.rb +15 -0
  65. data/test/cache_test.rb +19 -0
  66. data/test/calculations_test.rb +195 -0
  67. data/test/configuration_test.rb +78 -0
  68. data/test/custom_block_test.rb +32 -0
  69. data/test/error_handling_test.rb +43 -0
  70. data/test/fixtures/bing_invalid_key +1 -0
  71. data/test/fixtures/bing_madison_square_garden +40 -0
  72. data/test/fixtures/bing_no_results +16 -0
  73. data/test/fixtures/bing_reverse +42 -0
  74. data/test/fixtures/freegeoip_74_200_247_59 +12 -0
  75. data/test/fixtures/freegeoip_no_results +1 -0
  76. data/test/fixtures/geocoder_ca_madison_square_garden +1 -0
  77. data/test/fixtures/geocoder_ca_no_results +1 -0
  78. data/test/fixtures/geocoder_ca_reverse +34 -0
  79. data/test/fixtures/google_garbage +456 -0
  80. data/test/fixtures/google_madison_square_garden +57 -0
  81. data/test/fixtures/google_no_city_data +44 -0
  82. data/test/fixtures/google_no_locality +51 -0
  83. data/test/fixtures/google_no_results +4 -0
  84. data/test/fixtures/mapquest_madison_square_garden +52 -0
  85. data/test/fixtures/mapquest_no_results +7 -0
  86. data/test/fixtures/maxmind_24_24_24_21 +1 -0
  87. data/test/fixtures/maxmind_24_24_24_22 +1 -0
  88. data/test/fixtures/maxmind_24_24_24_23 +1 -0
  89. data/test/fixtures/maxmind_24_24_24_24 +1 -0
  90. data/test/fixtures/maxmind_74_200_247_59 +1 -0
  91. data/test/fixtures/maxmind_invalid_key +1 -0
  92. data/test/fixtures/maxmind_no_results +1 -0
  93. data/test/fixtures/nominatim_madison_square_garden +150 -0
  94. data/test/fixtures/nominatim_no_results +1 -0
  95. data/test/fixtures/ovi_madison_square_garden +72 -0
  96. data/test/fixtures/ovi_no_results +8 -0
  97. data/test/fixtures/yahoo_error +1 -0
  98. data/test/fixtures/yahoo_invalid_key +2 -0
  99. data/test/fixtures/yahoo_madison_square_garden +52 -0
  100. data/test/fixtures/yahoo_no_results +10 -0
  101. data/test/fixtures/yahoo_over_limit +2 -0
  102. data/test/fixtures/yandex_invalid_key +1 -0
  103. data/test/fixtures/yandex_kremlin +48 -0
  104. data/test/fixtures/yandex_no_city_and_town +112 -0
  105. data/test/fixtures/yandex_no_results +16 -0
  106. data/test/geocoder_test.rb +59 -0
  107. data/test/https_test.rb +16 -0
  108. data/test/integration/smoke_test.rb +26 -0
  109. data/test/lookup_test.rb +116 -0
  110. data/test/method_aliases_test.rb +25 -0
  111. data/test/mongoid_test.rb +39 -0
  112. data/test/mongoid_test_helper.rb +43 -0
  113. data/test/near_test.rb +43 -0
  114. data/test/oauth_util_test.rb +30 -0
  115. data/test/proxy_test.rb +23 -0
  116. data/test/query_test.rb +51 -0
  117. data/test/request_test.rb +29 -0
  118. data/test/result_test.rb +42 -0
  119. data/test/services_test.rb +277 -0
  120. data/test/test_helper.rb +279 -0
  121. data/test/test_mode_test.rb +50 -0
  122. metadata +170 -0
@@ -0,0 +1,114 @@
1
+ require 'geocoder'
2
+ require 'optparse'
3
+
4
+ module Geocoder
5
+ class Cli
6
+
7
+ def self.run(args, out = STDOUT)
8
+ show_url = false
9
+ show_json = false
10
+
11
+ OptionParser.new{ |opts|
12
+ opts.banner = "Usage:\n geocode [options] <location>"
13
+ opts.separator "\nOptions: "
14
+
15
+ opts.on("-k <key>", "--key <key>",
16
+ "Key for geocoding API (usually optional). Enclose multi-part keys in quotes and separate parts by spaces") do |key|
17
+ if (key_parts = key.split(/\s+/)).size > 1
18
+ Geocoder.configure(:api_key => key_parts)
19
+ else
20
+ Geocoder.configure(:api_key => key)
21
+ end
22
+ end
23
+
24
+ opts.on("-l <language>", "--language <language>",
25
+ "Language of output (see API docs for valid choices)") do |language|
26
+ Geocoder.configure(:language => language)
27
+ end
28
+
29
+ opts.on("-p <proxy>", "--proxy <proxy>",
30
+ "HTTP proxy server to use (user:pass@host:port)") do |proxy|
31
+ Geocoder.configure(:http_proxy => proxy)
32
+ end
33
+
34
+ opts.on("-s <service>", Geocoder::Lookup.all_services_except_test, "--service <service>",
35
+ "Geocoding service: #{Geocoder::Lookup.all_services_except_test * ', '}") do |service|
36
+ Geocoder.configure(:lookup => service.to_sym)
37
+ Geocoder.configure(:ip_lookup => service.to_sym)
38
+ end
39
+
40
+ opts.on("-t <seconds>", "--timeout <seconds>",
41
+ "Maximum number of seconds to wait for API response") do |timeout|
42
+ Geocoder.configure(:timeout => timeout.to_i)
43
+ end
44
+
45
+ opts.on("-j", "--json", "Print API's raw JSON response") do
46
+ show_json = true
47
+ end
48
+
49
+ opts.on("-u", "--url", "Print URL for API query instead of result") do
50
+ show_url = true
51
+ end
52
+
53
+ opts.on_tail("-v", "--version", "Print version number") do
54
+ require "geocoder/version"
55
+ out << "Geocoder #{Geocoder::VERSION}\n"
56
+ exit
57
+ end
58
+
59
+ opts.on_tail("-h", "--help", "Print this help") do
60
+ out << "Look up geographic information about a location.\n\n"
61
+ out << opts
62
+ out << "\nCreated and maintained by Alex Reisner, available under the MIT License.\n"
63
+ out << "Report bugs and contribute at http://github.com/alexreisner/geocoder\n"
64
+ exit
65
+ end
66
+ }.parse!(args)
67
+
68
+ query = args.join(" ")
69
+
70
+ if query == ""
71
+ out << "Please specify a location (run `geocode -h` for more info).\n"
72
+ exit 1
73
+ end
74
+
75
+ if show_url and show_json
76
+ out << "You can only specify one of -j and -u.\n"
77
+ exit 2
78
+ end
79
+
80
+ if show_url
81
+ q = Geocoder::Query.new(query)
82
+ out << q.url + "\n"
83
+ exit 0
84
+ end
85
+
86
+ if show_json
87
+ q = Geocoder::Query.new(query)
88
+ out << q.lookup.send(:fetch_raw_data, q) + "\n"
89
+ exit 0
90
+ end
91
+
92
+ if (result = Geocoder.search(query).first)
93
+ google = Geocoder::Lookup.get(:google)
94
+ lines = [
95
+ ["Latitude", result.latitude],
96
+ ["Longitude", result.longitude],
97
+ ["Full address", result.address],
98
+ ["City", result.city],
99
+ ["State/province", result.state],
100
+ ["Postal code", result.postal_code],
101
+ ["Country", result.country],
102
+ ["Google map", google.map_link_url(result.coordinates)],
103
+ ]
104
+ lines.each do |line|
105
+ out << (line[0] + ": ").ljust(18) + line[1].to_s + "\n"
106
+ end
107
+ exit 0
108
+ else
109
+ out << "Location '#{query}' not found.\n"
110
+ exit 1
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,130 @@
1
+ require 'singleton'
2
+ require 'geocoder/configuration_hash'
3
+
4
+ module Geocoder
5
+
6
+ ##
7
+ # Configuration options should be set by passing a hash:
8
+ #
9
+ # Geocoder.configure(
10
+ # :timeout => 5,
11
+ # :lookup => :yandex,
12
+ # :api_key => "2a9fsa983jaslfj982fjasd",
13
+ # :units => :km
14
+ # )
15
+ #
16
+ def self.configure(options = nil, &block)
17
+ if block_given?
18
+ warn "WARNING: Passing a block to Geocoder.configure is DEPRECATED. Please pass a hash instead (eg: Geocoder.configure(:units => ..., :api_key => ...))."
19
+ block.call(Configuration.instance)
20
+ elsif !options.nil?
21
+ Configuration.instance.configure(options)
22
+ else
23
+ warn "WARNING: Use of Geocoder.configure to read or write single config options is DEPRECATED. To write to the config please pass a hash (eg: Geocoder.configure(:units => ...)). To read config options please use the Geocoder.config object (eg: Geocoder.config.units)."
24
+ Configuration.instance
25
+ end
26
+ end
27
+
28
+ ##
29
+ # Read-only access to the singleton's config data.
30
+ #
31
+ def self.config
32
+ Configuration.instance.data
33
+ end
34
+
35
+ ##
36
+ # Read-only access to lookup-specific config data.
37
+ #
38
+ def self.config_for_lookup(lookup_name)
39
+ data = config.clone
40
+ data.reject!{ |key,value| !Configuration::OPTIONS.include?(key) }
41
+ if config.has_key?(lookup_name)
42
+ data.merge!(config[lookup_name])
43
+ end
44
+ data
45
+ end
46
+
47
+ class Configuration
48
+ include Singleton
49
+
50
+ OPTIONS = [
51
+ :timeout,
52
+ :lookup,
53
+ :ip_lookup,
54
+ :language,
55
+ :http_headers,
56
+ :use_https,
57
+ :http_proxy,
58
+ :https_proxy,
59
+ :api_key,
60
+ :cache,
61
+ :cache_prefix,
62
+ :always_raise,
63
+ :units,
64
+ :distances
65
+ ]
66
+
67
+ attr_accessor :data
68
+
69
+ def self.set_defaults
70
+ instance.set_defaults
71
+ end
72
+
73
+ OPTIONS.each do |o|
74
+ define_method o do
75
+ @data[o]
76
+ end
77
+ define_method "#{o}=" do |value|
78
+ @data[o] = value
79
+ end
80
+ end
81
+
82
+ def configure(options)
83
+ @data.rmerge!(options)
84
+ end
85
+
86
+ def initialize # :nodoc
87
+ @data = Geocoder::ConfigurationHash.new
88
+ set_defaults
89
+ end
90
+
91
+ def set_defaults
92
+
93
+ # geocoding options
94
+ @data[:timeout] = 3 # geocoding service timeout (secs)
95
+ @data[:lookup] = :google # name of street address geocoding service (symbol)
96
+ @data[:ip_lookup] = :freegeoip # name of IP address geocoding service (symbol)
97
+ @data[:language] = :en # ISO-639 language code
98
+ @data[:http_headers] = {} # HTTP headers for lookup
99
+ @data[:use_https] = false # use HTTPS for lookup requests? (if supported)
100
+ @data[:http_proxy] = nil # HTTP proxy server (user:pass@host:port)
101
+ @data[:https_proxy] = nil # HTTPS proxy server (user:pass@host:port)
102
+ @data[:api_key] = nil # API key for geocoding service
103
+ @data[:cache] = nil # cache object (must respond to #[], #[]=, and #keys)
104
+ @data[:cache_prefix] = "geocoder:" # prefix (string) to use for all cache keys
105
+
106
+ # exceptions that should not be rescued by default
107
+ # (if you want to implement custom error handling);
108
+ # supports SocketError and TimeoutError
109
+ @data[:always_raise] = []
110
+
111
+ # calculation options
112
+ @data[:units] = :mi # :mi or :km
113
+ @data[:distances] = :linear # :linear or :spherical
114
+ end
115
+
116
+ instance_eval(OPTIONS.map do |option|
117
+ o = option.to_s
118
+ <<-EOS
119
+ def #{o}
120
+ instance.data[:#{o}]
121
+ end
122
+
123
+ def #{o}=(value)
124
+ instance.data[:#{o}] = value
125
+ end
126
+ EOS
127
+ end.join("\n\n"))
128
+
129
+ end
130
+ end
@@ -0,0 +1,11 @@
1
+ require 'hash_recursive_merge'
2
+
3
+ module Geocoder
4
+ class ConfigurationHash < Hash
5
+ include HashRecursiveMerge
6
+
7
+ def method_missing(meth, *args, &block)
8
+ has_key?(meth) ? self[meth] : super
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ module Geocoder
2
+
3
+ class Error < StandardError
4
+ end
5
+
6
+ class ConfigurationError < Error
7
+ end
8
+
9
+ class OverQueryLimitError < Error
10
+ end
11
+
12
+ class RequestDenied < Error
13
+ end
14
+
15
+ class InvalidRequest < Error
16
+ end
17
+
18
+ class InvalidApiKey < Error
19
+ end
20
+
21
+ end
@@ -0,0 +1,82 @@
1
+ module Geocoder
2
+ module Lookup
3
+ extend self
4
+
5
+ ##
6
+ # Array of valid Lookup service names.
7
+ #
8
+ def all_services
9
+ street_services + ip_services
10
+ end
11
+
12
+ ##
13
+ # Array of valid Lookup service names, excluding :test.
14
+ #
15
+ def all_services_except_test
16
+ all_services - [:test]
17
+ end
18
+
19
+ ##
20
+ # All street address lookup services, default first.
21
+ #
22
+ def street_services
23
+ [
24
+ :google,
25
+ :google_premier,
26
+ :yahoo,
27
+ :bing,
28
+ :geocoder_ca,
29
+ :yandex,
30
+ :nominatim,
31
+ :mapquest,
32
+ :ovi,
33
+ :test
34
+ ]
35
+ end
36
+
37
+ ##
38
+ # All IP address lookup services, default first.
39
+ #
40
+ def ip_services
41
+ [:freegeoip, :maxmind]
42
+ end
43
+
44
+ ##
45
+ # Retrieve a Lookup object from the store.
46
+ # Use this instead of Geocoder::Lookup::X.new to get an
47
+ # already-configured Lookup object.
48
+ #
49
+ def get(name)
50
+ @services = {} unless defined?(@services)
51
+ @services[name] = spawn(name) unless @services.include?(name)
52
+ @services[name]
53
+ end
54
+
55
+
56
+ private # -----------------------------------------------------------------
57
+
58
+ ##
59
+ # Spawn a Lookup of the given name.
60
+ #
61
+ def spawn(name)
62
+ if all_services.include?(name)
63
+ Geocoder::Lookup.const_get(classify_name(name)).new
64
+ else
65
+ valids = all_services.map(&:inspect).join(", ")
66
+ raise ConfigurationError, "Please specify a valid lookup for Geocoder " +
67
+ "(#{name.inspect} is not one of: #{valids})."
68
+ end
69
+ end
70
+
71
+ ##
72
+ # Convert an "underscore" version of a name into a "class" version.
73
+ #
74
+ def classify_name(filename)
75
+ filename.to_s.split("_").map{ |i| i[0...1].upcase + i[1..-1] }.join
76
+ end
77
+ end
78
+ end
79
+
80
+ Geocoder::Lookup.all_services.each do |name|
81
+ require "geocoder/lookups/#{name}"
82
+ end
@@ -0,0 +1,250 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ require 'uri'
4
+
5
+ unless defined?(ActiveSupport::JSON)
6
+ begin
7
+ require 'rubygems' # for Ruby 1.8
8
+ require 'json'
9
+ rescue LoadError
10
+ raise LoadError, "Please install the 'json' or 'json_pure' gem to parse geocoder results."
11
+ end
12
+ end
13
+
14
+ module Geocoder
15
+ module Lookup
16
+
17
+ class Base
18
+
19
+ ##
20
+ # Human-readable name of the geocoding API.
21
+ #
22
+ def name
23
+ fail
24
+ end
25
+
26
+ ##
27
+ # Symbol which is used in configuration to refer to this Lookup.
28
+ #
29
+ def handle
30
+ str = self.class.to_s
31
+ str[str.rindex(':')+1..-1].gsub(/([a-z\d]+)([A-Z])/,'\1_\2').downcase.to_sym
32
+ end
33
+
34
+ ##
35
+ # Query the geocoding API and return a Geocoder::Result object.
36
+ # Returns +nil+ on timeout or error.
37
+ #
38
+ # Takes a search string (eg: "Mississippi Coast Coliseumf, Biloxi, MS",
39
+ # "205.128.54.202") for geocoding, or coordinates (latitude, longitude)
40
+ # for reverse geocoding. Returns an array of <tt>Geocoder::Result</tt>s.
41
+ #
42
+ def search(query, options = {})
43
+ query = Geocoder::Query.new(query, options) unless query.is_a?(Geocoder::Query)
44
+ results(query).map{ |r|
45
+ result = result_class.new(r)
46
+ result.cache_hit = @cache_hit if cache
47
+ result
48
+ }
49
+ end
50
+
51
+ ##
52
+ # Return the URL for a map of the given coordinates.
53
+ #
54
+ # Not necessarily implemented by all subclasses as only some lookups
55
+ # also provide maps.
56
+ #
57
+ def map_link_url(coordinates)
58
+ nil
59
+ end
60
+
61
+ ##
62
+ # Array containing string descriptions of keys required by the API.
63
+ # Empty array if keys are optional or not required.
64
+ #
65
+ def required_api_key_parts
66
+ []
67
+ end
68
+
69
+ ##
70
+ # URL to use for querying the geocoding engine.
71
+ #
72
+ def query_url(query)
73
+ fail
74
+ end
75
+
76
+ private # -------------------------------------------------------------
77
+
78
+ ##
79
+ # An object with configuration data for this particular lookup.
80
+ #
81
+ def configuration
82
+ Geocoder.config_for_lookup(handle)
83
+ end
84
+
85
+ ##
86
+ # Object used to make HTTP requests.
87
+ #
88
+ def http_client
89
+ protocol = "http#{'s' if configuration.use_https}"
90
+ proxy_name = "#{protocol}_proxy"
91
+ if proxy = configuration.send(proxy_name)
92
+ proxy_url = protocol + '://' + proxy
93
+ begin
94
+ uri = URI.parse(proxy_url)
95
+ rescue URI::InvalidURIError
96
+ raise ConfigurationError,
97
+ "Error parsing #{protocol.upcase} proxy URL: '#{proxy_url}'"
98
+ end
99
+ Net::HTTP::Proxy(uri.host, uri.port, uri.user, uri.password)
100
+ else
101
+ Net::HTTP
102
+ end
103
+ end
104
+
105
+ ##
106
+ # Geocoder::Result object or nil on timeout or other error.
107
+ #
108
+ def results(query)
109
+ fail
110
+ end
111
+
112
+ def query_url_params(query)
113
+ query.options[:params] || {}
114
+ end
115
+
116
+ def url_query_string(query)
117
+ hash_to_query(
118
+ query_url_params(query).reject{ |key,value| value.nil? }
119
+ )
120
+ end
121
+
122
+ ##
123
+ # Key to use for caching a geocoding result. Usually this will be the
124
+ # request URL, but in cases where OAuth is used and the nonce,
125
+ # timestamp, etc varies from one request to another, we need to use
126
+ # something else (like the URL before OAuth encoding).
127
+ #
128
+ def cache_key(query)
129
+ query_url(query)
130
+ end
131
+
132
+ ##
133
+ # Class of the result objects
134
+ #
135
+ def result_class
136
+ Geocoder::Result.const_get(self.class.to_s.split(":").last)
137
+ end
138
+
139
+ ##
140
+ # Raise exception if configuration specifies it should be raised.
141
+ # Return false if exception not raised.
142
+ #
143
+ def raise_error(error, message = nil)
144
+ exceptions = configuration.always_raise
145
+ if exceptions == :all or exceptions.include?( error.is_a?(Class) ? error : error.class )
146
+ raise error, message
147
+ else
148
+ false
149
+ end
150
+ end
151
+
152
+ ##
153
+ # Returns a parsed search result (Ruby hash).
154
+ #
155
+ def fetch_data(query)
156
+ parse_raw_data fetch_raw_data(query)
157
+ rescue SocketError => err
158
+ raise_error(err) or warn "Geocoding API connection cannot be established."
159
+ rescue TimeoutError => err
160
+ raise_error(err) or warn "Geocoding API not responding fast enough " +
161
+ "(use Geocoder.configure(:timeout => ...) to set limit)."
162
+ end
163
+
164
+ ##
165
+ # Parses a raw search result (returns hash or array).
166
+ #
167
+ def parse_raw_data(raw_data)
168
+ if defined?(ActiveSupport::JSON)
169
+ ActiveSupport::JSON.decode(raw_data)
170
+ else
171
+ JSON.parse(raw_data)
172
+ end
173
+ rescue
174
+ warn "Geocoding API's response was not valid JSON."
175
+ end
176
+
177
+ ##
178
+ # Protocol to use for communication with geocoding services.
179
+ # Set in configuration but not available for every service.
180
+ #
181
+ def protocol
182
+ "http" + (configuration.use_https ? "s" : "")
183
+ end
184
+
185
+ ##
186
+ # Fetch a raw geocoding result (JSON string).
187
+ # The result might or might not be cached.
188
+ #
189
+ def fetch_raw_data(query)
190
+ key = cache_key(query)
191
+ if cache and body = cache[key]
192
+ @cache_hit = true
193
+ else
194
+ check_api_key_configuration!(query)
195
+ response = make_api_request(query)
196
+ body = response.body
197
+ if cache and (200..399).include?(response.code.to_i)
198
+ cache[key] = body
199
+ end
200
+ @cache_hit = false
201
+ end
202
+ body
203
+ end
204
+
205
+ ##
206
+ # Make an HTTP(S) request to a geocoding API and
207
+ # return the response object.
208
+ #
209
+ def make_api_request(query)
210
+ timeout(configuration.timeout) do
211
+ uri = URI.parse(query_url(query))
212
+ client = http_client.new(uri.host, uri.port)
213
+ client.use_ssl = true if configuration.use_https
214
+ client.get(uri.request_uri, configuration.http_headers)
215
+ end
216
+ end
217
+
218
+ def check_api_key_configuration!(query)
219
+ key_parts = query.lookup.required_api_key_parts
220
+ if key_parts.size > Array(configuration.api_key).size
221
+ parts_string = key_parts.size == 1 ? key_parts.first : key_parts
222
+ raise Geocoder::ConfigurationError,
223
+ "The #{query.lookup.name} API requires a key to be configured: " +
224
+ parts_string.inspect
225
+ end
226
+ end
227
+
228
+ ##
229
+ # The working Cache object.
230
+ #
231
+ def cache
232
+ if @cache.nil? and store = configuration.cache
233
+ @cache = Cache.new(store, configuration.cache_prefix)
234
+ end
235
+ @cache
236
+ end
237
+
238
+ ##
239
+ # Simulate ActiveSupport's Object#to_query.
240
+ # Removes any keys with nil value.
241
+ #
242
+ def hash_to_query(hash)
243
+ require 'cgi' unless defined?(CGI) && defined?(CGI.escape)
244
+ hash.collect{ |p|
245
+ p[1].nil? ? nil : p.map{ |i| CGI.escape i.to_s } * '='
246
+ }.compact.sort * '&'
247
+ end
248
+ end
249
+ end
250
+ end