bw-geocoder 1.2.5

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 (233) hide show
  1. data/.gitignore +6 -0
  2. data/.travis.yml +31 -0
  3. data/CHANGELOG.md +377 -0
  4. data/LICENSE +20 -0
  5. data/README.md +1041 -0
  6. data/Rakefile +25 -0
  7. data/bin/geocode +5 -0
  8. data/examples/autoexpire_cache_dalli.rb +62 -0
  9. data/examples/autoexpire_cache_redis.rb +28 -0
  10. data/examples/cache_bypass.rb +48 -0
  11. data/gemfiles/Gemfile.mongoid-2.4.x +16 -0
  12. data/lib/generators/geocoder/config/config_generator.rb +14 -0
  13. data/lib/generators/geocoder/config/templates/initializer.rb +21 -0
  14. data/lib/generators/geocoder/maxmind/geolite_city_generator.rb +28 -0
  15. data/lib/generators/geocoder/maxmind/geolite_country_generator.rb +28 -0
  16. data/lib/generators/geocoder/maxmind/templates/migration/geolite_city.rb +30 -0
  17. data/lib/generators/geocoder/maxmind/templates/migration/geolite_country.rb +17 -0
  18. data/lib/geocoder/cache.rb +90 -0
  19. data/lib/geocoder/calculations.rb +428 -0
  20. data/lib/geocoder/cli.rb +121 -0
  21. data/lib/geocoder/configuration.rb +124 -0
  22. data/lib/geocoder/configuration_hash.rb +11 -0
  23. data/lib/geocoder/exceptions.rb +21 -0
  24. data/lib/geocoder/ip_address.rb +21 -0
  25. data/lib/geocoder/lookup.rb +100 -0
  26. data/lib/geocoder/lookups/baidu.rb +55 -0
  27. data/lib/geocoder/lookups/baidu_ip.rb +54 -0
  28. data/lib/geocoder/lookups/base.rb +302 -0
  29. data/lib/geocoder/lookups/bing.rb +59 -0
  30. data/lib/geocoder/lookups/dstk.rb +20 -0
  31. data/lib/geocoder/lookups/esri.rb +48 -0
  32. data/lib/geocoder/lookups/freegeoip.rb +47 -0
  33. data/lib/geocoder/lookups/geocoder_ca.rb +54 -0
  34. data/lib/geocoder/lookups/geocoder_us.rb +39 -0
  35. data/lib/geocoder/lookups/geocodio.rb +42 -0
  36. data/lib/geocoder/lookups/google.rb +67 -0
  37. data/lib/geocoder/lookups/google_places_details.rb +50 -0
  38. data/lib/geocoder/lookups/google_premier.rb +47 -0
  39. data/lib/geocoder/lookups/here.rb +62 -0
  40. data/lib/geocoder/lookups/ip_address_labs.rb +43 -0
  41. data/lib/geocoder/lookups/mapquest.rb +60 -0
  42. data/lib/geocoder/lookups/maxmind.rb +90 -0
  43. data/lib/geocoder/lookups/maxmind_local.rb +58 -0
  44. data/lib/geocoder/lookups/nominatim.rb +52 -0
  45. data/lib/geocoder/lookups/okf.rb +43 -0
  46. data/lib/geocoder/lookups/opencagedata.rb +58 -0
  47. data/lib/geocoder/lookups/ovi.rb +62 -0
  48. data/lib/geocoder/lookups/pointpin.rb +68 -0
  49. data/lib/geocoder/lookups/smarty_streets.rb +45 -0
  50. data/lib/geocoder/lookups/telize.rb +40 -0
  51. data/lib/geocoder/lookups/test.rb +44 -0
  52. data/lib/geocoder/lookups/yahoo.rb +88 -0
  53. data/lib/geocoder/lookups/yandex.rb +54 -0
  54. data/lib/geocoder/models/active_record.rb +50 -0
  55. data/lib/geocoder/models/base.rb +39 -0
  56. data/lib/geocoder/models/mongo_base.rb +64 -0
  57. data/lib/geocoder/models/mongo_mapper.rb +26 -0
  58. data/lib/geocoder/models/mongoid.rb +32 -0
  59. data/lib/geocoder/query.rb +111 -0
  60. data/lib/geocoder/railtie.rb +26 -0
  61. data/lib/geocoder/request.rb +25 -0
  62. data/lib/geocoder/results/baidu.rb +79 -0
  63. data/lib/geocoder/results/baidu_ip.rb +62 -0
  64. data/lib/geocoder/results/base.rb +67 -0
  65. data/lib/geocoder/results/bing.rb +48 -0
  66. data/lib/geocoder/results/dstk.rb +6 -0
  67. data/lib/geocoder/results/esri.rb +51 -0
  68. data/lib/geocoder/results/freegeoip.rb +45 -0
  69. data/lib/geocoder/results/geocoder_ca.rb +60 -0
  70. data/lib/geocoder/results/geocoder_us.rb +39 -0
  71. data/lib/geocoder/results/geocodio.rb +66 -0
  72. data/lib/geocoder/results/google.rb +124 -0
  73. data/lib/geocoder/results/google_places_details.rb +35 -0
  74. data/lib/geocoder/results/google_premier.rb +6 -0
  75. data/lib/geocoder/results/here.rb +62 -0
  76. data/lib/geocoder/results/ip_address_labs.rb +78 -0
  77. data/lib/geocoder/results/mapquest.rb +51 -0
  78. data/lib/geocoder/results/maxmind.rb +135 -0
  79. data/lib/geocoder/results/maxmind_local.rb +49 -0
  80. data/lib/geocoder/results/nominatim.rb +94 -0
  81. data/lib/geocoder/results/okf.rb +106 -0
  82. data/lib/geocoder/results/opencagedata.rb +82 -0
  83. data/lib/geocoder/results/ovi.rb +62 -0
  84. data/lib/geocoder/results/pointpin.rb +44 -0
  85. data/lib/geocoder/results/smarty_streets.rb +106 -0
  86. data/lib/geocoder/results/telize.rb +45 -0
  87. data/lib/geocoder/results/test.rb +33 -0
  88. data/lib/geocoder/results/yahoo.rb +55 -0
  89. data/lib/geocoder/results/yandex.rb +84 -0
  90. data/lib/geocoder/sql.rb +107 -0
  91. data/lib/geocoder/stores/active_record.rb +278 -0
  92. data/lib/geocoder/stores/base.rb +127 -0
  93. data/lib/geocoder/stores/mongo_base.rb +89 -0
  94. data/lib/geocoder/stores/mongo_mapper.rb +13 -0
  95. data/lib/geocoder/stores/mongoid.rb +13 -0
  96. data/lib/geocoder/version.rb +3 -0
  97. data/lib/geocoder.rb +47 -0
  98. data/lib/hash_recursive_merge.rb +74 -0
  99. data/lib/maxmind_database.rb +109 -0
  100. data/lib/oauth_util.rb +112 -0
  101. data/lib/tasks/geocoder.rake +29 -0
  102. data/lib/tasks/maxmind.rake +73 -0
  103. data/test/fixtures/baidu_invalid_key +1 -0
  104. data/test/fixtures/baidu_ip_202_198_16_3 +19 -0
  105. data/test/fixtures/baidu_ip_invalid_key +1 -0
  106. data/test/fixtures/baidu_ip_no_results +1 -0
  107. data/test/fixtures/baidu_no_results +1 -0
  108. data/test/fixtures/baidu_reverse +1 -0
  109. data/test/fixtures/baidu_shanghai_pearl_tower +12 -0
  110. data/test/fixtures/bing_invalid_key +1 -0
  111. data/test/fixtures/bing_madison_square_garden +40 -0
  112. data/test/fixtures/bing_no_results +16 -0
  113. data/test/fixtures/bing_reverse +42 -0
  114. data/test/fixtures/cloudmade_invalid_key +1 -0
  115. data/test/fixtures/cloudmade_madison_square_garden +1 -0
  116. data/test/fixtures/cloudmade_no_results +1 -0
  117. data/test/fixtures/esri_madison_square_garden +59 -0
  118. data/test/fixtures/esri_no_results +8 -0
  119. data/test/fixtures/esri_reverse +21 -0
  120. data/test/fixtures/freegeoip_74_200_247_59 +12 -0
  121. data/test/fixtures/freegeoip_no_results +1 -0
  122. data/test/fixtures/geocoder_ca_madison_square_garden +1 -0
  123. data/test/fixtures/geocoder_ca_no_results +1 -0
  124. data/test/fixtures/geocoder_ca_reverse +34 -0
  125. data/test/fixtures/geocoder_us_madison_square_garden +1 -0
  126. data/test/fixtures/geocoder_us_no_results +1 -0
  127. data/test/fixtures/geocodio_1101_pennsylvania_ave +1 -0
  128. data/test/fixtures/geocodio_bad_api_key +3 -0
  129. data/test/fixtures/geocodio_invalid +4 -0
  130. data/test/fixtures/geocodio_no_results +1 -0
  131. data/test/fixtures/geocodio_over_query_limit +4 -0
  132. data/test/fixtures/google_garbage +456 -0
  133. data/test/fixtures/google_madison_square_garden +57 -0
  134. data/test/fixtures/google_no_city_data +44 -0
  135. data/test/fixtures/google_no_locality +51 -0
  136. data/test/fixtures/google_no_results +4 -0
  137. data/test/fixtures/google_over_limit +4 -0
  138. data/test/fixtures/google_places_details_invalid_request +4 -0
  139. data/test/fixtures/google_places_details_madison_square_garden +120 -0
  140. data/test/fixtures/google_places_details_no_results +4 -0
  141. data/test/fixtures/google_places_details_no_reviews +60 -0
  142. data/test/fixtures/google_places_details_no_types +66 -0
  143. data/test/fixtures/here_madison_square_garden +72 -0
  144. data/test/fixtures/here_no_results +8 -0
  145. data/test/fixtures/mapquest_error +16 -0
  146. data/test/fixtures/mapquest_invalid_api_key +16 -0
  147. data/test/fixtures/mapquest_invalid_request +16 -0
  148. data/test/fixtures/mapquest_madison_square_garden +52 -0
  149. data/test/fixtures/mapquest_no_results +16 -0
  150. data/test/fixtures/maxmind_24_24_24_21 +1 -0
  151. data/test/fixtures/maxmind_24_24_24_22 +1 -0
  152. data/test/fixtures/maxmind_24_24_24_23 +1 -0
  153. data/test/fixtures/maxmind_24_24_24_24 +1 -0
  154. data/test/fixtures/maxmind_74_200_247_59 +1 -0
  155. data/test/fixtures/maxmind_invalid_key +1 -0
  156. data/test/fixtures/maxmind_no_results +1 -0
  157. data/test/fixtures/nominatim_madison_square_garden +150 -0
  158. data/test/fixtures/nominatim_no_results +1 -0
  159. data/test/fixtures/nominatim_over_limit +1 -0
  160. data/test/fixtures/okf_kirstinmaki +67 -0
  161. data/test/fixtures/okf_no_results +4 -0
  162. data/test/fixtures/opencagedata_invalid_api_key +25 -0
  163. data/test/fixtures/opencagedata_invalid_request +26 -0
  164. data/test/fixtures/opencagedata_madison_square_garden +73 -0
  165. data/test/fixtures/opencagedata_no_results +29 -0
  166. data/test/fixtures/opencagedata_over_limit +31 -0
  167. data/test/fixtures/ovi_madison_square_garden +72 -0
  168. data/test/fixtures/ovi_no_results +8 -0
  169. data/test/fixtures/pointpin_10_10_10_10 +1 -0
  170. data/test/fixtures/pointpin_555_555_555_555 +1 -0
  171. data/test/fixtures/pointpin_80_111_555_555 +1 -0
  172. data/test/fixtures/pointpin_no_results +1 -0
  173. data/test/fixtures/smarty_streets_11211 +1 -0
  174. data/test/fixtures/smarty_streets_madison_square_garden +47 -0
  175. data/test/fixtures/smarty_streets_no_results +1 -0
  176. data/test/fixtures/telize_10_10_10_10 +1 -0
  177. data/test/fixtures/telize_555_555_555_555 +4 -0
  178. data/test/fixtures/telize_74_200_247_59 +1 -0
  179. data/test/fixtures/telize_no_results +1 -0
  180. data/test/fixtures/yahoo_error +1 -0
  181. data/test/fixtures/yahoo_invalid_key +2 -0
  182. data/test/fixtures/yahoo_madison_square_garden +52 -0
  183. data/test/fixtures/yahoo_no_results +10 -0
  184. data/test/fixtures/yahoo_over_limit +2 -0
  185. data/test/fixtures/yandex_canada_rue_dupuis_14 +446 -0
  186. data/test/fixtures/yandex_invalid_key +1 -0
  187. data/test/fixtures/yandex_kremlin +48 -0
  188. data/test/fixtures/yandex_new_york +1 -0
  189. data/test/fixtures/yandex_no_city_and_town +112 -0
  190. data/test/fixtures/yandex_no_results +16 -0
  191. data/test/integration/http_client_test.rb +31 -0
  192. data/test/mongoid_test_helper.rb +43 -0
  193. data/test/test_helper.rb +386 -0
  194. data/test/unit/active_record_test.rb +16 -0
  195. data/test/unit/cache_test.rb +37 -0
  196. data/test/unit/calculations_test.rb +220 -0
  197. data/test/unit/configuration_test.rb +55 -0
  198. data/test/unit/error_handling_test.rb +56 -0
  199. data/test/unit/geocoder_test.rb +78 -0
  200. data/test/unit/https_test.rb +17 -0
  201. data/test/unit/ip_address_test.rb +27 -0
  202. data/test/unit/lookup_test.rb +153 -0
  203. data/test/unit/lookups/bing_test.rb +68 -0
  204. data/test/unit/lookups/dstk_test.rb +26 -0
  205. data/test/unit/lookups/esri_test.rb +48 -0
  206. data/test/unit/lookups/freegeoip_test.rb +27 -0
  207. data/test/unit/lookups/geocoder_ca_test.rb +17 -0
  208. data/test/unit/lookups/geocodio_test.rb +55 -0
  209. data/test/unit/lookups/google_places_details_test.rb +122 -0
  210. data/test/unit/lookups/google_premier_test.rb +22 -0
  211. data/test/unit/lookups/google_test.rb +84 -0
  212. data/test/unit/lookups/mapquest_test.rb +60 -0
  213. data/test/unit/lookups/maxmind_local_test.rb +28 -0
  214. data/test/unit/lookups/maxmind_test.rb +63 -0
  215. data/test/unit/lookups/nominatim_test.rb +31 -0
  216. data/test/unit/lookups/okf_test.rb +38 -0
  217. data/test/unit/lookups/opencagedata_test.rb +64 -0
  218. data/test/unit/lookups/pointpin_test.rb +30 -0
  219. data/test/unit/lookups/smarty_streets_test.rb +71 -0
  220. data/test/unit/lookups/telize_test.rb +36 -0
  221. data/test/unit/lookups/yahoo_test.rb +35 -0
  222. data/test/unit/method_aliases_test.rb +26 -0
  223. data/test/unit/model_test.rb +38 -0
  224. data/test/unit/mongoid_test.rb +47 -0
  225. data/test/unit/near_test.rb +87 -0
  226. data/test/unit/oauth_util_test.rb +31 -0
  227. data/test/unit/proxy_test.rb +37 -0
  228. data/test/unit/query_test.rb +52 -0
  229. data/test/unit/rake_task_test.rb +21 -0
  230. data/test/unit/request_test.rb +35 -0
  231. data/test/unit/result_test.rb +72 -0
  232. data/test/unit/test_mode_test.rb +70 -0
  233. metadata +281 -0
@@ -0,0 +1,21 @@
1
+ module Geocoder
2
+ class IpAddress < String
3
+
4
+ def loopback?
5
+ valid? and (self == "0.0.0.0" or self.match(/\A127\./) or self == "::1")
6
+ end
7
+
8
+ def valid?
9
+ ipregex = %r{
10
+ \A( # String Starts
11
+ ((::ffff:)?((\d{1,3})\.){3}\d{1,3}) # Check for IPv4
12
+ | # .... Or
13
+ (\S+?(:\S+?){6}\S+) # Check for IPv6
14
+ | # .... Or
15
+ (::1) # IPv6 loopback
16
+ )\z
17
+ }x
18
+ !!self.match(ipregex)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,100 @@
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
+ :dstk,
25
+ :esri,
26
+ :google,
27
+ :google_premier,
28
+ :google_places_details,
29
+ :yahoo,
30
+ :bing,
31
+ :geocoder_ca,
32
+ :geocoder_us,
33
+ :yandex,
34
+ :nominatim,
35
+ :mapquest,
36
+ :opencagedata,
37
+ :ovi,
38
+ :here,
39
+ :baidu,
40
+ :geocodio,
41
+ :smarty_streets,
42
+ :okf,
43
+ :test
44
+ ]
45
+ end
46
+
47
+ ##
48
+ # All IP address lookup services, default first.
49
+ #
50
+ def ip_services
51
+ [
52
+ :baidu_ip,
53
+ :freegeoip,
54
+ :maxmind,
55
+ :maxmind_local,
56
+ :telize,
57
+ :pointpin,
58
+ :ip_address_labs
59
+ ]
60
+ end
61
+
62
+ ##
63
+ # Retrieve a Lookup object from the store.
64
+ # Use this instead of Geocoder::Lookup::X.new to get an
65
+ # already-configured Lookup object.
66
+ #
67
+ def get(name)
68
+ @services = {} unless defined?(@services)
69
+ @services[name] = spawn(name) unless @services.include?(name)
70
+ @services[name]
71
+ end
72
+
73
+
74
+ private # -----------------------------------------------------------------
75
+
76
+ ##
77
+ # Spawn a Lookup of the given name.
78
+ #
79
+ def spawn(name)
80
+ if all_services.include?(name)
81
+ Geocoder::Lookup.const_get(classify_name(name)).new
82
+ else
83
+ valids = all_services.map(&:inspect).join(", ")
84
+ raise ConfigurationError, "Please specify a valid lookup for Geocoder " +
85
+ "(#{name.inspect} is not one of: #{valids})."
86
+ end
87
+ end
88
+
89
+ ##
90
+ # Convert an "underscore" version of a name into a "class" version.
91
+ #
92
+ def classify_name(filename)
93
+ filename.to_s.split("_").map{ |i| i[0...1].upcase + i[1..-1] }.join
94
+ end
95
+ end
96
+ end
97
+
98
+ Geocoder::Lookup.all_services.each do |name|
99
+ require "geocoder/lookups/#{name}"
100
+ end
@@ -0,0 +1,55 @@
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
+ def query_url(query)
16
+ "http://api.map.baidu.com/geocoder/v2/?" + url_query_string(query)
17
+ end
18
+
19
+ private # ---------------------------------------------------------------
20
+
21
+ def results(query, reverse = false)
22
+ return [] unless doc = fetch_data(query)
23
+ case doc['status']
24
+ when 0
25
+ return [doc['result']] unless doc['result'].blank?
26
+ when 1, 3, 4
27
+ raise_error(Geocoder::Error, "server error.") ||
28
+ warn("Baidu Geocoding API error: server error.")
29
+ when 2
30
+ raise_error(Geocoder::InvalidRequest, "invalid request.") ||
31
+ warn("Baidu Geocoding API error: invalid request.")
32
+ when 5
33
+ raise_error(Geocoder::InvalidApiKey, "invalid api key") ||
34
+ warn("Baidu Geocoding API error: invalid api key.")
35
+ when 101, 102, 200..299
36
+ raise_error(Geocoder::RequestDenied, "request denied") ||
37
+ warn("Baidu Geocoding API error: request denied.")
38
+ when 300..399
39
+ raise_error(Geocoder::OverQueryLimitError, "over query limit.") ||
40
+ warn("Baidu Geocoding API error: over query limit.")
41
+ end
42
+ return []
43
+ end
44
+
45
+ def query_url_params(query)
46
+ {
47
+ (query.reverse_geocode? ? :location : :address) => query.sanitized_text,
48
+ :ak => configuration.api_key,
49
+ :output => "json"
50
+ }.merge(super)
51
+ end
52
+
53
+ end
54
+ end
55
+
@@ -0,0 +1,54 @@
1
+ require 'geocoder/lookups/base'
2
+ require 'geocoder/results/baidu_ip'
3
+
4
+ module Geocoder::Lookup
5
+ class BaiduIp < Base
6
+
7
+ def name
8
+ "Baidu IP"
9
+ end
10
+
11
+ def required_api_key_parts
12
+ ["key"]
13
+ end
14
+
15
+ def query_url(query)
16
+ "http://api.map.baidu.com/location/ip?" + url_query_string(query)
17
+ end
18
+
19
+ private # ---------------------------------------------------------------
20
+
21
+ def results(query, reverse = false)
22
+ return [] unless doc = fetch_data(query)
23
+ case doc['status']
24
+ when 0
25
+ return [doc['content']] unless doc['content'].blank?
26
+ when 1, 3, 4
27
+ raise_error(Geocoder::Error, "server error.") ||
28
+ warn("Baidu IP Geocoding API error: server error.")
29
+ when 2
30
+ raise_error(Geocoder::InvalidRequest, "invalid request.") ||
31
+ warn("Baidu IP Geocoding API error: invalid request.")
32
+ when 5
33
+ raise_error(Geocoder::InvalidApiKey, "invalid api key.") ||
34
+ warn("Baidu IP Geocoding API error: invalid api key.")
35
+ when 101, 102, 200..299
36
+ raise_error(Geocoder::RequestDenied, "request denied.") ||
37
+ warn("Baidu IP Geocoding API error: request denied.")
38
+ when 300..399
39
+ raise_error(Geocoder::OverQueryLimitError, "over query limit") ||
40
+ warn("Baidu IP Geocoding API error: over query limit.")
41
+ end
42
+ return []
43
+ end
44
+
45
+ def query_url_params(query)
46
+ {
47
+ :ip => query.sanitized_text,
48
+ :ak => configuration.api_key,
49
+ :coor => "bd09ll"
50
+ }.merge(super)
51
+ end
52
+
53
+ end
54
+ end
@@ -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