geocoder-kb 1.2.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.travis.yml +31 -0
- data/CHANGELOG.md +384 -0
- data/LICENSE +20 -0
- data/README.md +1085 -0
- data/Rakefile +25 -0
- data/bin/geocode +5 -0
- data/examples/autoexpire_cache_dalli.rb +62 -0
- data/examples/autoexpire_cache_redis.rb +28 -0
- data/examples/cache_bypass.rb +48 -0
- data/examples/sidekiq_worker.rb +16 -0
- data/gemfiles/Gemfile.mongoid-2.4.x +16 -0
- data/lib/generators/geocoder/config/config_generator.rb +14 -0
- data/lib/generators/geocoder/config/templates/initializer.rb +21 -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 +30 -0
- data/lib/generators/geocoder/maxmind/templates/migration/geolite_country.rb +17 -0
- data/lib/geocoder.rb +47 -0
- data/lib/geocoder/cache.rb +90 -0
- data/lib/geocoder/calculations.rb +428 -0
- data/lib/geocoder/cli.rb +121 -0
- data/lib/geocoder/configuration.rb +124 -0
- data/lib/geocoder/configuration_hash.rb +11 -0
- data/lib/geocoder/exceptions.rb +21 -0
- data/lib/geocoder/ip_address.rb +21 -0
- data/lib/geocoder/lookup.rb +102 -0
- data/lib/geocoder/lookups/amap.rb +55 -0
- data/lib/geocoder/lookups/baidu.rb +55 -0
- data/lib/geocoder/lookups/baidu_ip.rb +54 -0
- data/lib/geocoder/lookups/base.rb +302 -0
- data/lib/geocoder/lookups/bing.rb +59 -0
- data/lib/geocoder/lookups/dstk.rb +20 -0
- data/lib/geocoder/lookups/esri.rb +48 -0
- data/lib/geocoder/lookups/freegeoip.rb +47 -0
- data/lib/geocoder/lookups/geocoder_ca.rb +54 -0
- data/lib/geocoder/lookups/geocoder_us.rb +39 -0
- data/lib/geocoder/lookups/geocodio.rb +42 -0
- data/lib/geocoder/lookups/geoip2.rb +40 -0
- data/lib/geocoder/lookups/google.rb +67 -0
- data/lib/geocoder/lookups/google_places_details.rb +50 -0
- data/lib/geocoder/lookups/google_premier.rb +47 -0
- data/lib/geocoder/lookups/here.rb +62 -0
- data/lib/geocoder/lookups/mapquest.rb +60 -0
- data/lib/geocoder/lookups/maxmind.rb +90 -0
- data/lib/geocoder/lookups/maxmind_local.rb +58 -0
- data/lib/geocoder/lookups/nominatim.rb +52 -0
- data/lib/geocoder/lookups/okf.rb +43 -0
- data/lib/geocoder/lookups/opencagedata.rb +58 -0
- data/lib/geocoder/lookups/ovi.rb +62 -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 +45 -0
- data/lib/geocoder/lookups/telize.rb +40 -0
- data/lib/geocoder/lookups/test.rb +44 -0
- data/lib/geocoder/lookups/yahoo.rb +88 -0
- data/lib/geocoder/lookups/yandex.rb +54 -0
- data/lib/geocoder/models/active_record.rb +50 -0
- data/lib/geocoder/models/base.rb +39 -0
- data/lib/geocoder/models/mongo_base.rb +64 -0
- data/lib/geocoder/models/mongo_mapper.rb +26 -0
- data/lib/geocoder/models/mongoid.rb +32 -0
- data/lib/geocoder/query.rb +111 -0
- data/lib/geocoder/railtie.rb +26 -0
- data/lib/geocoder/request.rb +25 -0
- data/lib/geocoder/results/amap.rb +85 -0
- data/lib/geocoder/results/baidu.rb +79 -0
- data/lib/geocoder/results/baidu_ip.rb +62 -0
- data/lib/geocoder/results/base.rb +67 -0
- data/lib/geocoder/results/bing.rb +48 -0
- data/lib/geocoder/results/dstk.rb +6 -0
- data/lib/geocoder/results/esri.rb +51 -0
- data/lib/geocoder/results/freegeoip.rb +45 -0
- data/lib/geocoder/results/geocoder_ca.rb +60 -0
- data/lib/geocoder/results/geocoder_us.rb +39 -0
- data/lib/geocoder/results/geocodio.rb +66 -0
- data/lib/geocoder/results/geoip2.rb +64 -0
- data/lib/geocoder/results/google.rb +124 -0
- data/lib/geocoder/results/google_places_details.rb +35 -0
- data/lib/geocoder/results/google_premier.rb +6 -0
- data/lib/geocoder/results/here.rb +62 -0
- data/lib/geocoder/results/mapquest.rb +51 -0
- data/lib/geocoder/results/maxmind.rb +135 -0
- data/lib/geocoder/results/maxmind_local.rb +49 -0
- data/lib/geocoder/results/nominatim.rb +94 -0
- data/lib/geocoder/results/okf.rb +106 -0
- data/lib/geocoder/results/opencagedata.rb +82 -0
- data/lib/geocoder/results/ovi.rb +62 -0
- data/lib/geocoder/results/pointpin.rb +44 -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 +33 -0
- data/lib/geocoder/results/yahoo.rb +55 -0
- data/lib/geocoder/results/yandex.rb +84 -0
- data/lib/geocoder/sql.rb +107 -0
- data/lib/geocoder/stores/active_record.rb +289 -0
- data/lib/geocoder/stores/base.rb +127 -0
- data/lib/geocoder/stores/mongo_base.rb +89 -0
- data/lib/geocoder/stores/mongo_mapper.rb +13 -0
- data/lib/geocoder/stores/mongoid.rb +13 -0
- data/lib/geocoder/version.rb +3 -0
- data/lib/hash_recursive_merge.rb +74 -0
- data/lib/maxmind_database.rb +109 -0
- data/lib/oauth_util.rb +112 -0
- data/lib/tasks/geocoder.rake +29 -0
- data/lib/tasks/maxmind.rake +73 -0
- data/test/fixtures/baidu_invalid_key +1 -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/baidu_no_results +1 -0
- data/test/fixtures/baidu_reverse +1 -0
- data/test/fixtures/baidu_shanghai_pearl_tower +12 -0
- data/test/fixtures/bing_invalid_key +1 -0
- data/test/fixtures/bing_madison_square_garden +40 -0
- data/test/fixtures/bing_no_results +16 -0
- data/test/fixtures/bing_reverse +42 -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/esri_madison_square_garden +59 -0
- data/test/fixtures/esri_no_results +8 -0
- data/test/fixtures/esri_reverse +21 -0
- data/test/fixtures/freegeoip_74_200_247_59 +12 -0
- data/test/fixtures/freegeoip_no_results +1 -0
- data/test/fixtures/geocoder_ca_madison_square_garden +1 -0
- data/test/fixtures/geocoder_ca_no_results +1 -0
- data/test/fixtures/geocoder_ca_reverse +34 -0
- data/test/fixtures/geocoder_us_madison_square_garden +1 -0
- data/test/fixtures/geocoder_us_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/google_garbage +456 -0
- data/test/fixtures/google_madison_square_garden +57 -0
- data/test/fixtures/google_no_city_data +44 -0
- data/test/fixtures/google_no_locality +51 -0
- data/test/fixtures/google_no_results +4 -0
- data/test/fixtures/google_over_limit +4 -0
- data/test/fixtures/google_places_details_invalid_request +4 -0
- data/test/fixtures/google_places_details_madison_square_garden +120 -0
- data/test/fixtures/google_places_details_no_results +4 -0
- data/test/fixtures/google_places_details_no_reviews +60 -0
- data/test/fixtures/google_places_details_no_types +66 -0
- data/test/fixtures/here_madison_square_garden +72 -0
- data/test/fixtures/here_no_results +8 -0
- data/test/fixtures/mapquest_error +16 -0
- data/test/fixtures/mapquest_invalid_api_key +16 -0
- data/test/fixtures/mapquest_invalid_request +16 -0
- data/test/fixtures/mapquest_madison_square_garden +52 -0
- data/test/fixtures/mapquest_no_results +16 -0
- data/test/fixtures/maxmind_24_24_24_21 +1 -0
- data/test/fixtures/maxmind_24_24_24_22 +1 -0
- data/test/fixtures/maxmind_24_24_24_23 +1 -0
- data/test/fixtures/maxmind_24_24_24_24 +1 -0
- data/test/fixtures/maxmind_74_200_247_59 +1 -0
- data/test/fixtures/maxmind_invalid_key +1 -0
- data/test/fixtures/maxmind_no_results +1 -0
- data/test/fixtures/nominatim_madison_square_garden +150 -0
- data/test/fixtures/nominatim_no_results +1 -0
- data/test/fixtures/nominatim_over_limit +1 -0
- data/test/fixtures/okf_kirstinmaki +67 -0
- data/test/fixtures/okf_no_results +4 -0
- data/test/fixtures/opencagedata_invalid_api_key +25 -0
- data/test/fixtures/opencagedata_invalid_request +26 -0
- data/test/fixtures/opencagedata_madison_square_garden +73 -0
- data/test/fixtures/opencagedata_no_results +29 -0
- data/test/fixtures/opencagedata_over_limit +31 -0
- data/test/fixtures/ovi_madison_square_garden +72 -0
- data/test/fixtures/ovi_no_results +8 -0
- data/test/fixtures/pointpin_10_10_10_10 +1 -0
- data/test/fixtures/pointpin_555_555_555_555 +1 -0
- data/test/fixtures/pointpin_80_111_555_555 +1 -0
- data/test/fixtures/pointpin_no_results +1 -0
- data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_WR26NJ +1 -0
- data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_generic_error +1 -0
- data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_hampshire +1 -0
- data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_key_limit_exceeded +1 -0
- data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_no_results +1 -0
- data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_romsey +1 -0
- data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_unknown_key +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/telize_10_10_10_10 +1 -0
- data/test/fixtures/telize_555_555_555_555 +4 -0
- data/test/fixtures/telize_74_200_247_59 +1 -0
- data/test/fixtures/telize_no_results +1 -0
- data/test/fixtures/yahoo_error +1 -0
- data/test/fixtures/yahoo_invalid_key +2 -0
- data/test/fixtures/yahoo_madison_square_garden +52 -0
- data/test/fixtures/yahoo_no_results +10 -0
- data/test/fixtures/yahoo_over_limit +2 -0
- data/test/fixtures/yandex_canada_rue_dupuis_14 +446 -0
- data/test/fixtures/yandex_invalid_key +1 -0
- data/test/fixtures/yandex_kremlin +48 -0
- data/test/fixtures/yandex_new_york +1 -0
- data/test/fixtures/yandex_no_city_and_town +112 -0
- data/test/fixtures/yandex_no_results +16 -0
- data/test/integration/http_client_test.rb +31 -0
- data/test/mongoid_test_helper.rb +43 -0
- data/test/test_helper.rb +416 -0
- data/test/unit/active_record_test.rb +16 -0
- data/test/unit/cache_test.rb +37 -0
- data/test/unit/calculations_test.rb +220 -0
- data/test/unit/configuration_test.rb +55 -0
- data/test/unit/error_handling_test.rb +56 -0
- data/test/unit/geocoder_test.rb +78 -0
- data/test/unit/https_test.rb +17 -0
- data/test/unit/ip_address_test.rb +27 -0
- data/test/unit/lookup_test.rb +153 -0
- 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/geoip2_test.rb +27 -0
- data/test/unit/lookups/google_places_details_test.rb +122 -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/okf_test.rb +38 -0
- data/test/unit/lookups/opencagedata_test.rb +64 -0
- data/test/unit/lookups/pointpin_test.rb +30 -0
- data/test/unit/lookups/postcode_anywhere_uk_test.rb +70 -0
- data/test/unit/lookups/smarty_streets_test.rb +71 -0
- data/test/unit/lookups/telize_test.rb +36 -0
- data/test/unit/lookups/yahoo_test.rb +35 -0
- data/test/unit/method_aliases_test.rb +26 -0
- data/test/unit/model_test.rb +38 -0
- data/test/unit/mongoid_test.rb +47 -0
- data/test/unit/near_test.rb +87 -0
- data/test/unit/oauth_util_test.rb +31 -0
- data/test/unit/proxy_test.rb +37 -0
- data/test/unit/query_test.rb +52 -0
- data/test/unit/rake_task_test.rb +21 -0
- data/test/unit/request_test.rb +35 -0
- data/test/unit/result_test.rb +72 -0
- data/test/unit/test_mode_test.rb +70 -0
- metadata +294 -0
@@ -0,0 +1,127 @@
|
|
1
|
+
module Geocoder
|
2
|
+
module Store
|
3
|
+
module Base
|
4
|
+
|
5
|
+
##
|
6
|
+
# Is this object geocoded? (Does it have latitude and longitude?)
|
7
|
+
#
|
8
|
+
def geocoded?
|
9
|
+
to_coordinates.compact.size > 0
|
10
|
+
end
|
11
|
+
|
12
|
+
##
|
13
|
+
# Coordinates [lat,lon] of the object.
|
14
|
+
#
|
15
|
+
def to_coordinates
|
16
|
+
[:latitude, :longitude].map{ |i| send self.class.geocoder_options[i] }
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# Calculate the distance from the object to an arbitrary point.
|
21
|
+
# See Geocoder::Calculations.distance_between for ways of specifying
|
22
|
+
# the point. Also takes a symbol specifying the units
|
23
|
+
# (:mi or :km; can be specified in Geocoder configuration).
|
24
|
+
#
|
25
|
+
def distance_to(point, units = nil)
|
26
|
+
units ||= self.class.geocoder_options[:units]
|
27
|
+
return nil unless geocoded?
|
28
|
+
Geocoder::Calculations.distance_between(
|
29
|
+
to_coordinates, point, :units => units)
|
30
|
+
end
|
31
|
+
|
32
|
+
alias_method :distance_from, :distance_to
|
33
|
+
|
34
|
+
##
|
35
|
+
# Calculate the bearing from the object to another point.
|
36
|
+
# See Geocoder::Calculations.distance_between for
|
37
|
+
# ways of specifying the point.
|
38
|
+
#
|
39
|
+
def bearing_to(point, options = {})
|
40
|
+
options[:method] ||= self.class.geocoder_options[:method]
|
41
|
+
return nil unless geocoded?
|
42
|
+
Geocoder::Calculations.bearing_between(
|
43
|
+
to_coordinates, point, options)
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Calculate the bearing from another point to the object.
|
48
|
+
# See Geocoder::Calculations.distance_between for
|
49
|
+
# ways of specifying the point.
|
50
|
+
#
|
51
|
+
def bearing_from(point, options = {})
|
52
|
+
options[:method] ||= self.class.geocoder_options[:method]
|
53
|
+
return nil unless geocoded?
|
54
|
+
Geocoder::Calculations.bearing_between(
|
55
|
+
point, to_coordinates, options)
|
56
|
+
end
|
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
|
+
##
|
70
|
+
# Look up coordinates and assign to +latitude+ and +longitude+ attributes
|
71
|
+
# (or other as specified in +geocoded_by+). Returns coordinates (array).
|
72
|
+
#
|
73
|
+
def geocode
|
74
|
+
fail
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# Look up address and assign to +address+ attribute (or other as specified
|
79
|
+
# in +reverse_geocoded_by+). Returns address (string).
|
80
|
+
#
|
81
|
+
def reverse_geocode
|
82
|
+
fail
|
83
|
+
end
|
84
|
+
|
85
|
+
private # --------------------------------------------------------------
|
86
|
+
|
87
|
+
##
|
88
|
+
# Look up geographic data based on object attributes (configured in
|
89
|
+
# geocoded_by or reverse_geocoded_by) and handle the results with the
|
90
|
+
# block (given to geocoded_by or reverse_geocoded_by). The block is
|
91
|
+
# given two-arguments: the object being geocoded and an array of
|
92
|
+
# Geocoder::Result objects).
|
93
|
+
#
|
94
|
+
def do_lookup(reverse = false)
|
95
|
+
options = self.class.geocoder_options
|
96
|
+
if reverse and options[:reverse_geocode]
|
97
|
+
query = to_coordinates
|
98
|
+
elsif !reverse and options[:geocode]
|
99
|
+
query = send(options[:user_address])
|
100
|
+
else
|
101
|
+
return
|
102
|
+
end
|
103
|
+
|
104
|
+
query_options = [:lookup, :ip_lookup, :language].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)
|
112
|
+
|
113
|
+
# execute custom block, if specified in configuration
|
114
|
+
block_key = reverse ? :reverse_block : :geocode_block
|
115
|
+
if custom_block = options[block_key]
|
116
|
+
custom_block.call(self, results)
|
117
|
+
|
118
|
+
# else execute block passed directly to this method,
|
119
|
+
# which generally performs the "auto-assigns"
|
120
|
+
elsif block_given?
|
121
|
+
yield(self, results)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Geocoder::Store
|
2
|
+
module MongoBase
|
3
|
+
|
4
|
+
def self.included_by_model(base)
|
5
|
+
base.class_eval do
|
6
|
+
|
7
|
+
scope :geocoded, lambda {
|
8
|
+
where(geocoder_options[:coordinates].ne => nil)
|
9
|
+
}
|
10
|
+
|
11
|
+
scope :not_geocoded, lambda {
|
12
|
+
where(geocoder_options[:coordinates] => nil)
|
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
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Coordinates [lat,lon] of the object.
|
50
|
+
# This method always returns coordinates in lat,lon order,
|
51
|
+
# even though internally they are stored in the opposite order.
|
52
|
+
#
|
53
|
+
def to_coordinates
|
54
|
+
coords = send(self.class.geocoder_options[:coordinates])
|
55
|
+
coords.is_a?(Array) ? coords.reverse : []
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Look up coordinates and assign to +latitude+ and +longitude+ attributes
|
60
|
+
# (or other as specified in +geocoded_by+). Returns coordinates (array).
|
61
|
+
#
|
62
|
+
def geocode
|
63
|
+
do_lookup(false) do |o,rs|
|
64
|
+
if r = rs.first
|
65
|
+
unless r.coordinates.nil?
|
66
|
+
o.__send__ "#{self.class.geocoder_options[:coordinates]}=", r.coordinates.reverse
|
67
|
+
end
|
68
|
+
r.coordinates
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# Look up address and assign to +address+ attribute (or other as specified
|
75
|
+
# in +reverse_geocoded_by+). Returns address (string).
|
76
|
+
#
|
77
|
+
def reverse_geocode
|
78
|
+
do_lookup(true) do |o,rs|
|
79
|
+
if r = rs.first
|
80
|
+
unless r.address.nil?
|
81
|
+
o.__send__ "#{self.class.geocoder_options[:fetched_address]}=", r.address
|
82
|
+
end
|
83
|
+
r.address
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
@@ -0,0 +1,74 @@
|
|
1
|
+
#
|
2
|
+
# = Hash Recursive Merge
|
3
|
+
#
|
4
|
+
# Merges a Ruby Hash recursively, Also known as deep merge.
|
5
|
+
# Recursive version of Hash#merge and Hash#merge!.
|
6
|
+
#
|
7
|
+
# Category:: Ruby
|
8
|
+
# Package:: Hash
|
9
|
+
# Author:: Simone Carletti <weppos@weppos.net>
|
10
|
+
# Copyright:: 2007-2008 The Authors
|
11
|
+
# License:: MIT License
|
12
|
+
# Link:: http://www.simonecarletti.com/
|
13
|
+
# Source:: http://gist.github.com/gists/6391/
|
14
|
+
#
|
15
|
+
module HashRecursiveMerge
|
16
|
+
|
17
|
+
#
|
18
|
+
# Recursive version of Hash#merge!
|
19
|
+
#
|
20
|
+
# Adds the contents of +other_hash+ to +hsh+,
|
21
|
+
# merging entries in +hsh+ with duplicate keys with those from +other_hash+.
|
22
|
+
#
|
23
|
+
# Compared with Hash#merge!, this method supports nested hashes.
|
24
|
+
# When both +hsh+ and +other_hash+ contains an entry with the same key,
|
25
|
+
# it merges and returns the values from both arrays.
|
26
|
+
#
|
27
|
+
# h1 = {"a" => 100, "b" => 200, "c" => {"c1" => 12, "c2" => 14}}
|
28
|
+
# h2 = {"b" => 254, "c" => {"c1" => 16, "c3" => 94}}
|
29
|
+
# h1.rmerge!(h2) #=> {"a" => 100, "b" => 254, "c" => {"c1" => 16, "c2" => 14, "c3" => 94}}
|
30
|
+
#
|
31
|
+
# Simply using Hash#merge! would return
|
32
|
+
#
|
33
|
+
# h1.merge!(h2) #=> {"a" => 100, "b" = >254, "c" => {"c1" => 16, "c3" => 94}}
|
34
|
+
#
|
35
|
+
def rmerge!(other_hash)
|
36
|
+
merge!(other_hash) do |key, oldval, newval|
|
37
|
+
oldval.class == self.class ? oldval.rmerge!(newval) : newval
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# Recursive version of Hash#merge
|
43
|
+
#
|
44
|
+
# Compared with Hash#merge!, this method supports nested hashes.
|
45
|
+
# When both +hsh+ and +other_hash+ contains an entry with the same key,
|
46
|
+
# it merges and returns the values from both arrays.
|
47
|
+
#
|
48
|
+
# Compared with Hash#merge, this method provides a different approch
|
49
|
+
# for merging nasted hashes.
|
50
|
+
# If the value of a given key is an Hash and both +other_hash+ abd +hsh
|
51
|
+
# includes the same key, the value is merged instead replaced with
|
52
|
+
# +other_hash+ value.
|
53
|
+
#
|
54
|
+
# h1 = {"a" => 100, "b" => 200, "c" => {"c1" => 12, "c2" => 14}}
|
55
|
+
# h2 = {"b" => 254, "c" => {"c1" => 16, "c3" => 94}}
|
56
|
+
# h1.rmerge(h2) #=> {"a" => 100, "b" => 254, "c" => {"c1" => 16, "c2" => 14, "c3" => 94}}
|
57
|
+
#
|
58
|
+
# Simply using Hash#merge would return
|
59
|
+
#
|
60
|
+
# h1.merge(h2) #=> {"a" => 100, "b" = >254, "c" => {"c1" => 16, "c3" => 94}}
|
61
|
+
#
|
62
|
+
def rmerge(other_hash)
|
63
|
+
r = {}
|
64
|
+
merge(other_hash) do |key, oldval, newval|
|
65
|
+
r[key] = oldval.class == self.class ? oldval.rmerge(newval) : newval
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
class Hash
|
73
|
+
include HashRecursiveMerge
|
74
|
+
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).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/oauth_util.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
# A utility for signing an url using OAuth in a way that's convenient for debugging
|
2
|
+
# Note: the standard Ruby OAuth lib is here http://github.com/mojodna/oauth
|
3
|
+
# Source: http://gist.github.com/383159
|
4
|
+
# License: http://gist.github.com/375593
|
5
|
+
# Usage: see example.rb below
|
6
|
+
#
|
7
|
+
# NOTE: This file has been modified from the original Gist:
|
8
|
+
#
|
9
|
+
# 1. Fix to prevent param-array conversion, as mentioned in Gist comment.
|
10
|
+
# 2. Query string escaping has been changed. See:
|
11
|
+
# https://github.com/alexreisner/geocoder/pull/360
|
12
|
+
#
|
13
|
+
|
14
|
+
require 'uri'
|
15
|
+
require 'cgi'
|
16
|
+
require 'openssl'
|
17
|
+
require 'base64'
|
18
|
+
|
19
|
+
class OauthUtil
|
20
|
+
|
21
|
+
attr_accessor :consumer_key, :consumer_secret, :token, :token_secret, :req_method,
|
22
|
+
:sig_method, :oauth_version, :callback_url, :params, :req_url, :base_str
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@consumer_key = ''
|
26
|
+
@consumer_secret = ''
|
27
|
+
@token = ''
|
28
|
+
@token_secret = ''
|
29
|
+
@req_method = 'GET'
|
30
|
+
@sig_method = 'HMAC-SHA1'
|
31
|
+
@oauth_version = '1.0'
|
32
|
+
@callback_url = ''
|
33
|
+
end
|
34
|
+
|
35
|
+
# openssl::random_bytes returns non-word chars, which need to be removed. using alt method to get length
|
36
|
+
# ref http://snippets.dzone.com/posts/show/491
|
37
|
+
def nonce
|
38
|
+
Array.new( 5 ) { rand(256) }.pack('C*').unpack('H*').first
|
39
|
+
end
|
40
|
+
|
41
|
+
def percent_encode( string )
|
42
|
+
|
43
|
+
# ref http://snippets.dzone.com/posts/show/1260
|
44
|
+
return URI.escape( string, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]") ).gsub('*', '%2A')
|
45
|
+
end
|
46
|
+
|
47
|
+
# @ref http://oauth.net/core/1.0/#rfc.section.9.2
|
48
|
+
def signature
|
49
|
+
key = percent_encode( @consumer_secret ) + '&' + percent_encode( @token_secret )
|
50
|
+
|
51
|
+
# ref: http://blog.nathanielbibler.com/post/63031273/openssl-hmac-vs-ruby-hmac-benchmarks
|
52
|
+
digest = OpenSSL::Digest.new( 'sha1' )
|
53
|
+
hmac = OpenSSL::HMAC.digest( digest, key, @base_str )
|
54
|
+
|
55
|
+
# ref http://groups.google.com/group/oauth-ruby/browse_thread/thread/9110ed8c8f3cae81
|
56
|
+
Base64.encode64( hmac ).chomp.gsub( /\n/, '' )
|
57
|
+
end
|
58
|
+
|
59
|
+
# sort (very important as it affects the signature), concat, and percent encode
|
60
|
+
# @ref http://oauth.net/core/1.0/#rfc.section.9.1.1
|
61
|
+
# @ref http://oauth.net/core/1.0/#9.2.1
|
62
|
+
# @ref http://oauth.net/core/1.0/#rfc.section.A.5.1
|
63
|
+
def query_string
|
64
|
+
pairs = []
|
65
|
+
@params.sort.each { | key, val |
|
66
|
+
pairs.push( "#{ CGI.escape(key.to_s).gsub(/%(5B|5D)/n) { [$1].pack('H*') } }=#{ CGI.escape(val.to_s) }" )
|
67
|
+
}
|
68
|
+
pairs.join '&'
|
69
|
+
end
|
70
|
+
|
71
|
+
# organize params & create signature
|
72
|
+
def sign( parsed_url )
|
73
|
+
|
74
|
+
@params = {
|
75
|
+
'oauth_consumer_key' => @consumer_key,
|
76
|
+
'oauth_nonce' => nonce,
|
77
|
+
'oauth_signature_method' => @sig_method,
|
78
|
+
'oauth_timestamp' => Time.now.to_i.to_s,
|
79
|
+
'oauth_version' => @oauth_version
|
80
|
+
}
|
81
|
+
|
82
|
+
# if url has query, merge key/values into params obj overwriting defaults
|
83
|
+
if parsed_url.query
|
84
|
+
CGI.parse( parsed_url.query ).each do |k,v|
|
85
|
+
if v.is_a?(Array) && v.count == 1
|
86
|
+
@params[k] = v.first
|
87
|
+
else
|
88
|
+
@params[k] = v
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# @ref http://oauth.net/core/1.0/#rfc.section.9.1.2
|
94
|
+
@req_url = parsed_url.scheme + '://' + parsed_url.host + parsed_url.path
|
95
|
+
|
96
|
+
# create base str. make it an object attr for ez debugging
|
97
|
+
# ref http://oauth.net/core/1.0/#anchor14
|
98
|
+
@base_str = [
|
99
|
+
@req_method,
|
100
|
+
percent_encode( req_url ),
|
101
|
+
|
102
|
+
# normalization is just x-www-form-urlencoded
|
103
|
+
percent_encode( query_string )
|
104
|
+
|
105
|
+
].join( '&' )
|
106
|
+
|
107
|
+
# add signature
|
108
|
+
@params[ 'oauth_signature' ] = signature
|
109
|
+
|
110
|
+
return self
|
111
|
+
end
|
112
|
+
end
|