really-broken-geocoder 1.5.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 (136) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +557 -0
  3. data/LICENSE +20 -0
  4. data/README.md +3 -0
  5. data/bin/geocode +5 -0
  6. data/examples/autoexpire_cache_dalli.rb +62 -0
  7. data/examples/autoexpire_cache_redis.rb +28 -0
  8. data/examples/cache_bypass.rb +48 -0
  9. data/examples/reverse_geocode_job.rb +40 -0
  10. data/lib/generators/geocoder/config/config_generator.rb +14 -0
  11. data/lib/generators/geocoder/config/templates/initializer.rb +22 -0
  12. data/lib/generators/geocoder/maxmind/geolite_city_generator.rb +30 -0
  13. data/lib/generators/geocoder/maxmind/geolite_country_generator.rb +30 -0
  14. data/lib/generators/geocoder/maxmind/templates/migration/geolite_city.rb +30 -0
  15. data/lib/generators/geocoder/maxmind/templates/migration/geolite_country.rb +17 -0
  16. data/lib/generators/geocoder/migration_version.rb +15 -0
  17. data/lib/geocoder.rb +48 -0
  18. data/lib/geocoder/cache.rb +94 -0
  19. data/lib/geocoder/calculations.rb +420 -0
  20. data/lib/geocoder/cli.rb +121 -0
  21. data/lib/geocoder/configuration.rb +137 -0
  22. data/lib/geocoder/configuration_hash.rb +11 -0
  23. data/lib/geocoder/esri_token.rb +38 -0
  24. data/lib/geocoder/exceptions.rb +40 -0
  25. data/lib/geocoder/ip_address.rb +26 -0
  26. data/lib/geocoder/kernel_logger.rb +25 -0
  27. data/lib/geocoder/logger.rb +47 -0
  28. data/lib/geocoder/lookup.rb +118 -0
  29. data/lib/geocoder/lookups/amap.rb +63 -0
  30. data/lib/geocoder/lookups/baidu.rb +63 -0
  31. data/lib/geocoder/lookups/baidu_ip.rb +30 -0
  32. data/lib/geocoder/lookups/ban_data_gouv_fr.rb +130 -0
  33. data/lib/geocoder/lookups/base.rb +348 -0
  34. data/lib/geocoder/lookups/bing.rb +82 -0
  35. data/lib/geocoder/lookups/db_ip_com.rb +52 -0
  36. data/lib/geocoder/lookups/dstk.rb +22 -0
  37. data/lib/geocoder/lookups/esri.rb +95 -0
  38. data/lib/geocoder/lookups/freegeoip.rb +60 -0
  39. data/lib/geocoder/lookups/geocoder_ca.rb +53 -0
  40. data/lib/geocoder/lookups/geocoder_us.rb +51 -0
  41. data/lib/geocoder/lookups/geocodio.rb +42 -0
  42. data/lib/geocoder/lookups/geoip2.rb +45 -0
  43. data/lib/geocoder/lookups/geoportail_lu.rb +65 -0
  44. data/lib/geocoder/lookups/google.rb +95 -0
  45. data/lib/geocoder/lookups/google_places_details.rb +50 -0
  46. data/lib/geocoder/lookups/google_places_search.rb +33 -0
  47. data/lib/geocoder/lookups/google_premier.rb +57 -0
  48. data/lib/geocoder/lookups/here.rb +77 -0
  49. data/lib/geocoder/lookups/ip2location.rb +75 -0
  50. data/lib/geocoder/lookups/ipapi_com.rb +82 -0
  51. data/lib/geocoder/lookups/ipdata_co.rb +62 -0
  52. data/lib/geocoder/lookups/ipinfo_io.rb +44 -0
  53. data/lib/geocoder/lookups/ipstack.rb +63 -0
  54. data/lib/geocoder/lookups/latlon.rb +59 -0
  55. data/lib/geocoder/lookups/location_iq.rb +50 -0
  56. data/lib/geocoder/lookups/mapbox.rb +59 -0
  57. data/lib/geocoder/lookups/mapquest.rb +58 -0
  58. data/lib/geocoder/lookups/maxmind.rb +90 -0
  59. data/lib/geocoder/lookups/maxmind_geoip2.rb +70 -0
  60. data/lib/geocoder/lookups/maxmind_local.rb +65 -0
  61. data/lib/geocoder/lookups/nominatim.rb +64 -0
  62. data/lib/geocoder/lookups/opencagedata.rb +65 -0
  63. data/lib/geocoder/lookups/pelias.rb +63 -0
  64. data/lib/geocoder/lookups/pickpoint.rb +41 -0
  65. data/lib/geocoder/lookups/pointpin.rb +69 -0
  66. data/lib/geocoder/lookups/postcode_anywhere_uk.rb +50 -0
  67. data/lib/geocoder/lookups/postcodes_io.rb +31 -0
  68. data/lib/geocoder/lookups/smarty_streets.rb +63 -0
  69. data/lib/geocoder/lookups/telize.rb +75 -0
  70. data/lib/geocoder/lookups/tencent.rb +59 -0
  71. data/lib/geocoder/lookups/test.rb +44 -0
  72. data/lib/geocoder/lookups/yandex.rb +62 -0
  73. data/lib/geocoder/models/active_record.rb +51 -0
  74. data/lib/geocoder/models/base.rb +39 -0
  75. data/lib/geocoder/models/mongo_base.rb +62 -0
  76. data/lib/geocoder/models/mongo_mapper.rb +26 -0
  77. data/lib/geocoder/models/mongoid.rb +32 -0
  78. data/lib/geocoder/query.rb +125 -0
  79. data/lib/geocoder/railtie.rb +26 -0
  80. data/lib/geocoder/request.rb +114 -0
  81. data/lib/geocoder/results/amap.rb +87 -0
  82. data/lib/geocoder/results/baidu.rb +79 -0
  83. data/lib/geocoder/results/baidu_ip.rb +62 -0
  84. data/lib/geocoder/results/ban_data_gouv_fr.rb +257 -0
  85. data/lib/geocoder/results/base.rb +79 -0
  86. data/lib/geocoder/results/bing.rb +52 -0
  87. data/lib/geocoder/results/db_ip_com.rb +58 -0
  88. data/lib/geocoder/results/dstk.rb +6 -0
  89. data/lib/geocoder/results/esri.rb +75 -0
  90. data/lib/geocoder/results/freegeoip.rb +40 -0
  91. data/lib/geocoder/results/geocoder_ca.rb +60 -0
  92. data/lib/geocoder/results/geocoder_us.rb +39 -0
  93. data/lib/geocoder/results/geocodio.rb +78 -0
  94. data/lib/geocoder/results/geoip2.rb +76 -0
  95. data/lib/geocoder/results/geoportail_lu.rb +71 -0
  96. data/lib/geocoder/results/google.rb +150 -0
  97. data/lib/geocoder/results/google_places_details.rb +39 -0
  98. data/lib/geocoder/results/google_places_search.rb +52 -0
  99. data/lib/geocoder/results/google_premier.rb +6 -0
  100. data/lib/geocoder/results/here.rb +79 -0
  101. data/lib/geocoder/results/ip2location.rb +22 -0
  102. data/lib/geocoder/results/ipapi_com.rb +45 -0
  103. data/lib/geocoder/results/ipdata_co.rb +40 -0
  104. data/lib/geocoder/results/ipinfo_io.rb +48 -0
  105. data/lib/geocoder/results/ipstack.rb +60 -0
  106. data/lib/geocoder/results/latlon.rb +71 -0
  107. data/lib/geocoder/results/location_iq.rb +6 -0
  108. data/lib/geocoder/results/mapbox.rb +57 -0
  109. data/lib/geocoder/results/mapquest.rb +48 -0
  110. data/lib/geocoder/results/maxmind.rb +130 -0
  111. data/lib/geocoder/results/maxmind_geoip2.rb +9 -0
  112. data/lib/geocoder/results/maxmind_local.rb +44 -0
  113. data/lib/geocoder/results/nominatim.rb +109 -0
  114. data/lib/geocoder/results/opencagedata.rb +100 -0
  115. data/lib/geocoder/results/pelias.rb +58 -0
  116. data/lib/geocoder/results/pickpoint.rb +6 -0
  117. data/lib/geocoder/results/pointpin.rb +40 -0
  118. data/lib/geocoder/results/postcode_anywhere_uk.rb +42 -0
  119. data/lib/geocoder/results/postcodes_io.rb +40 -0
  120. data/lib/geocoder/results/smarty_streets.rb +142 -0
  121. data/lib/geocoder/results/telize.rb +40 -0
  122. data/lib/geocoder/results/tencent.rb +72 -0
  123. data/lib/geocoder/results/test.rb +33 -0
  124. data/lib/geocoder/results/yandex.rb +134 -0
  125. data/lib/geocoder/sql.rb +110 -0
  126. data/lib/geocoder/stores/active_record.rb +328 -0
  127. data/lib/geocoder/stores/base.rb +115 -0
  128. data/lib/geocoder/stores/mongo_base.rb +58 -0
  129. data/lib/geocoder/stores/mongo_mapper.rb +13 -0
  130. data/lib/geocoder/stores/mongoid.rb +13 -0
  131. data/lib/geocoder/version.rb +3 -0
  132. data/lib/hash_recursive_merge.rb +74 -0
  133. data/lib/maxmind_database.rb +109 -0
  134. data/lib/tasks/geocoder.rake +54 -0
  135. data/lib/tasks/maxmind.rake +73 -0
  136. metadata +186 -0
@@ -0,0 +1,63 @@
1
+ require 'geocoder/lookups/base'
2
+ require "geocoder/results/amap"
3
+
4
+ module Geocoder::Lookup
5
+ class Amap < Base
6
+
7
+ def name
8
+ "AMap"
9
+ end
10
+
11
+ def required_api_key_parts
12
+ ["key"]
13
+ end
14
+
15
+ def supported_protocols
16
+ [:http]
17
+ end
18
+
19
+ private # ---------------------------------------------------------------
20
+
21
+ def base_query_url(query)
22
+ path = query.reverse_geocode? ? 'regeo' : 'geo'
23
+ "http://restapi.amap.com/v3/geocode/#{path}?"
24
+ end
25
+
26
+ def results(query, reverse = false)
27
+ return [] unless doc = fetch_data(query)
28
+ case [doc['status'], doc['info']]
29
+ when ['1', 'OK']
30
+ return doc['regeocodes'] unless doc['regeocodes'].blank?
31
+ return [doc['regeocode']] unless doc['regeocode'].blank?
32
+ return doc['geocodes'] unless doc['geocodes'].blank?
33
+ when ['0', 'INVALID_USER_KEY']
34
+ raise_error(Geocoder::InvalidApiKey, "invalid api key") ||
35
+ warn("#{self.name} Geocoding API error: invalid api key.")
36
+ else
37
+ raise_error(Geocoder::Error, "server error.") ||
38
+ warn("#{self.name} Geocoding API error: server error - [#{doc['info']}]")
39
+ end
40
+ return []
41
+ end
42
+
43
+ def query_url_params(query)
44
+ params = {
45
+ :key => configuration.api_key,
46
+ :output => "json"
47
+ }
48
+ if query.reverse_geocode?
49
+ params[:location] = revert_coordinates(query.text)
50
+ params[:extensions] = "all"
51
+ params[:coordsys] = "gps"
52
+ else
53
+ params[:address] = query.sanitized_text
54
+ end
55
+ params.merge(super)
56
+ end
57
+
58
+ def revert_coordinates(text)
59
+ [text[1],text[0]].join(",")
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,63 @@
1
+ require 'geocoder/lookups/base'
2
+ require "geocoder/results/baidu"
3
+
4
+ module Geocoder::Lookup
5
+ class Baidu < Base
6
+
7
+ def name
8
+ "Baidu"
9
+ end
10
+
11
+ def required_api_key_parts
12
+ ["key"]
13
+ end
14
+
15
+ # HTTP only
16
+ def supported_protocols
17
+ [:http]
18
+ end
19
+
20
+ private # ---------------------------------------------------------------
21
+
22
+ def base_query_url(query)
23
+ "#{protocol}://api.map.baidu.com/geocoder/v2/?"
24
+ end
25
+
26
+ def content_key
27
+ 'result'
28
+ end
29
+
30
+ def results(query, reverse = false)
31
+ return [] unless doc = fetch_data(query)
32
+ case doc['status']
33
+ when 0
34
+ return [doc[content_key]] unless doc[content_key].blank?
35
+ when 1, 3, 4
36
+ raise_error(Geocoder::Error, "server error.") ||
37
+ Geocoder.log(:warn, "#{name} Geocoding API error: server error.")
38
+ when 2
39
+ raise_error(Geocoder::InvalidRequest, "invalid request.") ||
40
+ Geocoder.log(:warn, "#{name} Geocoding API error: invalid request.")
41
+ when 5
42
+ raise_error(Geocoder::InvalidApiKey, "invalid api key") ||
43
+ Geocoder.log(:warn, "#{name} Geocoding API error: invalid api key.")
44
+ when 101, 102, 200..299
45
+ raise_error(Geocoder::RequestDenied, "request denied") ||
46
+ Geocoder.log(:warn, "#{name} Geocoding API error: request denied.")
47
+ when 300..399
48
+ raise_error(Geocoder::OverQueryLimitError, "over query limit.") ||
49
+ Geocoder.log(:warn, "#{name} Geocoding API error: over query limit.")
50
+ end
51
+ return []
52
+ end
53
+
54
+ def query_url_params(query)
55
+ {
56
+ (query.reverse_geocode? ? :location : :address) => query.sanitized_text,
57
+ :ak => configuration.api_key,
58
+ :output => "json"
59
+ }.merge(super)
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,30 @@
1
+ require 'geocoder/lookups/baidu'
2
+ require 'geocoder/results/baidu_ip'
3
+
4
+ module Geocoder::Lookup
5
+ class BaiduIp < Baidu
6
+
7
+ def name
8
+ "Baidu IP"
9
+ end
10
+
11
+ private # ---------------------------------------------------------------
12
+
13
+ def base_query_url(query)
14
+ "#{protocol}://api.map.baidu.com/location/ip?"
15
+ end
16
+
17
+ def content_key
18
+ 'content'
19
+ end
20
+
21
+ def query_url_params(query)
22
+ {
23
+ :ip => query.sanitized_text,
24
+ :ak => configuration.api_key,
25
+ :coor => "bd09ll"
26
+ }.merge(super)
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,130 @@
1
+ # encoding: utf-8
2
+
3
+ require 'geocoder/lookups/base'
4
+ require 'geocoder/results/ban_data_gouv_fr'
5
+
6
+ module Geocoder::Lookup
7
+ class BanDataGouvFr < Base
8
+
9
+ def name
10
+ "Base Adresse Nationale Française"
11
+ end
12
+
13
+ def map_link_url(coordinates)
14
+ "https://www.openstreetmap.org/#map=19/#{coordinates.join('/')}"
15
+ end
16
+
17
+ private # ---------------------------------------------------------------
18
+
19
+ def base_query_url(query)
20
+ method = query.reverse_geocode? ? "reverse" : "search"
21
+ "#{protocol}://api-adresse.data.gouv.fr/#{method}/?"
22
+ end
23
+
24
+ def any_result?(doc)
25
+ doc['features'].any?
26
+ end
27
+
28
+ def results(query)
29
+ if doc = fetch_data(query) and any_result?(doc)
30
+ [doc]
31
+ else
32
+ []
33
+ end
34
+ end
35
+
36
+ #### PARAMS ####
37
+
38
+ def query_url_params(query)
39
+ query_ban_datagouv_fr_params(query).merge(super)
40
+ end
41
+
42
+ def query_ban_datagouv_fr_params(query)
43
+ query.reverse_geocode? ? reverse_geocode_ban_fr_params(query) : search_geocode_ban_fr_params(query)
44
+ end
45
+
46
+ #### SEARCH GEOCODING PARAMS ####
47
+ #
48
+ # :q => required, full text search param)
49
+
50
+ # :limit => force limit number of results returned by raw API
51
+ # (default = 5) note : only first result is taken
52
+ # in account in geocoder
53
+ #
54
+ # :autocomplete => pass 0 to disable autocomplete treatment of :q
55
+ # (default = 1)
56
+ #
57
+ # :lat => force filter results around specific lat/lon
58
+ #
59
+ # :lon => force filter results around specific lat/lon
60
+ #
61
+ # :type => force filter the returned result type
62
+ # (check results for a list of accepted types)
63
+ #
64
+ # :postcode => force filter results on a specific city post code
65
+ #
66
+ # :citycode => force filter results on a specific city UUID INSEE code
67
+ #
68
+ # For up to date doc (in french only) : https://adresse.data.gouv.fr/api/
69
+ #
70
+ def search_geocode_ban_fr_params(query)
71
+ params = {
72
+ q: query.sanitized_text
73
+ }
74
+ unless (limit = query.options[:limit]).nil? || !limit_param_is_valid?(limit)
75
+ params[:limit] = limit.to_i
76
+ end
77
+ unless (autocomplete = query.options[:autocomplete]).nil? || !autocomplete_param_is_valid?(autocomplete)
78
+ params[:autocomplete] = autocomplete.to_s
79
+ end
80
+ unless (type = query.options[:type]).nil? || !type_param_is_valid?(type)
81
+ params[:type] = type.downcase
82
+ end
83
+ unless (postcode = query.options[:postcode]).nil? || !code_param_is_valid?(postcode)
84
+ params[:postcode] = postcode.to_s
85
+ end
86
+ unless (citycode = query.options[:citycode]).nil? || !code_param_is_valid?(citycode)
87
+ params[:citycode] = citycode.to_s
88
+ end
89
+ params
90
+ end
91
+
92
+ #### REVERSE GEOCODING PARAMS ####
93
+ #
94
+ # :lat => required
95
+ #
96
+ # :lon => required
97
+ #
98
+ # :type => force returned results type
99
+ # (check results for a list of accepted types)
100
+ #
101
+ def reverse_geocode_ban_fr_params(query)
102
+ lat_lon = query.coordinates
103
+ params = {
104
+ lat: lat_lon.first,
105
+ lon: lat_lon.last
106
+ }
107
+ unless (type = query.options[:type]).nil? || !type_param_is_valid?(type)
108
+ params[:type] = type.downcase
109
+ end
110
+ params
111
+ end
112
+
113
+ def limit_param_is_valid?(param)
114
+ param.to_i.positive?
115
+ end
116
+
117
+ def autocomplete_param_is_valid?(param)
118
+ [0,1].include?(param.to_i)
119
+ end
120
+
121
+ def type_param_is_valid?(param)
122
+ %w(housenumber street locality village town city).include?(param.downcase)
123
+ end
124
+
125
+ def code_param_is_valid?(param)
126
+ (1..99999).include?(param.to_i)
127
+ end
128
+
129
+ end
130
+ end
@@ -0,0 +1,348 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ require 'uri'
4
+
5
+ unless defined?(ActiveSupport::JSON)
6
+ begin
7
+ require 'json'
8
+ rescue LoadError
9
+ raise LoadError, "Please install the 'json' or 'json_pure' gem to parse geocoder results."
10
+ end
11
+ end
12
+
13
+ module Geocoder
14
+ module Lookup
15
+
16
+ class Base
17
+ def initialize
18
+ @cache = nil
19
+ end
20
+
21
+ ##
22
+ # Human-readable name of the geocoding API.
23
+ #
24
+ def name
25
+ fail
26
+ end
27
+
28
+ ##
29
+ # Symbol which is used in configuration to refer to this Lookup.
30
+ #
31
+ def handle
32
+ str = self.class.to_s
33
+ str[str.rindex(':')+1..-1].gsub(/([a-z\d]+)([A-Z])/,'\1_\2').downcase.to_sym
34
+ end
35
+
36
+ ##
37
+ # Query the geocoding API and return a Geocoder::Result object.
38
+ # Returns +nil+ on timeout or error.
39
+ #
40
+ # Takes a search string (eg: "Mississippi Coast Coliseumf, Biloxi, MS",
41
+ # "205.128.54.202") for geocoding, or coordinates (latitude, longitude)
42
+ # for reverse geocoding. Returns an array of <tt>Geocoder::Result</tt>s.
43
+ #
44
+ def search(query, options = {})
45
+ query = Geocoder::Query.new(query, options) unless query.is_a?(Geocoder::Query)
46
+ results(query).map{ |r|
47
+ result = result_class.new(r)
48
+ result.cache_hit = @cache_hit if cache
49
+ result
50
+ }
51
+ end
52
+
53
+ ##
54
+ # Return the URL for a map of the given coordinates.
55
+ #
56
+ # Not necessarily implemented by all subclasses as only some lookups
57
+ # also provide maps.
58
+ #
59
+ def map_link_url(coordinates)
60
+ nil
61
+ end
62
+
63
+ ##
64
+ # Array containing string descriptions of keys required by the API.
65
+ # Empty array if keys are optional or not required.
66
+ #
67
+ def required_api_key_parts
68
+ []
69
+ end
70
+
71
+ ##
72
+ # URL to use for querying the geocoding engine.
73
+ #
74
+ # Subclasses should not modify this method. Instead they should define
75
+ # base_query_url and url_query_string. If absolutely necessary to
76
+ # subclss this method, they must also subclass #cache_key.
77
+ #
78
+ def query_url(query)
79
+ base_query_url(query) + url_query_string(query)
80
+ end
81
+
82
+ ##
83
+ # The working Cache object.
84
+ #
85
+ def cache
86
+ if @cache.nil? and store = configuration.cache
87
+ @cache = Cache.new(store, configuration.cache_prefix)
88
+ end
89
+ @cache
90
+ end
91
+
92
+ ##
93
+ # Array containing the protocols supported by the api.
94
+ # Should be set to [:http] if only HTTP is supported
95
+ # or [:https] if only HTTPS is supported.
96
+ #
97
+ def supported_protocols
98
+ [:http, :https]
99
+ end
100
+
101
+ private # -------------------------------------------------------------
102
+
103
+ ##
104
+ # String which, when concatenated with url_query_string(query)
105
+ # produces the full query URL. Should include the "?" a the end.
106
+ #
107
+ def base_query_url(query)
108
+ fail
109
+ end
110
+
111
+ ##
112
+ # An object with configuration data for this particular lookup.
113
+ #
114
+ def configuration
115
+ Geocoder.config_for_lookup(handle)
116
+ end
117
+
118
+ ##
119
+ # Object used to make HTTP requests.
120
+ #
121
+ def http_client
122
+ proxy_name = "#{protocol}_proxy"
123
+ if proxy = configuration.send(proxy_name)
124
+ proxy_url = !!(proxy =~ /^#{protocol}/) ? proxy : protocol + '://' + proxy
125
+ begin
126
+ uri = URI.parse(proxy_url)
127
+ rescue URI::InvalidURIError
128
+ raise ConfigurationError,
129
+ "Error parsing #{protocol.upcase} proxy URL: '#{proxy_url}'"
130
+ end
131
+ Net::HTTP::Proxy(uri.host, uri.port, uri.user, uri.password)
132
+ else
133
+ Net::HTTP
134
+ end
135
+ end
136
+
137
+ ##
138
+ # Geocoder::Result object or nil on timeout or other error.
139
+ #
140
+ def results(query)
141
+ fail
142
+ end
143
+
144
+ def query_url_params(query)
145
+ query.options[:params] || {}
146
+ end
147
+
148
+ def url_query_string(query)
149
+ hash_to_query(
150
+ query_url_params(query).reject{ |key,value| value.nil? }
151
+ )
152
+ end
153
+
154
+ ##
155
+ # Key to use for caching a geocoding result. Usually this will be the
156
+ # request URL, but in cases where OAuth is used and the nonce,
157
+ # timestamp, etc varies from one request to another, we need to use
158
+ # something else (like the URL before OAuth encoding).
159
+ #
160
+ def cache_key(query)
161
+ base_query_url(query) + hash_to_query(cache_key_params(query))
162
+ end
163
+
164
+ def cache_key_params(query)
165
+ # omit api_key and token because they may vary among requests
166
+ query_url_params(query).reject do |key,value|
167
+ key.to_s.match(/(key|token)/)
168
+ end
169
+ end
170
+
171
+ ##
172
+ # Class of the result objects
173
+ #
174
+ def result_class
175
+ Geocoder::Result.const_get(self.class.to_s.split(":").last)
176
+ end
177
+
178
+ ##
179
+ # Raise exception if configuration specifies it should be raised.
180
+ # Return false if exception not raised.
181
+ #
182
+ def raise_error(error, message = nil)
183
+ exceptions = configuration.always_raise
184
+ if exceptions == :all or exceptions.include?( error.is_a?(Class) ? error : error.class )
185
+ raise error, message
186
+ else
187
+ false
188
+ end
189
+ end
190
+
191
+ ##
192
+ # Returns a parsed search result (Ruby hash).
193
+ #
194
+ def fetch_data(query)
195
+ parse_raw_data fetch_raw_data(query)
196
+ rescue SocketError => err
197
+ raise_error(err) or Geocoder.log(:warn, "Geocoding API connection cannot be established.")
198
+ rescue Errno::ECONNREFUSED => err
199
+ raise_error(err) or Geocoder.log(:warn, "Geocoding API connection refused.")
200
+ rescue Timeout::Error => err
201
+ raise_error(err) or Geocoder.log(:warn, "Geocoding API not responding fast enough " +
202
+ "(use Geocoder.configure(:timeout => ...) to set limit).")
203
+ end
204
+
205
+ def parse_json(data)
206
+ if defined?(ActiveSupport::JSON)
207
+ ActiveSupport::JSON.decode(data)
208
+ else
209
+ JSON.parse(data)
210
+ end
211
+ rescue
212
+ unless raise_error(ResponseParseError.new(data))
213
+ Geocoder.log(:warn, "Geocoding API's response was not valid JSON")
214
+ Geocoder.log(:debug, "Raw response: #{data}")
215
+ end
216
+ end
217
+
218
+ ##
219
+ # Parses a raw search result (returns hash or array).
220
+ #
221
+ def parse_raw_data(raw_data)
222
+ parse_json(raw_data)
223
+ end
224
+
225
+ ##
226
+ # Protocol to use for communication with geocoding services.
227
+ # Set in configuration but not available for every service.
228
+ #
229
+ def protocol
230
+ "http" + (use_ssl? ? "s" : "")
231
+ end
232
+
233
+ def valid_response?(response)
234
+ (200..399).include?(response.code.to_i)
235
+ end
236
+
237
+ ##
238
+ # Fetch a raw geocoding result (JSON string).
239
+ # The result might or might not be cached.
240
+ #
241
+ def fetch_raw_data(query)
242
+ key = cache_key(query)
243
+ if cache and body = cache[key]
244
+ @cache_hit = true
245
+ else
246
+ check_api_key_configuration!(query)
247
+ response = make_api_request(query)
248
+ check_response_for_errors!(response)
249
+ body = response.body
250
+
251
+ # apply the charset from the Content-Type header, if possible
252
+ ct = response['content-type']
253
+
254
+ if ct && ct['charset']
255
+ charset = ct.split(';').select do |s|
256
+ s['charset']
257
+ end.first.to_s.split('=')
258
+ if charset.length == 2
259
+ body.force_encoding(charset.last) rescue ArgumentError
260
+ end
261
+ end
262
+
263
+ if cache and valid_response?(response)
264
+ cache[key] = body
265
+ end
266
+ @cache_hit = false
267
+ end
268
+ body
269
+ end
270
+
271
+ def check_response_for_errors!(response)
272
+ if response.code.to_i == 400
273
+ raise_error(Geocoder::InvalidRequest) ||
274
+ Geocoder.log(:warn, "Geocoding API error: 400 Bad Request")
275
+ elsif response.code.to_i == 401
276
+ raise_error(Geocoder::RequestDenied) ||
277
+ Geocoder.log(:warn, "Geocoding API error: 401 Unauthorized")
278
+ elsif response.code.to_i == 402
279
+ raise_error(Geocoder::OverQueryLimitError) ||
280
+ Geocoder.log(:warn, "Geocoding API error: 402 Payment Required")
281
+ elsif response.code.to_i == 429
282
+ raise_error(Geocoder::OverQueryLimitError) ||
283
+ Geocoder.log(:warn, "Geocoding API error: 429 Too Many Requests")
284
+ elsif response.code.to_i == 503
285
+ raise_error(Geocoder::ServiceUnavailable) ||
286
+ Geocoder.log(:warn, "Geocoding API error: 503 Service Unavailable")
287
+ end
288
+ end
289
+
290
+ ##
291
+ # Make an HTTP(S) request to a geocoding API and
292
+ # return the response object.
293
+ #
294
+ def make_api_request(query)
295
+ uri = URI.parse(query_url(query))
296
+ Geocoder.log(:debug, "Geocoder: HTTP request being made for #{uri.to_s}")
297
+ http_client.start(uri.host, uri.port, use_ssl: use_ssl?, open_timeout: configuration.timeout, read_timeout: configuration.timeout) do |client|
298
+ configure_ssl!(client) if use_ssl?
299
+ req = Net::HTTP::Get.new(uri.request_uri, configuration.http_headers)
300
+ if configuration.basic_auth[:user] and configuration.basic_auth[:password]
301
+ req.basic_auth(
302
+ configuration.basic_auth[:user],
303
+ configuration.basic_auth[:password]
304
+ )
305
+ end
306
+ client.request(req)
307
+ end
308
+ rescue Timeout::Error
309
+ raise Geocoder::LookupTimeout
310
+ rescue Errno::EHOSTUNREACH, Errno::ETIMEDOUT, Errno::ENETUNREACH, Errno::ECONNRESET
311
+ raise Geocoder::NetworkError
312
+ end
313
+
314
+ def use_ssl?
315
+ if supported_protocols == [:https]
316
+ true
317
+ elsif supported_protocols == [:http]
318
+ false
319
+ else
320
+ configuration.use_https
321
+ end
322
+ end
323
+
324
+ def configure_ssl!(client); end
325
+
326
+ def check_api_key_configuration!(query)
327
+ key_parts = query.lookup.required_api_key_parts
328
+ if key_parts.size > Array(configuration.api_key).size
329
+ parts_string = key_parts.size == 1 ? key_parts.first : key_parts
330
+ raise Geocoder::ConfigurationError,
331
+ "The #{query.lookup.name} API requires a key to be configured: " +
332
+ parts_string.inspect
333
+ end
334
+ end
335
+
336
+ ##
337
+ # Simulate ActiveSupport's Object#to_query.
338
+ # Removes any keys with nil value.
339
+ #
340
+ def hash_to_query(hash)
341
+ require 'cgi' unless defined?(CGI) && defined?(CGI.escape)
342
+ hash.collect{ |p|
343
+ p[1].nil? ? nil : p.map{ |i| CGI.escape i.to_s } * '='
344
+ }.compact.sort * '&'
345
+ end
346
+ end
347
+ end
348
+ end