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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +157 -0
- data/README.md +467 -70
- data/examples/reverse_geocode_job.rb +40 -0
- data/lib/generators/geocoder/config/templates/initializer.rb +16 -16
- data/lib/generators/geocoder/maxmind/geolite_city_generator.rb +28 -0
- data/lib/generators/geocoder/maxmind/geolite_country_generator.rb +28 -0
- data/lib/generators/geocoder/maxmind/templates/migration/geolite_city.rb +30 -0
- data/lib/generators/geocoder/maxmind/templates/migration/geolite_country.rb +17 -0
- data/lib/geocoder/cache.rb +3 -2
- data/lib/geocoder/calculations.rb +44 -2
- data/lib/geocoder/configuration.rb +17 -10
- data/lib/geocoder/esri_token.rb +38 -0
- data/lib/geocoder/exceptions.rb +19 -0
- data/lib/geocoder/ip_address.rb +13 -0
- data/lib/geocoder/kernel_logger.rb +25 -0
- data/lib/geocoder/logger.rb +47 -0
- data/lib/geocoder/lookup.rb +32 -8
- data/lib/geocoder/lookups/baidu.rb +18 -13
- data/lib/geocoder/lookups/baidu_ip.rb +59 -0
- data/lib/geocoder/lookups/base.rb +81 -19
- data/lib/geocoder/lookups/bing.rb +40 -7
- data/lib/geocoder/lookups/esri.rb +42 -5
- data/lib/geocoder/lookups/freegeoip.rb +9 -1
- data/lib/geocoder/lookups/geocoder_ca.rb +1 -2
- data/lib/geocoder/lookups/geocoder_us.rb +6 -2
- data/lib/geocoder/lookups/geocodio.rb +42 -0
- data/lib/geocoder/lookups/geoip2.rb +45 -0
- data/lib/geocoder/lookups/geoportail_lu.rb +65 -0
- data/lib/geocoder/lookups/google.rb +29 -5
- data/lib/geocoder/lookups/google_places_details.rb +50 -0
- data/lib/geocoder/lookups/google_premier.rb +1 -1
- data/lib/geocoder/lookups/here.rb +62 -0
- data/lib/geocoder/lookups/ipapi_com.rb +86 -0
- data/lib/geocoder/lookups/ipinfo_io.rb +55 -0
- data/lib/geocoder/lookups/latlon.rb +59 -0
- data/lib/geocoder/lookups/mapbox.rb +53 -0
- data/lib/geocoder/lookups/mapquest.rb +6 -6
- data/lib/geocoder/lookups/mapzen.rb +15 -0
- data/lib/geocoder/lookups/maxmind.rb +4 -2
- data/lib/geocoder/lookups/maxmind_geoip2.rb +69 -0
- data/lib/geocoder/lookups/maxmind_local.rb +65 -0
- data/lib/geocoder/lookups/nominatim.rb +9 -1
- data/lib/geocoder/lookups/okf.rb +44 -0
- data/lib/geocoder/lookups/opencagedata.rb +58 -0
- data/lib/geocoder/lookups/pelias.rb +64 -0
- data/lib/geocoder/lookups/pointpin.rb +68 -0
- data/lib/geocoder/lookups/postcode_anywhere_uk.rb +51 -0
- data/lib/geocoder/lookups/smarty_streets.rb +53 -0
- data/lib/geocoder/lookups/telize.rb +55 -0
- data/lib/geocoder/lookups/yandex.rb +8 -4
- data/lib/geocoder/models/active_record.rb +7 -3
- data/lib/geocoder/models/base.rb +1 -4
- data/lib/geocoder/models/mongo_base.rb +6 -4
- data/lib/geocoder/query.rb +9 -5
- data/lib/geocoder/railtie.rb +1 -1
- data/lib/geocoder/request.rb +74 -12
- data/lib/geocoder/results/baidu_ip.rb +62 -0
- data/lib/geocoder/results/bing.rb +4 -0
- data/lib/geocoder/results/esri.rb +30 -6
- data/lib/geocoder/results/freegeoip.rb +2 -2
- data/lib/geocoder/results/geocodio.rb +70 -0
- data/lib/geocoder/results/geoip2.rb +62 -0
- data/lib/geocoder/results/geoportail_lu.rb +69 -0
- data/lib/geocoder/results/google.rb +15 -0
- data/lib/geocoder/results/google_places_details.rb +35 -0
- data/lib/geocoder/results/here.rb +71 -0
- data/lib/geocoder/results/ipapi_com.rb +45 -0
- data/lib/geocoder/results/ipinfo_io.rb +48 -0
- data/lib/geocoder/results/latlon.rb +71 -0
- data/lib/geocoder/results/mapbox.rb +47 -0
- data/lib/geocoder/results/mapquest.rb +5 -8
- data/lib/geocoder/results/mapzen.rb +5 -0
- data/lib/geocoder/results/maxmind_geoip2.rb +9 -0
- data/lib/geocoder/results/maxmind_local.rb +49 -0
- data/lib/geocoder/results/nominatim.rb +6 -1
- data/lib/geocoder/results/okf.rb +106 -0
- data/lib/geocoder/results/opencagedata.rb +90 -0
- data/lib/geocoder/results/ovi.rb +9 -0
- data/lib/geocoder/results/pelias.rb +58 -0
- data/lib/geocoder/results/pointpin.rb +40 -0
- data/lib/geocoder/results/postcode_anywhere_uk.rb +42 -0
- data/lib/geocoder/results/smarty_streets.rb +106 -0
- data/lib/geocoder/results/telize.rb +45 -0
- data/lib/geocoder/results/test.rb +20 -3
- data/lib/geocoder/results/yandex.rb +18 -6
- data/lib/geocoder/sql.rb +16 -15
- data/lib/geocoder/stores/active_record.rb +51 -18
- data/lib/geocoder/stores/base.rb +8 -12
- data/lib/geocoder/stores/mongo_base.rb +0 -31
- data/lib/geocoder/version.rb +1 -1
- data/lib/geocoder.rb +6 -13
- data/lib/maxmind_database.rb +109 -0
- data/lib/tasks/geocoder.rake +14 -3
- data/lib/tasks/maxmind.rake +73 -0
- metadata +59 -85
- data/.gitignore +0 -5
- data/.travis.yml +0 -27
- data/Rakefile +0 -25
- data/gemfiles/Gemfile.mongoid-2.4.x +0 -15
- data/lib/geocoder/lookups/yahoo.rb +0 -86
- data/lib/geocoder/results/yahoo.rb +0 -55
- data/lib/oauth_util.rb +0 -112
- data/test/active_record_test.rb +0 -15
- data/test/cache_test.rb +0 -35
- data/test/calculations_test.rb +0 -211
- data/test/configuration_test.rb +0 -78
- data/test/custom_block_test.rb +0 -32
- data/test/error_handling_test.rb +0 -43
- data/test/fixtures/baidu_invalid_key +0 -1
- data/test/fixtures/baidu_no_results +0 -1
- data/test/fixtures/baidu_reverse +0 -1
- data/test/fixtures/baidu_shanghai_pearl_tower +0 -12
- data/test/fixtures/bing_invalid_key +0 -1
- data/test/fixtures/bing_madison_square_garden +0 -40
- data/test/fixtures/bing_no_results +0 -16
- data/test/fixtures/bing_reverse +0 -42
- data/test/fixtures/esri_madison_square_garden +0 -59
- data/test/fixtures/esri_no_results +0 -8
- data/test/fixtures/esri_reverse +0 -21
- data/test/fixtures/freegeoip_74_200_247_59 +0 -12
- data/test/fixtures/freegeoip_no_results +0 -1
- data/test/fixtures/geocoder_ca_madison_square_garden +0 -1
- data/test/fixtures/geocoder_ca_no_results +0 -1
- data/test/fixtures/geocoder_ca_reverse +0 -34
- data/test/fixtures/geocoder_us_madison_square_garden +0 -1
- data/test/fixtures/geocoder_us_no_results +0 -1
- data/test/fixtures/google_garbage +0 -456
- data/test/fixtures/google_madison_square_garden +0 -57
- data/test/fixtures/google_no_city_data +0 -44
- data/test/fixtures/google_no_locality +0 -51
- data/test/fixtures/google_no_results +0 -4
- data/test/fixtures/google_over_limit +0 -4
- data/test/fixtures/mapquest_error +0 -16
- data/test/fixtures/mapquest_invalid_api_key +0 -16
- data/test/fixtures/mapquest_invalid_request +0 -16
- data/test/fixtures/mapquest_madison_square_garden +0 -52
- data/test/fixtures/mapquest_no_results +0 -16
- data/test/fixtures/maxmind_24_24_24_21 +0 -1
- data/test/fixtures/maxmind_24_24_24_22 +0 -1
- data/test/fixtures/maxmind_24_24_24_23 +0 -1
- data/test/fixtures/maxmind_24_24_24_24 +0 -1
- data/test/fixtures/maxmind_74_200_247_59 +0 -1
- data/test/fixtures/maxmind_invalid_key +0 -1
- data/test/fixtures/maxmind_no_results +0 -1
- data/test/fixtures/nominatim_madison_square_garden +0 -150
- data/test/fixtures/nominatim_no_results +0 -1
- data/test/fixtures/ovi_madison_square_garden +0 -72
- data/test/fixtures/ovi_no_results +0 -8
- data/test/fixtures/yahoo_error +0 -1
- data/test/fixtures/yahoo_invalid_key +0 -2
- data/test/fixtures/yahoo_madison_square_garden +0 -52
- data/test/fixtures/yahoo_no_results +0 -10
- data/test/fixtures/yahoo_over_limit +0 -2
- data/test/fixtures/yandex_invalid_key +0 -1
- data/test/fixtures/yandex_kremlin +0 -48
- data/test/fixtures/yandex_no_city_and_town +0 -112
- data/test/fixtures/yandex_no_results +0 -16
- data/test/geocoder_test.rb +0 -59
- data/test/https_test.rb +0 -16
- data/test/integration/smoke_test.rb +0 -26
- data/test/lookup_test.rb +0 -117
- data/test/method_aliases_test.rb +0 -25
- data/test/mongoid_test.rb +0 -46
- data/test/mongoid_test_helper.rb +0 -43
- data/test/near_test.rb +0 -61
- data/test/oauth_util_test.rb +0 -30
- data/test/proxy_test.rb +0 -36
- data/test/query_test.rb +0 -52
- data/test/request_test.rb +0 -29
- data/test/result_test.rb +0 -42
- data/test/services_test.rb +0 -393
- data/test/test_helper.rb +0 -289
- 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
|
-
"
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
")) + 360 " +
|
|
73
|
-
"AS decimal)
|
|
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
|
-
"
|
|
77
|
-
"SIN(
|
|
78
|
-
"COS(
|
|
79
|
-
"COS(
|
|
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(
|
|
82
|
-
"COS(
|
|
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)
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
122
|
-
bearing_column = options.fetch(:bearing_column
|
|
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(
|
|
127
|
-
full_column_name(
|
|
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
|
-
|
|
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
|
|
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).
|
data/lib/geocoder/stores/base.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
|
data/lib/geocoder/version.rb
CHANGED
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
|
data/lib/tasks/geocoder.rake
CHANGED
|
@@ -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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|