geokit 1.7.1 → 1.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +6 -14
- data/.travis.yml +1 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +2 -1
- data/MIT-LICENSE +20 -0
- data/README.markdown +44 -39
- data/Rakefile +15 -0
- data/fixtures/vcr_cassettes/bing_full.yml +102 -0
- data/fixtures/vcr_cassettes/bing_full_au.yml +91 -0
- data/fixtures/vcr_cassettes/bing_full_de.yml +91 -0
- data/fixtures/vcr_cassettes/fcc_reverse_geocode.yml +37 -0
- data/fixtures/vcr_cassettes/free_geo_ip_geocode.yml +36 -0
- data/fixtures/vcr_cassettes/geo_plugin_geocode.yml +38 -0
- data/fixtures/vcr_cassettes/geonames_geocode.yml +304 -0
- data/fixtures/vcr_cassettes/{google3_city.yml → google_city.yml} +0 -0
- data/fixtures/vcr_cassettes/{google3_country_code_biased_result.yml → google_country_code_biased_result.yml} +0 -0
- data/fixtures/vcr_cassettes/{google3_full.yml → google_full.yml} +0 -0
- data/fixtures/vcr_cassettes/{google3_full_short.yml → google_full_short.yml} +0 -0
- data/fixtures/vcr_cassettes/{google3_language_response_fr.yml → google_language_response_fr.yml} +0 -0
- data/fixtures/vcr_cassettes/{google3_multi.yml → google_multi.yml} +0 -0
- data/fixtures/vcr_cassettes/{google3_reverse_madrid.yml → google_reverse_madrid.yml} +0 -0
- data/fixtures/vcr_cassettes/ripe_geocode.yml +66 -0
- data/fixtures/vcr_cassettes/ripe_geocode_au.yml +66 -0
- data/geokit.gemspec +1 -1
- data/lib/geokit.rb +5 -0
- data/lib/geokit/bounds.rb +96 -0
- data/lib/geokit/core_ext.rb +17 -0
- data/lib/geokit/geo_loc.rb +134 -0
- data/lib/geokit/geocoders.rb +48 -35
- data/lib/geokit/geocoders/base_ip.rb +43 -0
- data/lib/geokit/geocoders/bing.rb +101 -0
- data/lib/geokit/geocoders/ca_geocoder.rb +50 -0
- data/lib/geokit/{services → geocoders}/fcc.rb +17 -20
- data/lib/geokit/geocoders/free_geo_ip.rb +34 -0
- data/lib/geokit/geocoders/geo_plugin.rb +33 -0
- data/lib/geokit/geocoders/geonames.rb +53 -0
- data/lib/geokit/{services/google3.rb → geocoders/google.rb} +59 -57
- data/lib/geokit/geocoders/ip.rb +69 -0
- data/lib/geokit/geocoders/mapquest.rb +72 -0
- data/lib/geokit/geocoders/maxmind.rb +29 -0
- data/lib/geokit/geocoders/openstreetmap.rb +119 -0
- data/lib/geokit/geocoders/ripe.rb +41 -0
- data/lib/geokit/{services → geocoders}/us_geocoder.rb +15 -20
- data/lib/geokit/{services → geocoders}/yahoo.rb +52 -55
- data/lib/geokit/geocoders/yandex.rb +61 -0
- data/lib/geokit/inflectors.rb +1 -2
- data/lib/geokit/lat_lng.rb +129 -0
- data/lib/geokit/mappable.rb +41 -424
- data/lib/geokit/multi_geocoder.rb +6 -2
- data/lib/geokit/polygon.rb +46 -0
- data/lib/geokit/version.rb +1 -1
- data/test/helper.rb +2 -12
- data/test/test_base_geocoder.rb +0 -10
- data/test/test_bing_geocoder.rb +60 -0
- data/test/test_fcc_geocoder.rb +23 -0
- data/test/test_free_geo_ip_geocoder.rb +23 -0
- data/test/test_geo_plugin_geocoder.rb +23 -0
- data/test/test_geonames_geocoder.rb +23 -0
- data/test/test_google_geocoder.rb +208 -235
- data/test/test_maxmind_geocoder.rb +35 -4
- data/test/test_multi_geocoder.rb +3 -1
- data/test/test_ripe_geocoder.rb +35 -0
- data/test/test_yahoo_geocoder.rb +0 -12
- metadata +78 -52
- data/LICENSE +0 -25
- data/Manifest.txt +0 -21
- data/data/GeoLiteCity.dat +0 -0
- data/lib/geokit/services/ca_geocoder.rb +0 -55
- data/lib/geokit/services/geo_plugin.rb +0 -31
- data/lib/geokit/services/geonames.rb +0 -53
- data/lib/geokit/services/google.rb +0 -158
- data/lib/geokit/services/ip.rb +0 -103
- data/lib/geokit/services/maxmind.rb +0 -39
- data/lib/geokit/services/openstreetmap.rb +0 -119
- data/lib/geokit/services/ripe.rb +0 -32
- data/lib/geokit/services/yandex.rb +0 -51
- data/test/test_google_geocoder3.rb +0 -238
- data/test/test_google_reverse_geocoder.rb +0 -49
data/Manifest.txt
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
History.txt
|
2
|
-
Manifest.txt
|
3
|
-
README.markdown
|
4
|
-
Rakefile
|
5
|
-
geokit.gemspec
|
6
|
-
lib/geokit.rb
|
7
|
-
lib/geokit/geocoders.rb
|
8
|
-
lib/geokit/mappable.rb
|
9
|
-
test/test_base_geocoder.rb
|
10
|
-
test/test_bounds.rb
|
11
|
-
test/test_ca_geocoder.rb
|
12
|
-
test/test_geoloc.rb
|
13
|
-
test/test_geoplugin_geocoder.rb
|
14
|
-
test/test_google_geocoder.rb
|
15
|
-
test/test_google_reverse_geocoder.rb
|
16
|
-
test/test_inflector.rb
|
17
|
-
test/test_ipgeocoder.rb
|
18
|
-
test/test_latlng.rb
|
19
|
-
test/test_multi_geocoder.rb
|
20
|
-
test/test_us_geocoder.rb
|
21
|
-
test/test_yahoo_geocoder.rb
|
data/data/GeoLiteCity.dat
DELETED
Binary file
|
@@ -1,55 +0,0 @@
|
|
1
|
-
|
2
|
-
# Geocoder CA geocoder implementation. Requires the Geokit::Geocoders::GEOCODER_CA variable to
|
3
|
-
# contain true or false based upon whether authentication is to occur. Conforms to the
|
4
|
-
# interface set by the Geocoder class.
|
5
|
-
#
|
6
|
-
# Returns a response like:
|
7
|
-
# <?xml version="1.0" encoding="UTF-8" ?>
|
8
|
-
# <geodata>
|
9
|
-
# <latt>49.243086</latt>
|
10
|
-
# <longt>-123.153684</longt>
|
11
|
-
# </geodata>
|
12
|
-
module Geokit
|
13
|
-
module Geocoders
|
14
|
-
class CaGeocoder < Geocoder
|
15
|
-
|
16
|
-
private
|
17
|
-
|
18
|
-
# Template method which does the geocode lookup.
|
19
|
-
def self.do_geocode(address, options = {})
|
20
|
-
raise ArgumentError('Geocoder.ca requires a GeoLoc argument') unless address.is_a?(GeoLoc)
|
21
|
-
url = construct_request(address)
|
22
|
-
res = self.call_geocoder_service(url)
|
23
|
-
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
24
|
-
xml = res.body
|
25
|
-
logger.debug "Geocoder.ca geocoding. Address: #{address}. Result: #{xml}"
|
26
|
-
# Parse the document.
|
27
|
-
doc = REXML::Document.new(xml)
|
28
|
-
address.lat = doc.elements['//latt'].text
|
29
|
-
address.lng = doc.elements['//longt'].text
|
30
|
-
address.success = true
|
31
|
-
return address
|
32
|
-
rescue
|
33
|
-
logger.error "Caught an error during Geocoder.ca geocoding call: "+$!
|
34
|
-
return GeoLoc.new
|
35
|
-
end
|
36
|
-
|
37
|
-
# Formats the request in the format acceptable by the CA geocoder.
|
38
|
-
def self.construct_request(location)
|
39
|
-
url = ""
|
40
|
-
url += add_ampersand(url) + "stno=#{location.street_number}" if location.street_address
|
41
|
-
url += add_ampersand(url) + "addresst=#{Geokit::Inflector::url_escape(location.street_name)}" if location.street_address
|
42
|
-
url += add_ampersand(url) + "city=#{Geokit::Inflector::url_escape(location.city)}" if location.city
|
43
|
-
url += add_ampersand(url) + "prov=#{location.state}" if location.state
|
44
|
-
url += add_ampersand(url) + "postal=#{location.zip}" if location.zip
|
45
|
-
url += add_ampersand(url) + "auth=#{Geokit::Geocoders::geocoder_ca}" if Geokit::Geocoders::geocoder_ca
|
46
|
-
url += add_ampersand(url) + "geoit=xml"
|
47
|
-
'http://geocoder.ca/?' + url
|
48
|
-
end
|
49
|
-
|
50
|
-
def self.add_ampersand(url)
|
51
|
-
url && url.length > 0 ? "&" : ""
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
@@ -1,31 +0,0 @@
|
|
1
|
-
module Geokit
|
2
|
-
module Geocoders
|
3
|
-
# Provides geocoding based upon an IP address. The underlying web service is geoplugin.net
|
4
|
-
class GeoPluginGeocoder < Geocoder
|
5
|
-
private
|
6
|
-
|
7
|
-
def self.do_geocode(ip, options = {})
|
8
|
-
return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
|
9
|
-
response = self.call_geocoder_service("http://www.geoplugin.net/xml.gp?ip=#{ip}")
|
10
|
-
return response.is_a?(Net::HTTPSuccess) ? parse_xml(response.body) : GeoLoc.new
|
11
|
-
rescue
|
12
|
-
logger.error "Caught an error during GeoPluginGeocoder geocoding call: "+$!
|
13
|
-
return GeoLoc.new
|
14
|
-
end
|
15
|
-
|
16
|
-
def self.parse_xml(xml)
|
17
|
-
xml = REXML::Document.new(xml)
|
18
|
-
geo = GeoLoc.new
|
19
|
-
geo.provider='geoPlugin'
|
20
|
-
geo.city = xml.elements['//geoplugin_city'].text
|
21
|
-
geo.state = xml.elements['//geoplugin_region'].text
|
22
|
-
geo.country_code = xml.elements['//geoplugin_countryCode'].text
|
23
|
-
geo.lat = xml.elements['//geoplugin_latitude'].text.to_f
|
24
|
-
geo.lng = xml.elements['//geoplugin_longitude'].text.to_f
|
25
|
-
geo.success = !!geo.city && !geo.city.empty?
|
26
|
-
return geo
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
end
|
31
|
-
end
|
@@ -1,53 +0,0 @@
|
|
1
|
-
module Geokit
|
2
|
-
module Geocoders
|
3
|
-
# Another geocoding web service
|
4
|
-
# http://www.geonames.org
|
5
|
-
class GeonamesGeocoder < Geocoder
|
6
|
-
|
7
|
-
private
|
8
|
-
|
9
|
-
# Template method which does the geocode lookup.
|
10
|
-
def self.do_geocode(address, options = {})
|
11
|
-
address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
|
12
|
-
# geonames need a space seperated search string
|
13
|
-
address_str.gsub!(/,/, " ")
|
14
|
-
params = "/postalCodeSearch?placename=#{Geokit::Inflector::url_escape(address_str)}&maxRows=10"
|
15
|
-
|
16
|
-
if(Geokit::Geocoders::geonames)
|
17
|
-
url = "http://ws.geonames.net#{params}&username=#{Geokit::Geocoders::geonames}"
|
18
|
-
else
|
19
|
-
url = "http://ws.geonames.org#{params}"
|
20
|
-
end
|
21
|
-
|
22
|
-
res = self.call_geocoder_service(url)
|
23
|
-
|
24
|
-
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
25
|
-
|
26
|
-
xml=res.body
|
27
|
-
logger.debug "Geonames geocoding. Address: #{address}. Result: #{xml}"
|
28
|
-
doc=REXML::Document.new(xml)
|
29
|
-
|
30
|
-
if(doc.elements['//geonames/totalResultsCount'].text.to_i > 0)
|
31
|
-
res=GeoLoc.new
|
32
|
-
|
33
|
-
# only take the first result
|
34
|
-
res.lat=doc.elements['//code/lat'].text if doc.elements['//code/lat']
|
35
|
-
res.lng=doc.elements['//code/lng'].text if doc.elements['//code/lng']
|
36
|
-
res.country_code=doc.elements['//code/countryCode'].text if doc.elements['//code/countryCode']
|
37
|
-
res.provider='genomes'
|
38
|
-
res.city=doc.elements['//code/name'].text if doc.elements['//code/name']
|
39
|
-
res.state=doc.elements['//code/adminName1'].text if doc.elements['//code/adminName1']
|
40
|
-
res.zip=doc.elements['//code/postalcode'].text if doc.elements['//code/postalcode']
|
41
|
-
res.success=true
|
42
|
-
return res
|
43
|
-
else
|
44
|
-
logger.info "Geonames was unable to geocode address: "+address
|
45
|
-
return GeoLoc.new
|
46
|
-
end
|
47
|
-
|
48
|
-
rescue
|
49
|
-
logger.error "Caught an error during Geonames geocoding call: "+$!
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
@@ -1,158 +0,0 @@
|
|
1
|
-
module Geokit
|
2
|
-
module Geocoders
|
3
|
-
# -------------------------------------------------------------------------------------------
|
4
|
-
# Address geocoders that also provide reverse geocoding
|
5
|
-
# -------------------------------------------------------------------------------------------
|
6
|
-
|
7
|
-
# Google geocoder implementation. Requires the Geokit::Geocoders::GOOGLE variable to
|
8
|
-
# contain a Google API key. Conforms to the interface set by the Geocoder class.
|
9
|
-
class GoogleGeocoder < Geocoder
|
10
|
-
|
11
|
-
private
|
12
|
-
|
13
|
-
# Template method which does the reverse-geocode lookup.
|
14
|
-
def self.do_reverse_geocode(latlng)
|
15
|
-
latlng=LatLng.normalize(latlng)
|
16
|
-
res = self.call_geocoder_service("http://maps.google.com/maps/geo?ll=#{Geokit::Inflector::url_escape(latlng.ll)}&output=xml&key=#{Geokit::Geocoders::google}&oe=utf-8")
|
17
|
-
# res = Net::HTTP.get_response(URI.parse("http://maps.google.com/maps/geo?ll=#{Geokit::Inflector::url_escape(address_str)}&output=xml&key=#{Geokit::Geocoders::google}&oe=utf-8"))
|
18
|
-
return GeoLoc.new unless (res.is_a?(Net::HTTPSuccess) || res.is_a?(Net::HTTPOK))
|
19
|
-
xml = self.transcode_to_utf8(res.body)
|
20
|
-
logger.debug "Google reverse-geocoding. LL: #{latlng}. Result: #{xml}"
|
21
|
-
return self.xml2GeoLoc(xml)
|
22
|
-
end
|
23
|
-
|
24
|
-
# Template method which does the geocode lookup.
|
25
|
-
#
|
26
|
-
# Supports viewport/country code biasing
|
27
|
-
#
|
28
|
-
# ==== OPTIONS
|
29
|
-
# * :bias - This option makes the Google Geocoder return results biased to a particular
|
30
|
-
# country or viewport. Country code biasing is achieved by passing the ccTLD
|
31
|
-
# ('uk' for .co.uk, for example) as a :bias value. For a list of ccTLD's,
|
32
|
-
# look here: http://en.wikipedia.org/wiki/CcTLD. By default, the geocoder
|
33
|
-
# will be biased to results within the US (ccTLD .com).
|
34
|
-
#
|
35
|
-
# If you'd like the Google Geocoder to prefer results within a given viewport,
|
36
|
-
# you can pass a Geokit::Bounds object as the :bias value.
|
37
|
-
#
|
38
|
-
# ==== EXAMPLES
|
39
|
-
# # By default, the geocoder will return Syracuse, NY
|
40
|
-
# Geokit::Geocoders::GoogleGeocoder.geocode('Syracuse').country_code # => 'US'
|
41
|
-
# # With country code biasing, it returns Syracuse in Sicily, Italy
|
42
|
-
# Geokit::Geocoders::GoogleGeocoder.geocode('Syracuse', :bias => :it).country_code # => 'IT'
|
43
|
-
#
|
44
|
-
# # By default, the geocoder will return Winnetka, IL
|
45
|
-
# Geokit::Geocoders::GoogleGeocoder.geocode('Winnetka').state # => 'IL'
|
46
|
-
# # When biased to an bounding box around California, it will now return the Winnetka neighbourhood, CA
|
47
|
-
# bounds = Geokit::Bounds.normalize([34.074081, -118.694401], [34.321129, -118.399487])
|
48
|
-
# Geokit::Geocoders::GoogleGeocoder.geocode('Winnetka', :bias => bounds).state # => 'CA'
|
49
|
-
def self.do_geocode(address, options = {})
|
50
|
-
bias_str = options[:bias] ? construct_bias_string_from_options(options[:bias]) : ''
|
51
|
-
address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
|
52
|
-
res = self.call_geocoder_service("http://maps.google.com/maps/geo?q=#{Geokit::Inflector::url_escape(address_str)}&output=xml#{bias_str}&key=#{Geokit::Geocoders::google}&oe=utf-8")
|
53
|
-
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
54
|
-
xml = self.transcode_to_utf8(res.body)
|
55
|
-
logger.debug "Google geocoding. Address: #{address}. Result: #{xml}"
|
56
|
-
return self.xml2GeoLoc(xml, address)
|
57
|
-
end
|
58
|
-
|
59
|
-
def self.construct_bias_string_from_options(bias)
|
60
|
-
if bias.is_a?(String) or bias.is_a?(Symbol)
|
61
|
-
# country code biasing
|
62
|
-
"&gl=#{bias.to_s.downcase}"
|
63
|
-
elsif bias.is_a?(Bounds)
|
64
|
-
# viewport biasing
|
65
|
-
"&ll=#{precise_ll(bias.center)}&spn=#{precise_ll(bias.to_span)}"
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
# Precision to 6 decimal places as per:
|
70
|
-
# https://developers.google.com/maps/documentation/staticmaps/?hl=en#Latlons
|
71
|
-
def self.precise_ll(loc)
|
72
|
-
"#{"%.6f" % loc.lat},#{"%.6f" % loc.lng}"
|
73
|
-
end
|
74
|
-
|
75
|
-
def self.xml2GeoLoc(xml, address="")
|
76
|
-
doc=REXML::Document.new(xml)
|
77
|
-
|
78
|
-
if doc.elements['//kml/Response/Status/code'].text == '200'
|
79
|
-
geoloc = nil
|
80
|
-
# Google can return multiple results as //Placemark elements.
|
81
|
-
# iterate through each and extract each placemark as a geoloc
|
82
|
-
doc.each_element('//Placemark') do |e|
|
83
|
-
extracted_geoloc = extract_placemark(e) # g is now an instance of GeoLoc
|
84
|
-
if geoloc.nil?
|
85
|
-
# first time through, geoloc is still nil, so we make it the geoloc we just extracted
|
86
|
-
geoloc = extracted_geoloc
|
87
|
-
else
|
88
|
-
# second (and subsequent) iterations, we push additional
|
89
|
-
# geolocs onto "geoloc.all"
|
90
|
-
geoloc.all.push(extracted_geoloc)
|
91
|
-
end
|
92
|
-
end
|
93
|
-
return geoloc
|
94
|
-
elsif doc.elements['//kml/Response/Status/code'].text == '620'
|
95
|
-
raise Geokit::TooManyQueriesError
|
96
|
-
else
|
97
|
-
logger.info "Google was unable to geocode address: "+address
|
98
|
-
return GeoLoc.new
|
99
|
-
end
|
100
|
-
|
101
|
-
rescue Geokit::TooManyQueriesError
|
102
|
-
# re-raise because of other rescue
|
103
|
-
raise Geokit::TooManyQueriesError, "Google returned a 620 status, too many queries. The given key has gone over the requests limit in the 24 hour period or has submitted too many requests in too short a period of time. If you're sending multiple requests in parallel or in a tight loop, use a timer or pause in your code to make sure you don't send the requests too quickly."
|
104
|
-
rescue
|
105
|
-
logger.error "Caught an error during Google geocoding call: "+$!
|
106
|
-
return GeoLoc.new
|
107
|
-
end
|
108
|
-
|
109
|
-
# extracts a single geoloc from a //placemark element in the google results xml
|
110
|
-
def self.extract_placemark(doc)
|
111
|
-
res = GeoLoc.new
|
112
|
-
coordinates=doc.elements['.//coordinates'].text.to_s.split(',')
|
113
|
-
|
114
|
-
#basics
|
115
|
-
res.lat=coordinates[1]
|
116
|
-
res.lng=coordinates[0]
|
117
|
-
res.country_code=doc.elements['.//CountryNameCode'].text if doc.elements['.//CountryNameCode']
|
118
|
-
res.provider='google'
|
119
|
-
|
120
|
-
#extended -- false if not not available
|
121
|
-
res.city = doc.elements['.//LocalityName'].text if doc.elements['.//LocalityName']
|
122
|
-
res.state = doc.elements['.//AdministrativeAreaName'].text if doc.elements['.//AdministrativeAreaName']
|
123
|
-
res.province = doc.elements['.//SubAdministrativeAreaName'].text if doc.elements['.//SubAdministrativeAreaName']
|
124
|
-
res.full_address = doc.elements['.//address'].text if doc.elements['.//address'] # google provides it
|
125
|
-
res.zip = doc.elements['.//PostalCodeNumber'].text if doc.elements['.//PostalCodeNumber']
|
126
|
-
res.street_address = doc.elements['.//ThoroughfareName'].text if doc.elements['.//ThoroughfareName']
|
127
|
-
res.country = doc.elements['.//CountryName'].text if doc.elements['.//CountryName']
|
128
|
-
res.district = doc.elements['.//DependentLocalityName'].text if doc.elements['.//DependentLocalityName']
|
129
|
-
# Translate accuracy into Yahoo-style token address, street, zip, zip+4, city, state, country
|
130
|
-
# For Google, 1=low accuracy, 8=high accuracy
|
131
|
-
address_details=doc.elements['.//*[local-name() = "AddressDetails"]']
|
132
|
-
res.accuracy = address_details ? address_details.attributes['Accuracy'].to_i : 0
|
133
|
-
res.precision=%w{unknown country state state city zip zip+4 street address building}[res.accuracy]
|
134
|
-
|
135
|
-
# google returns a set of suggested boundaries for the geocoded result
|
136
|
-
if suggested_bounds = doc.elements['//LatLonBox']
|
137
|
-
res.suggested_bounds = Bounds.normalize(
|
138
|
-
[suggested_bounds.attributes['south'], suggested_bounds.attributes['west']],
|
139
|
-
[suggested_bounds.attributes['north'], suggested_bounds.attributes['east']])
|
140
|
-
end
|
141
|
-
|
142
|
-
res.success=true
|
143
|
-
|
144
|
-
return res
|
145
|
-
end
|
146
|
-
|
147
|
-
def self.transcode_to_utf8(body)
|
148
|
-
require 'iconv' unless String.method_defined?(:encode)
|
149
|
-
if String.method_defined?(:encode)
|
150
|
-
body.encode!('UTF-8', 'UTF-8', :invalid => :replace)
|
151
|
-
else
|
152
|
-
ic = Iconv.new('UTF-8', 'UTF-8//IGNORE')
|
153
|
-
body = ic.iconv(body)
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|
158
|
-
end
|
data/lib/geokit/services/ip.rb
DELETED
@@ -1,103 +0,0 @@
|
|
1
|
-
module Geokit
|
2
|
-
module Geocoders
|
3
|
-
# Provides geocoding based upon an IP address. The underlying web service is a hostip.info
|
4
|
-
# which sources their data through a combination of publicly available information as well
|
5
|
-
# as community contributions.
|
6
|
-
class IpGeocoder < Geocoder
|
7
|
-
|
8
|
-
# A number of non-routable IP ranges.
|
9
|
-
#
|
10
|
-
# --
|
11
|
-
# Sources for these:
|
12
|
-
# RFC 3330: Special-Use IPv4 Addresses
|
13
|
-
# The bogon list: http://www.cymru.com/Documents/bogon-list.html
|
14
|
-
|
15
|
-
NON_ROUTABLE_IP_RANGES = [
|
16
|
-
IPAddr.new('0.0.0.0/8'), # "This" Network
|
17
|
-
IPAddr.new('10.0.0.0/8'), # Private-Use Networks
|
18
|
-
IPAddr.new('14.0.0.0/8'), # Public-Data Networks
|
19
|
-
IPAddr.new('127.0.0.0/8'), # Loopback
|
20
|
-
IPAddr.new('169.254.0.0/16'), # Link local
|
21
|
-
IPAddr.new('172.16.0.0/12'), # Private-Use Networks
|
22
|
-
IPAddr.new('192.0.2.0/24'), # Test-Net
|
23
|
-
IPAddr.new('192.168.0.0/16'), # Private-Use Networks
|
24
|
-
IPAddr.new('198.18.0.0/15'), # Network Interconnect Device Benchmark Testing
|
25
|
-
IPAddr.new('224.0.0.0/4'), # Multicast
|
26
|
-
IPAddr.new('240.0.0.0/4') # Reserved for future use
|
27
|
-
].freeze
|
28
|
-
|
29
|
-
private
|
30
|
-
|
31
|
-
# Given an IP address, returns a GeoLoc instance which contains latitude,
|
32
|
-
# longitude, city, and country code. Sets the success attribute to false if the ip
|
33
|
-
# parameter does not match an ip address.
|
34
|
-
def self.do_geocode(ip, options = {})
|
35
|
-
return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
|
36
|
-
return GeoLoc.new if self.private_ip_address?(ip)
|
37
|
-
url = "http://api.hostip.info/get_html.php?ip=#{ip}&position=true"
|
38
|
-
response = self.call_geocoder_service(url)
|
39
|
-
ensure_utf8_encoding(response)
|
40
|
-
response.is_a?(Net::HTTPSuccess) ? parse_body(response.body) : GeoLoc.new
|
41
|
-
rescue
|
42
|
-
logger.error "Caught an error during HostIp geocoding call: " + $!.to_s
|
43
|
-
return GeoLoc.new
|
44
|
-
end
|
45
|
-
|
46
|
-
# Converts the body to YAML since its in the form of:
|
47
|
-
#
|
48
|
-
# Country: UNITED STATES (US)
|
49
|
-
# City: Sugar Grove, IL
|
50
|
-
# Latitude: 41.7696
|
51
|
-
# Longitude: -88.4588
|
52
|
-
#
|
53
|
-
# then instantiates a GeoLoc instance to populate with location data.
|
54
|
-
def self.parse_body(body) # :nodoc:
|
55
|
-
body = body.encode('UTF-8') if body.respond_to? :encode
|
56
|
-
yaml = YAML.load(body)
|
57
|
-
res = GeoLoc.new
|
58
|
-
res.provider = 'hostip'
|
59
|
-
res.city, res.state = yaml['City'].split(', ')
|
60
|
-
res.country, res.country_code = yaml['Country'].split(' (')
|
61
|
-
res.lat = yaml['Latitude']
|
62
|
-
res.lng = yaml['Longitude']
|
63
|
-
res.country_code.chop!
|
64
|
-
res.success = !(res.city =~ /\(.+\)/)
|
65
|
-
res
|
66
|
-
end
|
67
|
-
|
68
|
-
# Forces UTF-8 encoding on the body
|
69
|
-
# Rails expects string input to be UTF-8
|
70
|
-
# hostip.info specifies the charset encoding in the headers
|
71
|
-
# thus extract encoding from headers and tell Rails about it by forcing it
|
72
|
-
def self.ensure_utf8_encoding(response)
|
73
|
-
if (enc_string = extract_charset(response))
|
74
|
-
if defined?(Encoding) && Encoding.aliases.values.include?(enc_string.upcase)
|
75
|
-
response.body.force_encoding(enc_string.upcase) if response.body.respond_to?(:force_encoding)
|
76
|
-
response.body.encode("UTF-8")
|
77
|
-
else
|
78
|
-
require 'iconv'
|
79
|
-
response.body.replace Iconv.conv("UTF8", "iso88591", response.body)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
# Extracts charset out of the response headers
|
85
|
-
def self.extract_charset(response)
|
86
|
-
if (content_type = response['content-type'])
|
87
|
-
capture = content_type.match(/charset=(.+)/)
|
88
|
-
capture && capture[1]
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
# Checks whether the IP address belongs to a private address range.
|
93
|
-
#
|
94
|
-
# This function is used to reduce the number of useless queries made to
|
95
|
-
# the geocoding service. Such queries can occur frequently during
|
96
|
-
# integration tests.
|
97
|
-
def self.private_ip_address?(ip)
|
98
|
-
return NON_ROUTABLE_IP_RANGES.any? { |range| range.include?(ip) }
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
@@ -1,39 +0,0 @@
|
|
1
|
-
module Geokit
|
2
|
-
module Geocoders
|
3
|
-
|
4
|
-
@@geoip_data_path = File.expand_path(File.join(File.dirname(__FILE__),'../../..','data','GeoLiteCity.dat'))
|
5
|
-
__define_accessors
|
6
|
-
|
7
|
-
# Provides geocoding based upon an IP address. The underlying web service is MaxMind
|
8
|
-
class MaxmindGeocoder < Geocoder
|
9
|
-
private
|
10
|
-
|
11
|
-
def self.do_geocode(ip, options = {})
|
12
|
-
# return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
|
13
|
-
return maxmind(ip)
|
14
|
-
rescue
|
15
|
-
logger.error "Caught an error during MaxMind geocoding call: " + $!.to_s
|
16
|
-
return GeoLoc.new
|
17
|
-
end
|
18
|
-
|
19
|
-
|
20
|
-
def self.maxmind(ip)
|
21
|
-
res = GeoIP.new(Geokit::Geocoders::geoip_data_path).city(ip)
|
22
|
-
|
23
|
-
loc = GeoLoc.new(
|
24
|
-
:provider => 'maxmind_city',
|
25
|
-
:lat => res.latitude,
|
26
|
-
:lng => res.longitude,
|
27
|
-
:city => res.city_name,
|
28
|
-
:state => res.region_name,
|
29
|
-
:zip => res.postal_code,
|
30
|
-
:country_code => res.country_code3
|
31
|
-
)
|
32
|
-
|
33
|
-
# loc.success = res.city_name && res.city_name != ''
|
34
|
-
loc.success = (res.longitude > 0 && res.latitude > 0)
|
35
|
-
return loc
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|