geocoder 1.1.9 → 1.2.0
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.
Potentially problematic release.
This version of geocoder might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +9 -5
- data/CHANGELOG.md +19 -0
- data/README.md +175 -10
- data/Rakefile +1 -1
- data/gemfiles/Gemfile.mongoid-2.4.x +1 -0
- 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 +27 -0
- data/lib/generators/geocoder/maxmind/templates/migration/geolite_country.rb +17 -0
- data/lib/geocoder.rb +4 -12
- data/lib/geocoder/cache.rb +3 -2
- data/lib/geocoder/calculations.rb +39 -0
- data/lib/geocoder/configuration.rb +1 -7
- data/lib/geocoder/ip_address.rb +12 -0
- data/lib/geocoder/lookup.rb +10 -1
- data/lib/geocoder/lookups/baidu.rb +7 -6
- data/lib/geocoder/lookups/baidu_ip.rb +54 -0
- data/lib/geocoder/lookups/base.rb +37 -9
- data/lib/geocoder/lookups/bing.rb +10 -5
- data/lib/geocoder/lookups/cloudmade.rb +35 -0
- data/lib/geocoder/lookups/freegeoip.rb +5 -1
- data/lib/geocoder/lookups/geocodio.rb +42 -0
- data/lib/geocoder/lookups/google_premier.rb +1 -1
- data/lib/geocoder/lookups/here.rb +62 -0
- data/lib/geocoder/lookups/mapquest.rb +2 -1
- data/lib/geocoder/lookups/maxmind_local.rb +58 -0
- data/lib/geocoder/lookups/nominatim.rb +8 -0
- data/lib/geocoder/lookups/smarty_streets.rb +45 -0
- data/lib/geocoder/lookups/yahoo.rb +1 -1
- data/lib/geocoder/models/active_record.rb +5 -3
- data/lib/geocoder/models/base.rb +1 -4
- data/lib/geocoder/models/mongo_base.rb +4 -2
- data/lib/geocoder/query.rb +4 -4
- data/lib/geocoder/railtie.rb +1 -1
- data/lib/geocoder/request.rb +10 -8
- data/lib/geocoder/results/baidu_ip.rb +62 -0
- data/lib/geocoder/results/cloudmade.rb +39 -0
- data/lib/geocoder/results/geocodio.rb +66 -0
- data/lib/geocoder/results/here.rb +62 -0
- data/lib/geocoder/results/maxmind_local.rb +49 -0
- data/lib/geocoder/results/smarty_streets.rb +106 -0
- data/lib/geocoder/results/test.rb +20 -3
- data/lib/geocoder/results/yandex.rb +7 -3
- data/lib/geocoder/sql.rb +16 -15
- data/lib/geocoder/stores/active_record.rb +6 -2
- data/lib/geocoder/stores/base.rb +8 -1
- data/lib/geocoder/version.rb +1 -1
- data/lib/maxmind_database.rb +109 -0
- data/lib/oauth_util.rb +1 -1
- data/lib/tasks/geocoder.rake +3 -1
- data/lib/tasks/maxmind.rake +73 -0
- data/test/fixtures/baidu_ip_202_198_16_3 +19 -0
- data/test/fixtures/baidu_ip_invalid_key +1 -0
- data/test/fixtures/baidu_ip_no_results +1 -0
- data/test/fixtures/cloudmade_invalid_key +1 -0
- data/test/fixtures/cloudmade_madison_square_garden +1 -0
- data/test/fixtures/cloudmade_no_results +1 -0
- data/test/fixtures/geocodio_1101_pennsylvania_ave +1 -0
- data/test/fixtures/geocodio_bad_api_key +3 -0
- data/test/fixtures/geocodio_invalid +4 -0
- data/test/fixtures/geocodio_no_results +1 -0
- data/test/fixtures/geocodio_over_query_limit +4 -0
- data/test/fixtures/here_madison_square_garden +72 -0
- data/test/fixtures/here_no_results +8 -0
- data/test/fixtures/nominatim_over_limit +1 -0
- data/test/fixtures/smarty_streets_11211 +1 -0
- data/test/fixtures/smarty_streets_madison_square_garden +47 -0
- data/test/fixtures/smarty_streets_no_results +1 -0
- data/test/fixtures/yandex_canada_rue_dupuis_14 +446 -0
- data/test/fixtures/yandex_new_york +1 -0
- data/test/integration/http_client_test.rb +25 -0
- data/test/mongoid_test_helper.rb +2 -2
- data/test/test_helper.rb +98 -30
- data/test/{active_record_test.rb → unit/active_record_test.rb} +4 -3
- data/test/{cache_test.rb → unit/cache_test.rb} +3 -1
- data/test/{calculations_test.rb → unit/calculations_test.rb} +22 -13
- data/test/{configuration_test.rb → unit/configuration_test.rb} +4 -27
- data/test/{error_handling_test.rb → unit/error_handling_test.rb} +10 -9
- data/test/{geocoder_test.rb → unit/geocoder_test.rb} +26 -7
- data/test/{https_test.rb → unit/https_test.rb} +4 -3
- data/test/unit/ip_address_test.rb +24 -0
- data/test/{lookup_test.rb → unit/lookup_test.rb} +33 -20
- data/test/unit/lookups/bing_test.rb +68 -0
- data/test/unit/lookups/dstk_test.rb +26 -0
- data/test/unit/lookups/esri_test.rb +48 -0
- data/test/unit/lookups/freegeoip_test.rb +27 -0
- data/test/unit/lookups/geocoder_ca_test.rb +17 -0
- data/test/unit/lookups/geocodio_test.rb +55 -0
- data/test/unit/lookups/google_premier_test.rb +22 -0
- data/test/unit/lookups/google_test.rb +84 -0
- data/test/unit/lookups/mapquest_test.rb +60 -0
- data/test/unit/lookups/maxmind_local_test.rb +28 -0
- data/test/unit/lookups/maxmind_test.rb +63 -0
- data/test/unit/lookups/nominatim_test.rb +31 -0
- data/test/unit/lookups/smarty_streets_test.rb +71 -0
- data/test/unit/lookups/yahoo_test.rb +35 -0
- data/test/{method_aliases_test.rb → unit/method_aliases_test.rb} +5 -4
- data/test/unit/model_test.rb +38 -0
- data/test/{mongoid_test.rb → unit/mongoid_test.rb} +10 -9
- data/test/unit/near_test.rb +87 -0
- data/test/{oauth_util_test.rb → unit/oauth_util_test.rb} +3 -2
- data/test/{proxy_test.rb → unit/proxy_test.rb} +2 -1
- data/test/{query_test.rb → unit/query_test.rb} +7 -8
- data/test/unit/rake_task_test.rb +21 -0
- data/test/{request_test.rb → unit/request_test.rb} +8 -2
- data/test/{result_test.rb → unit/result_test.rb} +29 -1
- data/test/{test_mode_test.rb → unit/test_mode_test.rb} +12 -1
- metadata +80 -27
- data/test/custom_block_test.rb +0 -32
- data/test/integration/smoke_test.rb +0 -26
- data/test/near_test.rb +0 -61
- data/test/services_test.rb +0 -393
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'geocoder/results/base'
|
2
|
+
|
3
|
+
module Geocoder::Result
|
4
|
+
class Geocodio < Base
|
5
|
+
def number
|
6
|
+
address_components["number"]
|
7
|
+
end
|
8
|
+
|
9
|
+
def street
|
10
|
+
address_components["street"]
|
11
|
+
end
|
12
|
+
|
13
|
+
def suffix
|
14
|
+
address_components["suffix"]
|
15
|
+
end
|
16
|
+
|
17
|
+
def state
|
18
|
+
address_components["state"]
|
19
|
+
end
|
20
|
+
alias_method :state_code, :state
|
21
|
+
|
22
|
+
def zip
|
23
|
+
address_components["zip"]
|
24
|
+
end
|
25
|
+
alias_method :postal_code, :zip
|
26
|
+
|
27
|
+
def country
|
28
|
+
"United States" # Geocodio only supports the US
|
29
|
+
end
|
30
|
+
|
31
|
+
def country_code
|
32
|
+
"US" # Geocodio only supports the US
|
33
|
+
end
|
34
|
+
|
35
|
+
def city
|
36
|
+
address_components["city"]
|
37
|
+
end
|
38
|
+
|
39
|
+
def postdirectional
|
40
|
+
address_components["postdirectional"]
|
41
|
+
end
|
42
|
+
|
43
|
+
def location
|
44
|
+
@data['location']
|
45
|
+
end
|
46
|
+
|
47
|
+
def coordinates
|
48
|
+
['lat', 'lng'].map{ |i| location[i] } if location
|
49
|
+
end
|
50
|
+
|
51
|
+
def accuracy
|
52
|
+
@data['accuracy'].to_f if @data.key?('accuracy')
|
53
|
+
end
|
54
|
+
|
55
|
+
def formatted_address(format = :full)
|
56
|
+
@data['formatted_address']
|
57
|
+
end
|
58
|
+
alias_method :address, :formatted_address
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def address_components
|
63
|
+
@data['address_components'] || {}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'geocoder/results/base'
|
2
|
+
|
3
|
+
module Geocoder::Result
|
4
|
+
class Here < Base
|
5
|
+
|
6
|
+
##
|
7
|
+
# A string in the given format.
|
8
|
+
#
|
9
|
+
def address(format = :full)
|
10
|
+
address_data['Label']
|
11
|
+
end
|
12
|
+
|
13
|
+
##
|
14
|
+
# A two-element array: [lat, lon].
|
15
|
+
#
|
16
|
+
def coordinates
|
17
|
+
fail unless d = @data['Location']['DisplayPosition']
|
18
|
+
[d['Latitude'].to_f, d['Longitude'].to_f]
|
19
|
+
end
|
20
|
+
|
21
|
+
def state
|
22
|
+
address_data['County']
|
23
|
+
end
|
24
|
+
|
25
|
+
def province
|
26
|
+
address_data['County']
|
27
|
+
end
|
28
|
+
|
29
|
+
def postal_code
|
30
|
+
address_data['PostalCode']
|
31
|
+
end
|
32
|
+
|
33
|
+
def city
|
34
|
+
address_data['City']
|
35
|
+
end
|
36
|
+
|
37
|
+
def state_code
|
38
|
+
address_data['State']
|
39
|
+
end
|
40
|
+
|
41
|
+
def province_code
|
42
|
+
address_data['State']
|
43
|
+
end
|
44
|
+
|
45
|
+
def country
|
46
|
+
fail unless d = address_data['AdditionalData']
|
47
|
+
if v = d.find{|ad| ad['key']=='CountryName'}
|
48
|
+
return v['value']
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def country_code
|
53
|
+
address_data['Country']
|
54
|
+
end
|
55
|
+
|
56
|
+
private # ----------------------------------------------------------------
|
57
|
+
|
58
|
+
def address_data
|
59
|
+
@data['Location']['Address'] || fail
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'geocoder/results/base'
|
2
|
+
|
3
|
+
module Geocoder::Result
|
4
|
+
class MaxmindLocal < Base
|
5
|
+
|
6
|
+
def address(format = :full)
|
7
|
+
s = state.to_s == "" ? "" : ", #{state}"
|
8
|
+
"#{city}#{s} #{postal_code}, #{country}".sub(/^[ ,]*/, "")
|
9
|
+
end
|
10
|
+
|
11
|
+
def coordinates
|
12
|
+
[@data[:latitude], @data[:longitude]]
|
13
|
+
end
|
14
|
+
|
15
|
+
def city
|
16
|
+
@data[:city_name]
|
17
|
+
end
|
18
|
+
|
19
|
+
def state
|
20
|
+
@data[:region_name]
|
21
|
+
end
|
22
|
+
|
23
|
+
def state_code
|
24
|
+
"" # Not available in Maxmind's database
|
25
|
+
end
|
26
|
+
|
27
|
+
def country
|
28
|
+
@data[:country_name]
|
29
|
+
end
|
30
|
+
|
31
|
+
def country_code
|
32
|
+
@data[:country_code2]
|
33
|
+
end
|
34
|
+
|
35
|
+
def postal_code
|
36
|
+
@data[:postal_code]
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.response_attributes
|
40
|
+
%w[ip]
|
41
|
+
end
|
42
|
+
|
43
|
+
response_attributes.each do |a|
|
44
|
+
define_method a do
|
45
|
+
@data[a]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'geocoder/lookups/base'
|
2
|
+
|
3
|
+
module Geocoder::Result
|
4
|
+
class SmartyStreets < Base
|
5
|
+
def coordinates
|
6
|
+
%w(latitude longitude).map do |i|
|
7
|
+
zipcode_endpoint? ? zipcodes.first[i] : metadata[i]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def address
|
12
|
+
[
|
13
|
+
delivery_line_1,
|
14
|
+
delivery_line_2,
|
15
|
+
last_line
|
16
|
+
].select{ |i| i.to_s != "" }.join(" ")
|
17
|
+
end
|
18
|
+
|
19
|
+
def state
|
20
|
+
zipcode_endpoint? ?
|
21
|
+
city_states.first['state'] :
|
22
|
+
components['state_abbreviation']
|
23
|
+
end
|
24
|
+
|
25
|
+
def state_code
|
26
|
+
zipcode_endpoint? ?
|
27
|
+
city_states.first['state_abbreviation'] :
|
28
|
+
components['state_abbreviation']
|
29
|
+
end
|
30
|
+
|
31
|
+
def country
|
32
|
+
# SmartyStreets returns results for USA only
|
33
|
+
"United States"
|
34
|
+
end
|
35
|
+
|
36
|
+
def country_code
|
37
|
+
# SmartyStreets returns results for USA only
|
38
|
+
"US"
|
39
|
+
end
|
40
|
+
|
41
|
+
## Extra methods not in base.rb ------------------------
|
42
|
+
|
43
|
+
def street
|
44
|
+
components['street_name']
|
45
|
+
end
|
46
|
+
|
47
|
+
def city
|
48
|
+
zipcode_endpoint? ?
|
49
|
+
city_states.first['city'] :
|
50
|
+
components['city_name']
|
51
|
+
end
|
52
|
+
|
53
|
+
def zipcode
|
54
|
+
zipcode_endpoint? ?
|
55
|
+
zipcodes.first['zipcode'] :
|
56
|
+
components['zipcode']
|
57
|
+
end
|
58
|
+
alias_method :postal_code, :zipcode
|
59
|
+
|
60
|
+
def zip4
|
61
|
+
components['plus4_code']
|
62
|
+
end
|
63
|
+
alias_method :postal_code_extended, :zip4
|
64
|
+
|
65
|
+
def fips
|
66
|
+
zipcode_endpoint? ?
|
67
|
+
zipcodes.first['county_fips'] :
|
68
|
+
metadata['county_fips']
|
69
|
+
end
|
70
|
+
|
71
|
+
def zipcode_endpoint?
|
72
|
+
zipcodes.any?
|
73
|
+
end
|
74
|
+
|
75
|
+
[
|
76
|
+
:delivery_line_1,
|
77
|
+
:delivery_line_2,
|
78
|
+
:last_line,
|
79
|
+
:delivery_point_barcode,
|
80
|
+
:addressee
|
81
|
+
].each do |m|
|
82
|
+
define_method(m) do
|
83
|
+
@data[m.to_s] || ''
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
[
|
88
|
+
:components,
|
89
|
+
:metadata,
|
90
|
+
:analysis
|
91
|
+
].each do |m|
|
92
|
+
define_method(m) do
|
93
|
+
@data[m.to_s] || {}
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
[
|
98
|
+
:city_states,
|
99
|
+
:zipcodes
|
100
|
+
].each do |m|
|
101
|
+
define_method(m) do
|
102
|
+
@data[m.to_s] || []
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -4,13 +4,30 @@ module Geocoder
|
|
4
4
|
module Result
|
5
5
|
class Test < Base
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
def self.add_result_attribute(attr)
|
8
|
+
begin
|
9
|
+
remove_method(attr) if method_defined?(attr)
|
10
|
+
rescue NameError # method defined on superclass
|
11
|
+
end
|
12
|
+
|
10
13
|
define_method(attr) do
|
11
14
|
@data[attr.to_s] || @data[attr.to_sym]
|
12
15
|
end
|
13
16
|
end
|
17
|
+
|
18
|
+
%w[latitude longitude neighborhood city state state_code sub_state
|
19
|
+
sub_state_code province province_code postal_code country
|
20
|
+
country_code address street_address street_number route geometry].each do |attr|
|
21
|
+
add_result_attribute(attr)
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(data)
|
25
|
+
data.keys.each do |attr|
|
26
|
+
Test.add_result_attribute(attr)
|
27
|
+
end
|
28
|
+
|
29
|
+
super
|
30
|
+
end
|
14
31
|
end
|
15
32
|
end
|
16
33
|
end
|
@@ -14,7 +14,7 @@ module Geocoder::Result
|
|
14
14
|
def city
|
15
15
|
if state.empty? and address_details.has_key? 'Locality'
|
16
16
|
address_details['Locality']['LocalityName']
|
17
|
-
elsif sub_state.empty?
|
17
|
+
elsif sub_state.empty? and address_details['AdministrativeArea'].has_key? 'Locality'
|
18
18
|
address_details['AdministrativeArea']['Locality']['LocalityName']
|
19
19
|
elsif not sub_state_city.empty?
|
20
20
|
sub_state_city
|
@@ -59,6 +59,10 @@ module Geocoder::Result
|
|
59
59
|
address_details['Locality']['Premise']['PremiseName']
|
60
60
|
end
|
61
61
|
|
62
|
+
def kind
|
63
|
+
@data['GeoObject']['metaDataProperty']['GeocoderMetaData']['kind']
|
64
|
+
end
|
65
|
+
|
62
66
|
def precision
|
63
67
|
@data['GeoObject']['metaDataProperty']['GeocoderMetaData']['precision']
|
64
68
|
end
|
@@ -70,8 +74,8 @@ module Geocoder::Result
|
|
70
74
|
end
|
71
75
|
|
72
76
|
def sub_state_city
|
73
|
-
if sub_state
|
74
|
-
|
77
|
+
if !sub_state.empty? and address_details['AdministrativeArea']['SubAdministrativeArea'].has_key? 'Locality'
|
78
|
+
address_details['AdministrativeArea']['SubAdministrativeArea']['Locality']['LocalityName'] || ""
|
75
79
|
else
|
76
80
|
""
|
77
81
|
end
|
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
|
|
@@ -107,6 +107,9 @@ module Geocoder::Store
|
|
107
107
|
# * +:exclude+ - an object to exclude (used by the +nearbys+ method)
|
108
108
|
# * +:distance_column+ - used to set the column name of the calculated distance.
|
109
109
|
# * +:bearing_column+ - used to set the column name of the calculated bearing.
|
110
|
+
# * +:min_radius+ - the value to use as the minimum radius.
|
111
|
+
# ignored if database is sqlite.
|
112
|
+
# default is 0.0
|
110
113
|
#
|
111
114
|
def near_scope_options(latitude, longitude, radius = 20, options = {})
|
112
115
|
if options[:units]
|
@@ -131,7 +134,8 @@ module Geocoder::Store
|
|
131
134
|
if using_sqlite?
|
132
135
|
conditions = bounding_box_conditions
|
133
136
|
else
|
134
|
-
|
137
|
+
min_radius = options.fetch(:min_radius, 0).to_f
|
138
|
+
conditions = [bounding_box_conditions + " AND (#{distance}) BETWEEN ? AND ?", min_radius, radius]
|
135
139
|
end
|
136
140
|
{
|
137
141
|
:select => select_clause(options[:select],
|
@@ -215,7 +219,7 @@ module Geocoder::Store
|
|
215
219
|
end
|
216
220
|
|
217
221
|
def using_sqlite?
|
218
|
-
connection.adapter_name.match
|
222
|
+
connection.adapter_name.match(/sqlite/i)
|
219
223
|
end
|
220
224
|
|
221
225
|
##
|
data/lib/geocoder/stores/base.rb
CHANGED
@@ -101,7 +101,14 @@ module Geocoder
|
|
101
101
|
return
|
102
102
|
end
|
103
103
|
|
104
|
-
|
104
|
+
query_options = [:lookup, :ip_lookup].inject({}) do |hash, key|
|
105
|
+
if options.has_key?(key)
|
106
|
+
val = options[key]
|
107
|
+
hash[key] = val.respond_to?(:call) ? val.call(self) : val
|
108
|
+
end
|
109
|
+
hash
|
110
|
+
end
|
111
|
+
results = Geocoder.search(query, query_options)
|
105
112
|
|
106
113
|
# execute custom block, if specified in configuration
|
107
114
|
block_key = reverse ? :reverse_block : :geocode_block
|
data/lib/geocoder/version.rb
CHANGED
@@ -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).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]
|
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
|