geocoder-kb 1.2.6

Sign up to get free protection for your applications and to get access to all the features.
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