geocoder-kb 1.2.6

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 (248) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/.travis.yml +31 -0
  4. data/CHANGELOG.md +384 -0
  5. data/LICENSE +20 -0
  6. data/README.md +1085 -0
  7. data/Rakefile +25 -0
  8. data/bin/geocode +5 -0
  9. data/examples/autoexpire_cache_dalli.rb +62 -0
  10. data/examples/autoexpire_cache_redis.rb +28 -0
  11. data/examples/cache_bypass.rb +48 -0
  12. data/examples/sidekiq_worker.rb +16 -0
  13. data/gemfiles/Gemfile.mongoid-2.4.x +16 -0
  14. data/lib/generators/geocoder/config/config_generator.rb +14 -0
  15. data/lib/generators/geocoder/config/templates/initializer.rb +21 -0
  16. data/lib/generators/geocoder/maxmind/geolite_city_generator.rb +28 -0
  17. data/lib/generators/geocoder/maxmind/geolite_country_generator.rb +28 -0
  18. data/lib/generators/geocoder/maxmind/templates/migration/geolite_city.rb +30 -0
  19. data/lib/generators/geocoder/maxmind/templates/migration/geolite_country.rb +17 -0
  20. data/lib/geocoder.rb +47 -0
  21. data/lib/geocoder/cache.rb +90 -0
  22. data/lib/geocoder/calculations.rb +428 -0
  23. data/lib/geocoder/cli.rb +121 -0
  24. data/lib/geocoder/configuration.rb +124 -0
  25. data/lib/geocoder/configuration_hash.rb +11 -0
  26. data/lib/geocoder/exceptions.rb +21 -0
  27. data/lib/geocoder/ip_address.rb +21 -0
  28. data/lib/geocoder/lookup.rb +102 -0
  29. data/lib/geocoder/lookups/amap.rb +55 -0
  30. data/lib/geocoder/lookups/baidu.rb +55 -0
  31. data/lib/geocoder/lookups/baidu_ip.rb +54 -0
  32. data/lib/geocoder/lookups/base.rb +302 -0
  33. data/lib/geocoder/lookups/bing.rb +59 -0
  34. data/lib/geocoder/lookups/dstk.rb +20 -0
  35. data/lib/geocoder/lookups/esri.rb +48 -0
  36. data/lib/geocoder/lookups/freegeoip.rb +47 -0
  37. data/lib/geocoder/lookups/geocoder_ca.rb +54 -0
  38. data/lib/geocoder/lookups/geocoder_us.rb +39 -0
  39. data/lib/geocoder/lookups/geocodio.rb +42 -0
  40. data/lib/geocoder/lookups/geoip2.rb +40 -0
  41. data/lib/geocoder/lookups/google.rb +67 -0
  42. data/lib/geocoder/lookups/google_places_details.rb +50 -0
  43. data/lib/geocoder/lookups/google_premier.rb +47 -0
  44. data/lib/geocoder/lookups/here.rb +62 -0
  45. data/lib/geocoder/lookups/mapquest.rb +60 -0
  46. data/lib/geocoder/lookups/maxmind.rb +90 -0
  47. data/lib/geocoder/lookups/maxmind_local.rb +58 -0
  48. data/lib/geocoder/lookups/nominatim.rb +52 -0
  49. data/lib/geocoder/lookups/okf.rb +43 -0
  50. data/lib/geocoder/lookups/opencagedata.rb +58 -0
  51. data/lib/geocoder/lookups/ovi.rb +62 -0
  52. data/lib/geocoder/lookups/pointpin.rb +68 -0
  53. data/lib/geocoder/lookups/postcode_anywhere_uk.rb +51 -0
  54. data/lib/geocoder/lookups/smarty_streets.rb +45 -0
  55. data/lib/geocoder/lookups/telize.rb +40 -0
  56. data/lib/geocoder/lookups/test.rb +44 -0
  57. data/lib/geocoder/lookups/yahoo.rb +88 -0
  58. data/lib/geocoder/lookups/yandex.rb +54 -0
  59. data/lib/geocoder/models/active_record.rb +50 -0
  60. data/lib/geocoder/models/base.rb +39 -0
  61. data/lib/geocoder/models/mongo_base.rb +64 -0
  62. data/lib/geocoder/models/mongo_mapper.rb +26 -0
  63. data/lib/geocoder/models/mongoid.rb +32 -0
  64. data/lib/geocoder/query.rb +111 -0
  65. data/lib/geocoder/railtie.rb +26 -0
  66. data/lib/geocoder/request.rb +25 -0
  67. data/lib/geocoder/results/amap.rb +85 -0
  68. data/lib/geocoder/results/baidu.rb +79 -0
  69. data/lib/geocoder/results/baidu_ip.rb +62 -0
  70. data/lib/geocoder/results/base.rb +67 -0
  71. data/lib/geocoder/results/bing.rb +48 -0
  72. data/lib/geocoder/results/dstk.rb +6 -0
  73. data/lib/geocoder/results/esri.rb +51 -0
  74. data/lib/geocoder/results/freegeoip.rb +45 -0
  75. data/lib/geocoder/results/geocoder_ca.rb +60 -0
  76. data/lib/geocoder/results/geocoder_us.rb +39 -0
  77. data/lib/geocoder/results/geocodio.rb +66 -0
  78. data/lib/geocoder/results/geoip2.rb +64 -0
  79. data/lib/geocoder/results/google.rb +124 -0
  80. data/lib/geocoder/results/google_places_details.rb +35 -0
  81. data/lib/geocoder/results/google_premier.rb +6 -0
  82. data/lib/geocoder/results/here.rb +62 -0
  83. data/lib/geocoder/results/mapquest.rb +51 -0
  84. data/lib/geocoder/results/maxmind.rb +135 -0
  85. data/lib/geocoder/results/maxmind_local.rb +49 -0
  86. data/lib/geocoder/results/nominatim.rb +94 -0
  87. data/lib/geocoder/results/okf.rb +106 -0
  88. data/lib/geocoder/results/opencagedata.rb +82 -0
  89. data/lib/geocoder/results/ovi.rb +62 -0
  90. data/lib/geocoder/results/pointpin.rb +44 -0
  91. data/lib/geocoder/results/postcode_anywhere_uk.rb +42 -0
  92. data/lib/geocoder/results/smarty_streets.rb +106 -0
  93. data/lib/geocoder/results/telize.rb +45 -0
  94. data/lib/geocoder/results/test.rb +33 -0
  95. data/lib/geocoder/results/yahoo.rb +55 -0
  96. data/lib/geocoder/results/yandex.rb +84 -0
  97. data/lib/geocoder/sql.rb +107 -0
  98. data/lib/geocoder/stores/active_record.rb +289 -0
  99. data/lib/geocoder/stores/base.rb +127 -0
  100. data/lib/geocoder/stores/mongo_base.rb +89 -0
  101. data/lib/geocoder/stores/mongo_mapper.rb +13 -0
  102. data/lib/geocoder/stores/mongoid.rb +13 -0
  103. data/lib/geocoder/version.rb +3 -0
  104. data/lib/hash_recursive_merge.rb +74 -0
  105. data/lib/maxmind_database.rb +109 -0
  106. data/lib/oauth_util.rb +112 -0
  107. data/lib/tasks/geocoder.rake +29 -0
  108. data/lib/tasks/maxmind.rake +73 -0
  109. data/test/fixtures/baidu_invalid_key +1 -0
  110. data/test/fixtures/baidu_ip_202_198_16_3 +19 -0
  111. data/test/fixtures/baidu_ip_invalid_key +1 -0
  112. data/test/fixtures/baidu_ip_no_results +1 -0
  113. data/test/fixtures/baidu_no_results +1 -0
  114. data/test/fixtures/baidu_reverse +1 -0
  115. data/test/fixtures/baidu_shanghai_pearl_tower +12 -0
  116. data/test/fixtures/bing_invalid_key +1 -0
  117. data/test/fixtures/bing_madison_square_garden +40 -0
  118. data/test/fixtures/bing_no_results +16 -0
  119. data/test/fixtures/bing_reverse +42 -0
  120. data/test/fixtures/cloudmade_invalid_key +1 -0
  121. data/test/fixtures/cloudmade_madison_square_garden +1 -0
  122. data/test/fixtures/cloudmade_no_results +1 -0
  123. data/test/fixtures/esri_madison_square_garden +59 -0
  124. data/test/fixtures/esri_no_results +8 -0
  125. data/test/fixtures/esri_reverse +21 -0
  126. data/test/fixtures/freegeoip_74_200_247_59 +12 -0
  127. data/test/fixtures/freegeoip_no_results +1 -0
  128. data/test/fixtures/geocoder_ca_madison_square_garden +1 -0
  129. data/test/fixtures/geocoder_ca_no_results +1 -0
  130. data/test/fixtures/geocoder_ca_reverse +34 -0
  131. data/test/fixtures/geocoder_us_madison_square_garden +1 -0
  132. data/test/fixtures/geocoder_us_no_results +1 -0
  133. data/test/fixtures/geocodio_1101_pennsylvania_ave +1 -0
  134. data/test/fixtures/geocodio_bad_api_key +3 -0
  135. data/test/fixtures/geocodio_invalid +4 -0
  136. data/test/fixtures/geocodio_no_results +1 -0
  137. data/test/fixtures/geocodio_over_query_limit +4 -0
  138. data/test/fixtures/google_garbage +456 -0
  139. data/test/fixtures/google_madison_square_garden +57 -0
  140. data/test/fixtures/google_no_city_data +44 -0
  141. data/test/fixtures/google_no_locality +51 -0
  142. data/test/fixtures/google_no_results +4 -0
  143. data/test/fixtures/google_over_limit +4 -0
  144. data/test/fixtures/google_places_details_invalid_request +4 -0
  145. data/test/fixtures/google_places_details_madison_square_garden +120 -0
  146. data/test/fixtures/google_places_details_no_results +4 -0
  147. data/test/fixtures/google_places_details_no_reviews +60 -0
  148. data/test/fixtures/google_places_details_no_types +66 -0
  149. data/test/fixtures/here_madison_square_garden +72 -0
  150. data/test/fixtures/here_no_results +8 -0
  151. data/test/fixtures/mapquest_error +16 -0
  152. data/test/fixtures/mapquest_invalid_api_key +16 -0
  153. data/test/fixtures/mapquest_invalid_request +16 -0
  154. data/test/fixtures/mapquest_madison_square_garden +52 -0
  155. data/test/fixtures/mapquest_no_results +16 -0
  156. data/test/fixtures/maxmind_24_24_24_21 +1 -0
  157. data/test/fixtures/maxmind_24_24_24_22 +1 -0
  158. data/test/fixtures/maxmind_24_24_24_23 +1 -0
  159. data/test/fixtures/maxmind_24_24_24_24 +1 -0
  160. data/test/fixtures/maxmind_74_200_247_59 +1 -0
  161. data/test/fixtures/maxmind_invalid_key +1 -0
  162. data/test/fixtures/maxmind_no_results +1 -0
  163. data/test/fixtures/nominatim_madison_square_garden +150 -0
  164. data/test/fixtures/nominatim_no_results +1 -0
  165. data/test/fixtures/nominatim_over_limit +1 -0
  166. data/test/fixtures/okf_kirstinmaki +67 -0
  167. data/test/fixtures/okf_no_results +4 -0
  168. data/test/fixtures/opencagedata_invalid_api_key +25 -0
  169. data/test/fixtures/opencagedata_invalid_request +26 -0
  170. data/test/fixtures/opencagedata_madison_square_garden +73 -0
  171. data/test/fixtures/opencagedata_no_results +29 -0
  172. data/test/fixtures/opencagedata_over_limit +31 -0
  173. data/test/fixtures/ovi_madison_square_garden +72 -0
  174. data/test/fixtures/ovi_no_results +8 -0
  175. data/test/fixtures/pointpin_10_10_10_10 +1 -0
  176. data/test/fixtures/pointpin_555_555_555_555 +1 -0
  177. data/test/fixtures/pointpin_80_111_555_555 +1 -0
  178. data/test/fixtures/pointpin_no_results +1 -0
  179. data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_WR26NJ +1 -0
  180. data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_generic_error +1 -0
  181. data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_hampshire +1 -0
  182. data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_key_limit_exceeded +1 -0
  183. data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_no_results +1 -0
  184. data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_romsey +1 -0
  185. data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_unknown_key +1 -0
  186. data/test/fixtures/smarty_streets_11211 +1 -0
  187. data/test/fixtures/smarty_streets_madison_square_garden +47 -0
  188. data/test/fixtures/smarty_streets_no_results +1 -0
  189. data/test/fixtures/telize_10_10_10_10 +1 -0
  190. data/test/fixtures/telize_555_555_555_555 +4 -0
  191. data/test/fixtures/telize_74_200_247_59 +1 -0
  192. data/test/fixtures/telize_no_results +1 -0
  193. data/test/fixtures/yahoo_error +1 -0
  194. data/test/fixtures/yahoo_invalid_key +2 -0
  195. data/test/fixtures/yahoo_madison_square_garden +52 -0
  196. data/test/fixtures/yahoo_no_results +10 -0
  197. data/test/fixtures/yahoo_over_limit +2 -0
  198. data/test/fixtures/yandex_canada_rue_dupuis_14 +446 -0
  199. data/test/fixtures/yandex_invalid_key +1 -0
  200. data/test/fixtures/yandex_kremlin +48 -0
  201. data/test/fixtures/yandex_new_york +1 -0
  202. data/test/fixtures/yandex_no_city_and_town +112 -0
  203. data/test/fixtures/yandex_no_results +16 -0
  204. data/test/integration/http_client_test.rb +31 -0
  205. data/test/mongoid_test_helper.rb +43 -0
  206. data/test/test_helper.rb +416 -0
  207. data/test/unit/active_record_test.rb +16 -0
  208. data/test/unit/cache_test.rb +37 -0
  209. data/test/unit/calculations_test.rb +220 -0
  210. data/test/unit/configuration_test.rb +55 -0
  211. data/test/unit/error_handling_test.rb +56 -0
  212. data/test/unit/geocoder_test.rb +78 -0
  213. data/test/unit/https_test.rb +17 -0
  214. data/test/unit/ip_address_test.rb +27 -0
  215. data/test/unit/lookup_test.rb +153 -0
  216. data/test/unit/lookups/bing_test.rb +68 -0
  217. data/test/unit/lookups/dstk_test.rb +26 -0
  218. data/test/unit/lookups/esri_test.rb +48 -0
  219. data/test/unit/lookups/freegeoip_test.rb +27 -0
  220. data/test/unit/lookups/geocoder_ca_test.rb +17 -0
  221. data/test/unit/lookups/geocodio_test.rb +55 -0
  222. data/test/unit/lookups/geoip2_test.rb +27 -0
  223. data/test/unit/lookups/google_places_details_test.rb +122 -0
  224. data/test/unit/lookups/google_premier_test.rb +22 -0
  225. data/test/unit/lookups/google_test.rb +84 -0
  226. data/test/unit/lookups/mapquest_test.rb +60 -0
  227. data/test/unit/lookups/maxmind_local_test.rb +28 -0
  228. data/test/unit/lookups/maxmind_test.rb +63 -0
  229. data/test/unit/lookups/nominatim_test.rb +31 -0
  230. data/test/unit/lookups/okf_test.rb +38 -0
  231. data/test/unit/lookups/opencagedata_test.rb +64 -0
  232. data/test/unit/lookups/pointpin_test.rb +30 -0
  233. data/test/unit/lookups/postcode_anywhere_uk_test.rb +70 -0
  234. data/test/unit/lookups/smarty_streets_test.rb +71 -0
  235. data/test/unit/lookups/telize_test.rb +36 -0
  236. data/test/unit/lookups/yahoo_test.rb +35 -0
  237. data/test/unit/method_aliases_test.rb +26 -0
  238. data/test/unit/model_test.rb +38 -0
  239. data/test/unit/mongoid_test.rb +47 -0
  240. data/test/unit/near_test.rb +87 -0
  241. data/test/unit/oauth_util_test.rb +31 -0
  242. data/test/unit/proxy_test.rb +37 -0
  243. data/test/unit/query_test.rb +52 -0
  244. data/test/unit/rake_task_test.rb +21 -0
  245. data/test/unit/request_test.rb +35 -0
  246. data/test/unit/result_test.rb +72 -0
  247. data/test/unit/test_mode_test.rb +70 -0
  248. metadata +294 -0
@@ -0,0 +1,302 @@
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
+ def initialize
19
+ @cache = nil
20
+ end
21
+
22
+ ##
23
+ # Human-readable name of the geocoding API.
24
+ #
25
+ def name
26
+ fail
27
+ end
28
+
29
+ ##
30
+ # Symbol which is used in configuration to refer to this Lookup.
31
+ #
32
+ def handle
33
+ str = self.class.to_s
34
+ str[str.rindex(':')+1..-1].gsub(/([a-z\d]+)([A-Z])/,'\1_\2').downcase.to_sym
35
+ end
36
+
37
+ ##
38
+ # Query the geocoding API and return a Geocoder::Result object.
39
+ # Returns +nil+ on timeout or error.
40
+ #
41
+ # Takes a search string (eg: "Mississippi Coast Coliseumf, Biloxi, MS",
42
+ # "205.128.54.202") for geocoding, or coordinates (latitude, longitude)
43
+ # for reverse geocoding. Returns an array of <tt>Geocoder::Result</tt>s.
44
+ #
45
+ def search(query, options = {})
46
+ query = Geocoder::Query.new(query, options) unless query.is_a?(Geocoder::Query)
47
+ results(query).map{ |r|
48
+ result = result_class.new(r)
49
+ result.cache_hit = @cache_hit if cache
50
+ result
51
+ }
52
+ end
53
+
54
+ ##
55
+ # Return the URL for a map of the given coordinates.
56
+ #
57
+ # Not necessarily implemented by all subclasses as only some lookups
58
+ # also provide maps.
59
+ #
60
+ def map_link_url(coordinates)
61
+ nil
62
+ end
63
+
64
+ ##
65
+ # Array containing string descriptions of keys required by the API.
66
+ # Empty array if keys are optional or not required.
67
+ #
68
+ def required_api_key_parts
69
+ []
70
+ end
71
+
72
+ ##
73
+ # URL to use for querying the geocoding engine.
74
+ #
75
+ def query_url(query)
76
+ fail
77
+ end
78
+
79
+ ##
80
+ # The working Cache object.
81
+ #
82
+ def cache
83
+ if @cache.nil? and store = configuration.cache
84
+ @cache = Cache.new(store, configuration.cache_prefix)
85
+ end
86
+ @cache
87
+ end
88
+
89
+ private # -------------------------------------------------------------
90
+
91
+ ##
92
+ # An object with configuration data for this particular lookup.
93
+ #
94
+ def configuration
95
+ Geocoder.config_for_lookup(handle)
96
+ end
97
+
98
+ ##
99
+ # Object used to make HTTP requests.
100
+ #
101
+ def http_client
102
+ protocol = "http#{'s' if use_ssl?}"
103
+ proxy_name = "#{protocol}_proxy"
104
+ if proxy = configuration.send(proxy_name)
105
+ proxy_url = !!(proxy =~ /^#{protocol}/) ? proxy : protocol + '://' + proxy
106
+ begin
107
+ uri = URI.parse(proxy_url)
108
+ rescue URI::InvalidURIError
109
+ raise ConfigurationError,
110
+ "Error parsing #{protocol.upcase} proxy URL: '#{proxy_url}'"
111
+ end
112
+ Net::HTTP::Proxy(uri.host, uri.port, uri.user, uri.password)
113
+ else
114
+ Net::HTTP
115
+ end
116
+ end
117
+
118
+ ##
119
+ # Geocoder::Result object or nil on timeout or other error.
120
+ #
121
+ def results(query)
122
+ fail
123
+ end
124
+
125
+ def query_url_params(query)
126
+ query.options[:params] || {}
127
+ end
128
+
129
+ def url_query_string(query)
130
+ hash_to_query(
131
+ query_url_params(query).reject{ |key,value| value.nil? }
132
+ )
133
+ end
134
+
135
+ ##
136
+ # Key to use for caching a geocoding result. Usually this will be the
137
+ # request URL, but in cases where OAuth is used and the nonce,
138
+ # timestamp, etc varies from one request to another, we need to use
139
+ # something else (like the URL before OAuth encoding).
140
+ #
141
+ def cache_key(query)
142
+ query_url(query)
143
+ end
144
+
145
+ ##
146
+ # Class of the result objects
147
+ #
148
+ def result_class
149
+ Geocoder::Result.const_get(self.class.to_s.split(":").last)
150
+ end
151
+
152
+ ##
153
+ # Raise exception if configuration specifies it should be raised.
154
+ # Return false if exception not raised.
155
+ #
156
+ def raise_error(error, message = nil)
157
+ exceptions = configuration.always_raise
158
+ if exceptions == :all or exceptions.include?( error.is_a?(Class) ? error : error.class )
159
+ raise error, message
160
+ else
161
+ false
162
+ end
163
+ end
164
+
165
+ ##
166
+ # Returns a parsed search result (Ruby hash).
167
+ #
168
+ def fetch_data(query)
169
+ parse_raw_data fetch_raw_data(query)
170
+ rescue SocketError => err
171
+ raise_error(err) or warn "Geocoding API connection cannot be established."
172
+ rescue Errno::ECONNREFUSED => err
173
+ raise_error(err) or warn "Geocoding API connection refused."
174
+ rescue TimeoutError => err
175
+ raise_error(err) or warn "Geocoding API not responding fast enough " +
176
+ "(use Geocoder.configure(:timeout => ...) to set limit)."
177
+ end
178
+
179
+ def parse_json(data)
180
+ if defined?(ActiveSupport::JSON)
181
+ ActiveSupport::JSON.decode(data)
182
+ else
183
+ JSON.parse(data)
184
+ end
185
+ end
186
+
187
+ ##
188
+ # Parses a raw search result (returns hash or array).
189
+ #
190
+ def parse_raw_data(raw_data)
191
+ parse_json(raw_data)
192
+ rescue
193
+ warn "Geocoding API's response was not valid JSON."
194
+ end
195
+
196
+ ##
197
+ # Protocol to use for communication with geocoding services.
198
+ # Set in configuration but not available for every service.
199
+ #
200
+ def protocol
201
+ "http" + (use_ssl? ? "s" : "")
202
+ end
203
+
204
+ def valid_response?(response)
205
+ (200..399).include?(response.code.to_i)
206
+ end
207
+
208
+ ##
209
+ # Fetch a raw geocoding result (JSON string).
210
+ # The result might or might not be cached.
211
+ #
212
+ def fetch_raw_data(query)
213
+ key = cache_key(query)
214
+ if cache and body = cache[key]
215
+ @cache_hit = true
216
+ else
217
+ check_api_key_configuration!(query)
218
+ response = make_api_request(query)
219
+ check_response_for_errors!(response)
220
+ body = response.body
221
+
222
+ # apply the charset from the Content-Type header, if possible
223
+ ct = response['content-type']
224
+
225
+ if ct && ct['charset']
226
+ charset = ct.split(';').select do |s|
227
+ s['charset']
228
+ end.first.to_s.split('=')
229
+ if charset.length == 2
230
+ body.force_encoding(charset.last) rescue ArgumentError
231
+ end
232
+ end
233
+
234
+ if cache and valid_response?(response)
235
+ cache[key] = body
236
+ end
237
+ @cache_hit = false
238
+ end
239
+ body
240
+ end
241
+
242
+ def check_response_for_errors!(response)
243
+ if response.code.to_i == 400
244
+ raise_error(Geocoder::InvalidRequest) ||
245
+ warn("Geocoding API error: 400 Bad Request")
246
+ elsif response.code.to_i == 401
247
+ raise_error(Geocoder::RequestDenied) ||
248
+ warn("Geocoding API error: 401 Unauthorized")
249
+ elsif response.code.to_i == 402
250
+ raise_error(Geocoder::OverQueryLimitError) ||
251
+ warn("Geocoding API error: 402 Payment Required")
252
+ elsif response.code.to_i == 429
253
+ raise_error(Geocoder::OverQueryLimitError) ||
254
+ warn("Geocoding API error: 429 Too Many Requests")
255
+ end
256
+ end
257
+
258
+ ##
259
+ # Make an HTTP(S) request to a geocoding API and
260
+ # return the response object.
261
+ #
262
+ def make_api_request(query)
263
+ timeout(configuration.timeout) do
264
+ uri = URI.parse(query_url(query))
265
+ args = [uri.host, uri.port]
266
+ args = args.push(uri.user, uri.password) unless uri.user.nil? or uri.password.nil?
267
+ opts = {}
268
+ opts[:use_ssl] = use_ssl?
269
+
270
+ http_client.start(*args, opts) do |client|
271
+ client.get(uri.request_uri, configuration.http_headers)
272
+ end
273
+ end
274
+ end
275
+
276
+ def use_ssl?
277
+ configuration.use_https
278
+ end
279
+
280
+ def check_api_key_configuration!(query)
281
+ key_parts = query.lookup.required_api_key_parts
282
+ if key_parts.size > Array(configuration.api_key).size
283
+ parts_string = key_parts.size == 1 ? key_parts.first : key_parts
284
+ raise Geocoder::ConfigurationError,
285
+ "The #{query.lookup.name} API requires a key to be configured: " +
286
+ parts_string.inspect
287
+ end
288
+ end
289
+
290
+ ##
291
+ # Simulate ActiveSupport's Object#to_query.
292
+ # Removes any keys with nil value.
293
+ #
294
+ def hash_to_query(hash)
295
+ require 'cgi' unless defined?(CGI) && defined?(CGI.escape)
296
+ hash.collect{ |p|
297
+ p[1].nil? ? nil : p.map{ |i| CGI.escape i.to_s } * '='
298
+ }.compact.sort * '&'
299
+ end
300
+ end
301
+ end
302
+ end
@@ -0,0 +1,59 @@
1
+ require 'geocoder/lookups/base'
2
+ require "geocoder/results/bing"
3
+
4
+ module Geocoder::Lookup
5
+ class Bing < Base
6
+
7
+ def name
8
+ "Bing"
9
+ end
10
+
11
+ def map_link_url(coordinates)
12
+ "http://www.bing.com/maps/default.aspx?cp=#{coordinates.join('~')}"
13
+ end
14
+
15
+ def required_api_key_parts
16
+ ["key"]
17
+ end
18
+
19
+ def query_url(query)
20
+ base_url(query) + url_query_string(query)
21
+ end
22
+
23
+ private # ---------------------------------------------------------------
24
+
25
+ def base_url(query)
26
+ url = "#{protocol}://dev.virtualearth.net/REST/v1/Locations"
27
+
28
+ if !query.reverse_geocode?
29
+ if r = query.options[:region]
30
+ url << "/#{r}"
31
+ end
32
+ # use the more forgiving 'unstructured' query format to allow special
33
+ # chars, newlines, brackets, typos.
34
+ url + "?q=" + URI.escape(query.sanitized_text.strip) + "&"
35
+ else
36
+ url + "/#{URI.escape(query.sanitized_text.strip)}?"
37
+ end
38
+ end
39
+
40
+ def results(query)
41
+ return [] unless doc = fetch_data(query)
42
+
43
+ if doc['statusCode'] == 200
44
+ return doc['resourceSets'].first['estimatedTotal'] > 0 ? doc['resourceSets'].first['resources'] : []
45
+ elsif doc['statusCode'] == 401 and doc["authenticationResultCode"] == "InvalidCredentials"
46
+ raise_error(Geocoder::InvalidApiKey) || warn("Invalid Bing API key.")
47
+ else
48
+ warn "Bing Geocoding API error: #{doc['statusCode']} (#{doc['statusDescription']})."
49
+ end
50
+ return []
51
+ end
52
+
53
+ def query_url_params(query)
54
+ {
55
+ key: configuration.api_key
56
+ }.merge(super)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,20 @@
1
+ # More information about the Data Science Toolkit can be found at:
2
+ # http://www.datasciencetoolkit.org/. The provided APIs mimic the
3
+ # Google geocoding api.
4
+
5
+ require 'geocoder/lookups/google'
6
+ require 'geocoder/results/dstk'
7
+
8
+ module Geocoder::Lookup
9
+ class Dstk < Google
10
+
11
+ def name
12
+ "Data Science Toolkit"
13
+ end
14
+
15
+ def query_url(query)
16
+ host = configuration[:host] || "www.datasciencetoolkit.org"
17
+ "#{protocol}://#{host}/maps/api/geocode/json?" + url_query_string(query)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,48 @@
1
+ require 'geocoder/lookups/base'
2
+ require "geocoder/results/esri"
3
+
4
+ module Geocoder::Lookup
5
+ class Esri < Base
6
+
7
+ def name
8
+ "Esri"
9
+ end
10
+
11
+ def query_url(query)
12
+ search_keyword = query.reverse_geocode? ? "reverseGeocode" : "find"
13
+
14
+ "#{protocol}://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/#{search_keyword}?" +
15
+ url_query_string(query)
16
+ end
17
+
18
+ private # ---------------------------------------------------------------
19
+
20
+ def results(query)
21
+ return [] unless doc = fetch_data(query)
22
+
23
+ if (!query.reverse_geocode?)
24
+ return [] if doc['locations'].empty?
25
+ end
26
+
27
+ if (doc['error'].nil?)
28
+ return [ doc ]
29
+ else
30
+ return []
31
+ end
32
+ end
33
+
34
+ def query_url_params(query)
35
+ params = {
36
+ :f => "pjson",
37
+ :outFields => "*"
38
+ }
39
+ if query.reverse_geocode?
40
+ params[:location] = query.coordinates.reverse.join(',')
41
+ else
42
+ params[:text] = query.sanitized_text
43
+ end
44
+ params.merge(super)
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,47 @@
1
+ require 'geocoder/lookups/base'
2
+ require 'geocoder/results/freegeoip'
3
+
4
+ module Geocoder::Lookup
5
+ class Freegeoip < Base
6
+
7
+ def name
8
+ "FreeGeoIP"
9
+ end
10
+
11
+ def query_url(query)
12
+ "#{protocol}://#{host}/json/#{query.sanitized_text}"
13
+ end
14
+
15
+ private # ---------------------------------------------------------------
16
+
17
+ def parse_raw_data(raw_data)
18
+ raw_data.match(/^<html><title>404/) ? nil : super(raw_data)
19
+ end
20
+
21
+ def results(query)
22
+ # don't look up a loopback address, just return the stored result
23
+ return [reserved_result(query.text)] if query.loopback_ip_address?
24
+ # note: Freegeoip.net returns plain text "Not Found" on bad request
25
+ (doc = fetch_data(query)) ? [doc] : []
26
+ end
27
+
28
+ def reserved_result(ip)
29
+ {
30
+ "ip" => ip,
31
+ "city" => "",
32
+ "region_code" => "",
33
+ "region_name" => "",
34
+ "metrocode" => "",
35
+ "zipcode" => "",
36
+ "latitude" => "0",
37
+ "longitude" => "0",
38
+ "country_name" => "Reserved",
39
+ "country_code" => "RD"
40
+ }
41
+ end
42
+
43
+ def host
44
+ configuration[:host] || "freegeoip.net"
45
+ end
46
+ end
47
+ end