geocoder-sgonyea 1.1.6.1

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.
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