geocoder 1.1.9 → 1.3.7

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 (174) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +157 -0
  3. data/README.md +467 -70
  4. data/examples/reverse_geocode_job.rb +40 -0
  5. data/lib/generators/geocoder/config/templates/initializer.rb +16 -16
  6. data/lib/generators/geocoder/maxmind/geolite_city_generator.rb +28 -0
  7. data/lib/generators/geocoder/maxmind/geolite_country_generator.rb +28 -0
  8. data/lib/generators/geocoder/maxmind/templates/migration/geolite_city.rb +30 -0
  9. data/lib/generators/geocoder/maxmind/templates/migration/geolite_country.rb +17 -0
  10. data/lib/geocoder/cache.rb +3 -2
  11. data/lib/geocoder/calculations.rb +44 -2
  12. data/lib/geocoder/configuration.rb +17 -10
  13. data/lib/geocoder/esri_token.rb +38 -0
  14. data/lib/geocoder/exceptions.rb +19 -0
  15. data/lib/geocoder/ip_address.rb +13 -0
  16. data/lib/geocoder/kernel_logger.rb +25 -0
  17. data/lib/geocoder/logger.rb +47 -0
  18. data/lib/geocoder/lookup.rb +32 -8
  19. data/lib/geocoder/lookups/baidu.rb +18 -13
  20. data/lib/geocoder/lookups/baidu_ip.rb +59 -0
  21. data/lib/geocoder/lookups/base.rb +81 -19
  22. data/lib/geocoder/lookups/bing.rb +40 -7
  23. data/lib/geocoder/lookups/esri.rb +42 -5
  24. data/lib/geocoder/lookups/freegeoip.rb +9 -1
  25. data/lib/geocoder/lookups/geocoder_ca.rb +1 -2
  26. data/lib/geocoder/lookups/geocoder_us.rb +6 -2
  27. data/lib/geocoder/lookups/geocodio.rb +42 -0
  28. data/lib/geocoder/lookups/geoip2.rb +45 -0
  29. data/lib/geocoder/lookups/geoportail_lu.rb +65 -0
  30. data/lib/geocoder/lookups/google.rb +29 -5
  31. data/lib/geocoder/lookups/google_places_details.rb +50 -0
  32. data/lib/geocoder/lookups/google_premier.rb +1 -1
  33. data/lib/geocoder/lookups/here.rb +62 -0
  34. data/lib/geocoder/lookups/ipapi_com.rb +86 -0
  35. data/lib/geocoder/lookups/ipinfo_io.rb +55 -0
  36. data/lib/geocoder/lookups/latlon.rb +59 -0
  37. data/lib/geocoder/lookups/mapbox.rb +53 -0
  38. data/lib/geocoder/lookups/mapquest.rb +6 -6
  39. data/lib/geocoder/lookups/mapzen.rb +15 -0
  40. data/lib/geocoder/lookups/maxmind.rb +4 -2
  41. data/lib/geocoder/lookups/maxmind_geoip2.rb +69 -0
  42. data/lib/geocoder/lookups/maxmind_local.rb +65 -0
  43. data/lib/geocoder/lookups/nominatim.rb +9 -1
  44. data/lib/geocoder/lookups/okf.rb +44 -0
  45. data/lib/geocoder/lookups/opencagedata.rb +58 -0
  46. data/lib/geocoder/lookups/pelias.rb +64 -0
  47. data/lib/geocoder/lookups/pointpin.rb +68 -0
  48. data/lib/geocoder/lookups/postcode_anywhere_uk.rb +51 -0
  49. data/lib/geocoder/lookups/smarty_streets.rb +53 -0
  50. data/lib/geocoder/lookups/telize.rb +55 -0
  51. data/lib/geocoder/lookups/yandex.rb +8 -4
  52. data/lib/geocoder/models/active_record.rb +7 -3
  53. data/lib/geocoder/models/base.rb +1 -4
  54. data/lib/geocoder/models/mongo_base.rb +6 -4
  55. data/lib/geocoder/query.rb +9 -5
  56. data/lib/geocoder/railtie.rb +1 -1
  57. data/lib/geocoder/request.rb +74 -12
  58. data/lib/geocoder/results/baidu_ip.rb +62 -0
  59. data/lib/geocoder/results/bing.rb +4 -0
  60. data/lib/geocoder/results/esri.rb +30 -6
  61. data/lib/geocoder/results/freegeoip.rb +2 -2
  62. data/lib/geocoder/results/geocodio.rb +70 -0
  63. data/lib/geocoder/results/geoip2.rb +62 -0
  64. data/lib/geocoder/results/geoportail_lu.rb +69 -0
  65. data/lib/geocoder/results/google.rb +15 -0
  66. data/lib/geocoder/results/google_places_details.rb +35 -0
  67. data/lib/geocoder/results/here.rb +71 -0
  68. data/lib/geocoder/results/ipapi_com.rb +45 -0
  69. data/lib/geocoder/results/ipinfo_io.rb +48 -0
  70. data/lib/geocoder/results/latlon.rb +71 -0
  71. data/lib/geocoder/results/mapbox.rb +47 -0
  72. data/lib/geocoder/results/mapquest.rb +5 -8
  73. data/lib/geocoder/results/mapzen.rb +5 -0
  74. data/lib/geocoder/results/maxmind_geoip2.rb +9 -0
  75. data/lib/geocoder/results/maxmind_local.rb +49 -0
  76. data/lib/geocoder/results/nominatim.rb +6 -1
  77. data/lib/geocoder/results/okf.rb +106 -0
  78. data/lib/geocoder/results/opencagedata.rb +90 -0
  79. data/lib/geocoder/results/ovi.rb +9 -0
  80. data/lib/geocoder/results/pelias.rb +58 -0
  81. data/lib/geocoder/results/pointpin.rb +40 -0
  82. data/lib/geocoder/results/postcode_anywhere_uk.rb +42 -0
  83. data/lib/geocoder/results/smarty_streets.rb +106 -0
  84. data/lib/geocoder/results/telize.rb +45 -0
  85. data/lib/geocoder/results/test.rb +20 -3
  86. data/lib/geocoder/results/yandex.rb +18 -6
  87. data/lib/geocoder/sql.rb +16 -15
  88. data/lib/geocoder/stores/active_record.rb +51 -18
  89. data/lib/geocoder/stores/base.rb +8 -12
  90. data/lib/geocoder/stores/mongo_base.rb +0 -31
  91. data/lib/geocoder/version.rb +1 -1
  92. data/lib/geocoder.rb +6 -13
  93. data/lib/maxmind_database.rb +109 -0
  94. data/lib/tasks/geocoder.rake +14 -3
  95. data/lib/tasks/maxmind.rake +73 -0
  96. metadata +59 -85
  97. data/.gitignore +0 -5
  98. data/.travis.yml +0 -27
  99. data/Rakefile +0 -25
  100. data/gemfiles/Gemfile.mongoid-2.4.x +0 -15
  101. data/lib/geocoder/lookups/yahoo.rb +0 -86
  102. data/lib/geocoder/results/yahoo.rb +0 -55
  103. data/lib/oauth_util.rb +0 -112
  104. data/test/active_record_test.rb +0 -15
  105. data/test/cache_test.rb +0 -35
  106. data/test/calculations_test.rb +0 -211
  107. data/test/configuration_test.rb +0 -78
  108. data/test/custom_block_test.rb +0 -32
  109. data/test/error_handling_test.rb +0 -43
  110. data/test/fixtures/baidu_invalid_key +0 -1
  111. data/test/fixtures/baidu_no_results +0 -1
  112. data/test/fixtures/baidu_reverse +0 -1
  113. data/test/fixtures/baidu_shanghai_pearl_tower +0 -12
  114. data/test/fixtures/bing_invalid_key +0 -1
  115. data/test/fixtures/bing_madison_square_garden +0 -40
  116. data/test/fixtures/bing_no_results +0 -16
  117. data/test/fixtures/bing_reverse +0 -42
  118. data/test/fixtures/esri_madison_square_garden +0 -59
  119. data/test/fixtures/esri_no_results +0 -8
  120. data/test/fixtures/esri_reverse +0 -21
  121. data/test/fixtures/freegeoip_74_200_247_59 +0 -12
  122. data/test/fixtures/freegeoip_no_results +0 -1
  123. data/test/fixtures/geocoder_ca_madison_square_garden +0 -1
  124. data/test/fixtures/geocoder_ca_no_results +0 -1
  125. data/test/fixtures/geocoder_ca_reverse +0 -34
  126. data/test/fixtures/geocoder_us_madison_square_garden +0 -1
  127. data/test/fixtures/geocoder_us_no_results +0 -1
  128. data/test/fixtures/google_garbage +0 -456
  129. data/test/fixtures/google_madison_square_garden +0 -57
  130. data/test/fixtures/google_no_city_data +0 -44
  131. data/test/fixtures/google_no_locality +0 -51
  132. data/test/fixtures/google_no_results +0 -4
  133. data/test/fixtures/google_over_limit +0 -4
  134. data/test/fixtures/mapquest_error +0 -16
  135. data/test/fixtures/mapquest_invalid_api_key +0 -16
  136. data/test/fixtures/mapquest_invalid_request +0 -16
  137. data/test/fixtures/mapquest_madison_square_garden +0 -52
  138. data/test/fixtures/mapquest_no_results +0 -16
  139. data/test/fixtures/maxmind_24_24_24_21 +0 -1
  140. data/test/fixtures/maxmind_24_24_24_22 +0 -1
  141. data/test/fixtures/maxmind_24_24_24_23 +0 -1
  142. data/test/fixtures/maxmind_24_24_24_24 +0 -1
  143. data/test/fixtures/maxmind_74_200_247_59 +0 -1
  144. data/test/fixtures/maxmind_invalid_key +0 -1
  145. data/test/fixtures/maxmind_no_results +0 -1
  146. data/test/fixtures/nominatim_madison_square_garden +0 -150
  147. data/test/fixtures/nominatim_no_results +0 -1
  148. data/test/fixtures/ovi_madison_square_garden +0 -72
  149. data/test/fixtures/ovi_no_results +0 -8
  150. data/test/fixtures/yahoo_error +0 -1
  151. data/test/fixtures/yahoo_invalid_key +0 -2
  152. data/test/fixtures/yahoo_madison_square_garden +0 -52
  153. data/test/fixtures/yahoo_no_results +0 -10
  154. data/test/fixtures/yahoo_over_limit +0 -2
  155. data/test/fixtures/yandex_invalid_key +0 -1
  156. data/test/fixtures/yandex_kremlin +0 -48
  157. data/test/fixtures/yandex_no_city_and_town +0 -112
  158. data/test/fixtures/yandex_no_results +0 -16
  159. data/test/geocoder_test.rb +0 -59
  160. data/test/https_test.rb +0 -16
  161. data/test/integration/smoke_test.rb +0 -26
  162. data/test/lookup_test.rb +0 -117
  163. data/test/method_aliases_test.rb +0 -25
  164. data/test/mongoid_test.rb +0 -46
  165. data/test/mongoid_test_helper.rb +0 -43
  166. data/test/near_test.rb +0 -61
  167. data/test/oauth_util_test.rb +0 -30
  168. data/test/proxy_test.rb +0 -36
  169. data/test/query_test.rb +0 -52
  170. data/test/request_test.rb +0 -29
  171. data/test/result_test.rb +0 -42
  172. data/test/services_test.rb +0 -393
  173. data/test/test_helper.rb +0 -289
  174. data/test/test_mode_test.rb +0 -59
data/lib/geocoder/sql.rb CHANGED
@@ -63,26 +63,27 @@ module Geocoder
63
63
  # http://www.beginningspatial.com/calculating_bearing_one_point_another
64
64
  #
65
65
  def full_bearing(latitude, longitude, lat_attr, lon_attr, options = {})
66
+ degrees_per_radian = Geocoder::Calculations::DEGREES_PER_RADIAN
66
67
  case options[:bearing] || Geocoder.config.distances
67
68
  when :linear
68
- "CAST(" +
69
- "DEGREES(ATAN2( " +
70
- "RADIANS(#{lon_attr} - #{longitude.to_f}), " +
71
- "RADIANS(#{lat_attr} - #{latitude.to_f})" +
72
- ")) + 360 " +
73
- "AS decimal) % 360"
69
+ "MOD(CAST(" +
70
+ "(ATAN2( " +
71
+ "((#{lon_attr} - #{longitude.to_f}) / #{degrees_per_radian}), " +
72
+ "((#{lat_attr} - #{latitude.to_f}) / #{degrees_per_radian})" +
73
+ ") * #{degrees_per_radian}) + 360 " +
74
+ "AS decimal), 360)"
74
75
  when :spherical
75
- "CAST(" +
76
- "DEGREES(ATAN2( " +
77
- "SIN(RADIANS(#{lon_attr} - #{longitude.to_f})) * " +
78
- "COS(RADIANS(#{lat_attr})), (" +
79
- "COS(RADIANS(#{latitude.to_f})) * SIN(RADIANS(#{lat_attr}))" +
76
+ "MOD(CAST(" +
77
+ "(ATAN2( " +
78
+ "SIN( (#{lon_attr} - #{longitude.to_f}) / #{degrees_per_radian} ) * " +
79
+ "COS( (#{lat_attr}) / #{degrees_per_radian} ), (" +
80
+ "COS( (#{latitude.to_f}) / #{degrees_per_radian} ) * SIN( (#{lat_attr}) / #{degrees_per_radian})" +
80
81
  ") - (" +
81
- "SIN(RADIANS(#{latitude.to_f})) * COS(RADIANS(#{lat_attr})) * " +
82
- "COS(RADIANS(#{lon_attr} - #{longitude.to_f}))" +
82
+ "SIN( (#{latitude.to_f}) / #{degrees_per_radian}) * COS((#{lat_attr}) / #{degrees_per_radian}) * " +
83
+ "COS( (#{lon_attr} - #{longitude.to_f}) / #{degrees_per_radian})" +
83
84
  ")" +
84
- ")) + 360 " +
85
- "AS decimal) % 360"
85
+ ") * #{degrees_per_radian}) + 360 " +
86
+ "AS decimal), 360)"
86
87
  end
87
88
  end
88
89
 
@@ -18,14 +18,19 @@ module Geocoder::Store
18
18
 
19
19
  # scope: geocoded objects
20
20
  scope :geocoded, lambda {
21
- where("#{geocoder_options[:latitude]} IS NOT NULL " +
22
- "AND #{geocoder_options[:longitude]} IS NOT NULL")
21
+ where("#{table_name}.#{geocoder_options[:latitude]} IS NOT NULL " +
22
+ "AND #{table_name}.#{geocoder_options[:longitude]} IS NOT NULL")
23
23
  }
24
24
 
25
25
  # scope: not-geocoded objects
26
26
  scope :not_geocoded, lambda {
27
- where("#{geocoder_options[:latitude]} IS NULL " +
28
- "OR #{geocoder_options[:longitude]} IS NULL")
27
+ where("#{table_name}.#{geocoder_options[:latitude]} IS NULL " +
28
+ "OR #{table_name}.#{geocoder_options[:longitude]} IS NULL")
29
+ }
30
+
31
+ # scope: not-reverse geocoded objects
32
+ scope :not_reverse_geocoded, lambda {
33
+ where("#{table_name}.#{geocoder_options[:fetched_address]} IS NULL")
29
34
  }
30
35
 
31
36
  ##
@@ -45,7 +50,7 @@ module Geocoder::Store
45
50
  # If no lat/lon given we don't want any results, but we still
46
51
  # need distance and bearing columns so you can add, for example:
47
52
  # .order("distance")
48
- select(select_clause(nil, "NULL", "NULL")).where(false_condition)
53
+ select(select_clause(nil, null_value, null_value)).where(false_condition)
49
54
  end
50
55
  }
51
56
 
@@ -64,7 +69,7 @@ module Geocoder::Store
64
69
  full_column_name(geocoder_options[:longitude])
65
70
  ))
66
71
  else
67
- select(select_clause(nil, "NULL", "NULL")).where(false_condition)
72
+ select(select_clause(nil, null_value, null_value)).where(false_condition)
68
73
  end
69
74
  }
70
75
  end
@@ -107,31 +112,37 @@ module Geocoder::Store
107
112
  # * +:exclude+ - an object to exclude (used by the +nearbys+ method)
108
113
  # * +:distance_column+ - used to set the column name of the calculated distance.
109
114
  # * +:bearing_column+ - used to set the column name of the calculated bearing.
115
+ # * +:min_radius+ - the value to use as the minimum radius.
116
+ # ignored if database is sqlite.
117
+ # default is 0.0
110
118
  #
111
119
  def near_scope_options(latitude, longitude, radius = 20, options = {})
112
120
  if options[:units]
113
121
  options[:units] = options[:units].to_sym
114
122
  end
123
+ latitude_attribute = options[:latitude] || geocoder_options[:latitude]
124
+ longitude_attribute = options[:longitude] || geocoder_options[:longitude]
115
125
  options[:units] ||= (geocoder_options[:units] || Geocoder.config.units)
116
- select_distance = options.fetch(:select_distance, true)
126
+ select_distance = options.fetch(:select_distance) { true }
117
127
  options[:order] = "" if !select_distance && !options.include?(:order)
118
- select_bearing = options.fetch(:select_bearing, true)
128
+ select_bearing = options.fetch(:select_bearing) { true }
119
129
  bearing = bearing_sql(latitude, longitude, options)
120
130
  distance = distance_sql(latitude, longitude, options)
121
- distance_column = options.fetch(:distance_column, 'distance')
122
- bearing_column = options.fetch(:bearing_column, 'bearing')
131
+ distance_column = options.fetch(:distance_column) { 'distance' }
132
+ bearing_column = options.fetch(:bearing_column) { 'bearing' }
123
133
 
124
134
  b = Geocoder::Calculations.bounding_box([latitude, longitude], radius, options)
125
135
  args = b + [
126
- full_column_name(geocoder_options[:latitude]),
127
- full_column_name(geocoder_options[:longitude])
136
+ full_column_name(latitude_attribute),
137
+ full_column_name(longitude_attribute)
128
138
  ]
129
139
  bounding_box_conditions = Geocoder::Sql.within_bounding_box(*args)
130
140
 
131
141
  if using_sqlite?
132
142
  conditions = bounding_box_conditions
133
143
  else
134
- conditions = [bounding_box_conditions + " AND #{distance} <= ?", radius]
144
+ min_radius = options.fetch(:min_radius, 0).to_f
145
+ conditions = [bounding_box_conditions + " AND (#{distance}) BETWEEN ? AND ?", min_radius, radius]
135
146
  end
136
147
  {
137
148
  :select => select_clause(options[:select],
@@ -153,8 +164,8 @@ module Geocoder::Store
153
164
  Geocoder::Sql.send(
154
165
  method_prefix + "_distance",
155
166
  latitude, longitude,
156
- full_column_name(geocoder_options[:latitude]),
157
- full_column_name(geocoder_options[:longitude]),
167
+ full_column_name(options[:latitude] || geocoder_options[:latitude]),
168
+ full_column_name(options[:longitude]|| geocoder_options[:longitude]),
158
169
  options
159
170
  )
160
171
  end
@@ -172,8 +183,8 @@ module Geocoder::Store
172
183
  Geocoder::Sql.send(
173
184
  method_prefix + "_bearing",
174
185
  latitude, longitude,
175
- full_column_name(geocoder_options[:latitude]),
176
- full_column_name(geocoder_options[:longitude]),
186
+ full_column_name(options[:latitude] || geocoder_options[:latitude]),
187
+ full_column_name(options[:longitude]|| geocoder_options[:longitude]),
177
188
  options
178
189
  )
179
190
  end
@@ -215,7 +226,18 @@ module Geocoder::Store
215
226
  end
216
227
 
217
228
  def using_sqlite?
218
- connection.adapter_name.match /sqlite/i
229
+ connection.adapter_name.match(/sqlite/i)
230
+ end
231
+
232
+ def using_postgres?
233
+ connection.adapter_name.match(/postgres/i)
234
+ end
235
+
236
+ ##
237
+ # Use OID type when running in PosgreSQL
238
+ #
239
+ def null_value
240
+ using_postgres? ? 'NULL::text' : 'NULL'
219
241
  end
220
242
 
221
243
  ##
@@ -234,6 +256,17 @@ module Geocoder::Store
234
256
  end
235
257
  end
236
258
 
259
+ ##
260
+ # Get nearby geocoded objects.
261
+ # Takes the same options hash as the near class method (scope).
262
+ # Returns nil if the object is not geocoded.
263
+ #
264
+ def nearbys(radius = 20, options = {})
265
+ return nil unless geocoded?
266
+ options.merge!(:exclude => self) unless send(self.class.primary_key).nil?
267
+ self.class.near(self, radius, options)
268
+ end
269
+
237
270
  ##
238
271
  # Look up coordinates and assign to +latitude+ and +longitude+ attributes
239
272
  # (or other as specified in +geocoded_by+). Returns coordinates (array).
@@ -55,17 +55,6 @@ module Geocoder
55
55
  point, to_coordinates, options)
56
56
  end
57
57
 
58
- ##
59
- # Get nearby geocoded objects.
60
- # Takes the same options hash as the near class method (scope).
61
- # Returns nil if the object is not geocoded.
62
- #
63
- def nearbys(radius = 20, options = {})
64
- return nil unless geocoded?
65
- options.merge!(:exclude => self) unless send(self.class.primary_key).nil?
66
- self.class.near(self, radius, options)
67
- end
68
-
69
58
  ##
70
59
  # Look up coordinates and assign to +latitude+ and +longitude+ attributes
71
60
  # (or other as specified in +geocoded_by+). Returns coordinates (array).
@@ -101,7 +90,14 @@ module Geocoder
101
90
  return
102
91
  end
103
92
 
104
- results = Geocoder.search(query)
93
+ query_options = [:lookup, :ip_lookup, :language].inject({}) do |hash, key|
94
+ if options.has_key?(key)
95
+ val = options[key]
96
+ hash[key] = val.respond_to?(:call) ? val.call(self) : val
97
+ end
98
+ hash
99
+ end
100
+ results = Geocoder.search(query, query_options)
105
101
 
106
102
  # execute custom block, if specified in configuration
107
103
  block_key = reverse ? :reverse_block : :geocode_block
@@ -11,37 +11,6 @@ module Geocoder::Store
11
11
  scope :not_geocoded, lambda {
12
12
  where(geocoder_options[:coordinates] => nil)
13
13
  }
14
-
15
- scope :near, lambda{ |location, *args|
16
- coords = Geocoder::Calculations.extract_coordinates(location)
17
-
18
- # no results if no lat/lon given
19
- return where(:id => false) unless coords.is_a?(Array)
20
-
21
- radius = args.size > 0 ? args.shift : 20
22
- options = args.size > 0 ? args.shift : {}
23
- options[:units] ||= geocoder_options[:units]
24
-
25
- # Use BSON::OrderedHash if Ruby's hashes are unordered.
26
- # Conditions must be in order required by indexes (see mongo gem).
27
- version = RUBY_VERSION.split('.').map { |i| i.to_i }
28
- empty = version[0] < 2 && version[1] < 9 ? BSON::OrderedHash.new : {}
29
-
30
- conds = empty.clone
31
- field = geocoder_options[:coordinates]
32
- conds[field] = empty.clone
33
- conds[field]["$nearSphere"] = coords.reverse
34
-
35
- if radius
36
- conds[field]["$maxDistance"] = \
37
- Geocoder::Calculations.distance_to_radians(radius, options[:units])
38
- end
39
-
40
- if obj = options[:exclude]
41
- conds[:_id.ne] = obj.id
42
- end
43
- where(conds)
44
- }
45
14
  end
46
15
  end
47
16
 
@@ -1,3 +1,3 @@
1
1
  module Geocoder
2
- VERSION = "1.1.9"
2
+ VERSION = "1.3.7"
3
3
  end
data/lib/geocoder.rb CHANGED
@@ -1,21 +1,23 @@
1
1
  require "geocoder/configuration"
2
+ require "geocoder/logger"
3
+ require "geocoder/kernel_logger"
2
4
  require "geocoder/query"
3
5
  require "geocoder/calculations"
4
6
  require "geocoder/exceptions"
5
7
  require "geocoder/cache"
6
8
  require "geocoder/request"
7
9
  require "geocoder/lookup"
10
+ require "geocoder/ip_address"
8
11
  require "geocoder/models/active_record" if defined?(::ActiveRecord)
9
12
  require "geocoder/models/mongoid" if defined?(::Mongoid)
10
13
  require "geocoder/models/mongo_mapper" if defined?(::MongoMapper)
11
14
 
12
15
  module Geocoder
13
- extend self
14
16
 
15
17
  ##
16
18
  # Search for information about an address or a set of coordinates.
17
19
  #
18
- def search(query, options = {})
20
+ def self.search(query, options = {})
19
21
  query = Geocoder::Query.new(query, options) unless query.is_a?(Geocoder::Query)
20
22
  query.blank? ? [] : query.execute
21
23
  end
@@ -23,7 +25,7 @@ module Geocoder
23
25
  ##
24
26
  # Look up the coordinates of the given street or IP address.
25
27
  #
26
- def coordinates(address, options = {})
28
+ def self.coordinates(address, options = {})
27
29
  if (results = search(address, options)).size > 0
28
30
  results.first.coordinates
29
31
  end
@@ -33,23 +35,14 @@ module Geocoder
33
35
  # Look up the address of the given coordinates ([lat,lon])
34
36
  # or IP address (string).
35
37
  #
36
- def address(query, options = {})
38
+ def self.address(query, options = {})
37
39
  if (results = search(query, options)).size > 0
38
40
  results.first.address
39
41
  end
40
42
  end
41
-
42
- ##
43
- # The working Cache object, or +nil+ if none configured.
44
- #
45
- def cache
46
- warn "WARNING: Calling Geocoder.cache is DEPRECATED. The #cache method now belongs to the Geocoder::Lookup object."
47
- Geocoder::Lookup.get(Geocoder.config.lookup).cache
48
- end
49
43
  end
50
44
 
51
45
  # load Railtie if Rails exists
52
46
  if defined?(Rails)
53
47
  require "geocoder/railtie"
54
- Geocoder::Railtie.insert
55
48
  end
@@ -0,0 +1,109 @@
1
+ require 'csv'
2
+ require 'net/http'
3
+
4
+ module Geocoder
5
+ module MaxmindDatabase
6
+ extend self
7
+
8
+ def download(package, dir = "tmp")
9
+ filepath = File.expand_path(File.join(dir, archive_filename(package)))
10
+ open(filepath, 'wb') do |file|
11
+ uri = URI.parse(archive_url(package))
12
+ Net::HTTP.start(uri.host, uri.port) do |http|
13
+ http.request_get(uri.path) do |resp|
14
+ # TODO: show progress
15
+ resp.read_body do |segment|
16
+ file.write(segment)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ def insert(package, dir = "tmp")
24
+ data_files(package, dir).each do |filepath,table|
25
+ print "Resetting table #{table}..."
26
+ ActiveRecord::Base.connection.execute("DELETE FROM #{table}")
27
+ puts "done"
28
+ insert_into_table(table, filepath)
29
+ end
30
+ end
31
+
32
+ def archive_filename(package)
33
+ p = archive_url_path(package)
34
+ s = !(pos = p.rindex('/')).nil? && pos + 1 || 0
35
+ p[s..-1]
36
+ end
37
+
38
+ private # -------------------------------------------------------------
39
+
40
+ def table_columns(table_name)
41
+ {
42
+ maxmind_geolite_city_blocks: %w[start_ip_num end_ip_num loc_id],
43
+ maxmind_geolite_city_location: %w[loc_id country region city postal_code latitude longitude metro_code area_code],
44
+ maxmind_geolite_country: %w[start_ip end_ip start_ip_num end_ip_num country_code country]
45
+ }[table_name.to_sym]
46
+ end
47
+
48
+ def insert_into_table(table, filepath)
49
+ start_time = Time.now
50
+ print "Loading data for table #{table}"
51
+ rows = []
52
+ columns = table_columns(table)
53
+ CSV.foreach(filepath, encoding: "ISO-8859-1") do |line|
54
+ # Some files have header rows.
55
+ # skip if starts with "Copyright" or "locId" or "startIpNum"
56
+ next if line.first.match(/[A-z]/)
57
+ rows << line.to_a
58
+ if rows.size == 10000
59
+ insert_rows(table, columns, rows)
60
+ rows = []
61
+ print "."
62
+ end
63
+ end
64
+ insert_rows(table, columns, rows) if rows.size > 0
65
+ puts "done (#{Time.now - start_time} seconds)"
66
+ end
67
+
68
+ def insert_rows(table, headers, rows)
69
+ value_strings = rows.map do |row|
70
+ "(" + row.map{ |col| sql_escaped_value(col) }.join(',') + ")"
71
+ end
72
+ q = "INSERT INTO #{table} (#{headers.join(',')}) " +
73
+ "VALUES #{value_strings.join(',')}"
74
+ ActiveRecord::Base.connection.execute(q)
75
+ end
76
+
77
+ def sql_escaped_value(value)
78
+ value.to_i.to_s == value ? value :
79
+ ActiveRecord::Base.connection.quote(value)
80
+ end
81
+
82
+ def data_files(package, dir = "tmp")
83
+ case package
84
+ when :geolite_city_csv
85
+ # use the last two in case multiple versions exist
86
+ files = Dir.glob(File.join(dir, "GeoLiteCity_*/*.csv"))[-2..-1].sort
87
+ Hash[*files.zip(["maxmind_geolite_city_blocks", "maxmind_geolite_city_location"]).flatten]
88
+ when :geolite_country_csv
89
+ {File.join(dir, "GeoIPCountryWhois.csv") => "maxmind_geolite_country"}
90
+ end
91
+ end
92
+
93
+ def archive_url(package)
94
+ base_url + archive_url_path(package)
95
+ end
96
+
97
+ def archive_url_path(package)
98
+ {
99
+ geolite_country_csv: "GeoIPCountryCSV.zip",
100
+ geolite_city_csv: "GeoLiteCity_CSV/GeoLiteCity-latest.zip",
101
+ geolite_asn_csv: "asnum/GeoIPASNum2.zip"
102
+ }[package]
103
+ end
104
+
105
+ def base_url
106
+ "http://geolite.maxmind.com/download/geoip/database/"
107
+ end
108
+ end
109
+ end
@@ -3,12 +3,23 @@ namespace :geocode do
3
3
  task :all => :environment do
4
4
  class_name = ENV['CLASS'] || ENV['class']
5
5
  sleep_timer = ENV['SLEEP'] || ENV['sleep']
6
+ batch = ENV['BATCH'] || ENV['batch']
7
+ reverse = ENV['REVERSE'] || ENV['reverse']
6
8
  raise "Please specify a CLASS (model)" unless class_name
7
9
  klass = class_from_string(class_name)
10
+ batch = batch.to_i unless batch.nil?
11
+ reverse = false unless reverse.to_s.downcase == 'true'
8
12
 
9
- klass.not_geocoded.each do |obj|
10
- obj.geocode; obj.save
11
- sleep(sleep_timer.to_f) unless sleep_timer.nil?
13
+ if reverse
14
+ klass.not_reverse_geocoded.find_each(batch_size: batch) do |obj|
15
+ obj.reverse_geocode; obj.save
16
+ sleep(sleep_timer.to_f) unless sleep_timer.nil?
17
+ end
18
+ else
19
+ klass.not_geocoded.find_each(batch_size: batch) do |obj|
20
+ obj.geocode; obj.save
21
+ sleep(sleep_timer.to_f) unless sleep_timer.nil?
22
+ end
12
23
  end
13
24
  end
14
25
  end
@@ -0,0 +1,73 @@
1
+ require 'maxmind_database'
2
+
3
+ namespace :geocoder do
4
+ namespace :maxmind do
5
+ namespace :geolite do
6
+
7
+ desc "Download and load/refresh MaxMind GeoLite City data"
8
+ task load: [:download, :extract, :insert]
9
+
10
+ desc "Download MaxMind GeoLite City data"
11
+ task :download do
12
+ p = MaxmindTask.check_for_package!
13
+ MaxmindTask.download!(p, dir: ENV['DIR'] || "tmp/")
14
+ end
15
+
16
+ desc "Extract (unzip) MaxMind GeoLite City data"
17
+ task :extract do
18
+ p = MaxmindTask.check_for_package!
19
+ MaxmindTask.extract!(p, dir: ENV['DIR'] || "tmp/")
20
+ end
21
+
22
+ desc "Load/refresh MaxMind GeoLite City data"
23
+ task insert: [:environment] do
24
+ p = MaxmindTask.check_for_package!
25
+ MaxmindTask.insert!(p, dir: ENV['DIR'] || "tmp/")
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ module MaxmindTask
32
+ extend self
33
+
34
+ def check_for_package!
35
+ if %w[city country].include?(p = ENV['PACKAGE'])
36
+ return p
37
+ else
38
+ puts "Please specify PACKAGE=city or PACKAGE=country"
39
+ exit
40
+ end
41
+ end
42
+
43
+ def download!(package, options = {})
44
+ p = "geolite_#{package}_csv".intern
45
+ Geocoder::MaxmindDatabase.download(p, options[:dir])
46
+ end
47
+
48
+ def extract!(package, options = {})
49
+ begin
50
+ require 'zip'
51
+ rescue LoadError
52
+ puts "Please install gem: rubyzip (>= 1.0.0)"
53
+ exit
54
+ end
55
+ require 'fileutils'
56
+ p = "geolite_#{package}_csv".intern
57
+ archive_filename = Geocoder::MaxmindDatabase.archive_filename(p)
58
+ Zip::File.open(File.join(options[:dir], archive_filename)).each do |entry|
59
+ filepath = File.join(options[:dir], entry.name)
60
+ if File.exist? filepath
61
+ warn "File already exists (#{entry.name}), skipping"
62
+ else
63
+ FileUtils.mkdir_p(File.dirname(filepath))
64
+ entry.extract(filepath)
65
+ end
66
+ end
67
+ end
68
+
69
+ def insert!(package, options = {})
70
+ p = "geolite_#{package}_csv".intern
71
+ Geocoder::MaxmindDatabase.insert(p, options[:dir])
72
+ end
73
+ end