drifter 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/Gemfile +4 -0
- data/README +102 -0
- data/README.rdoc +102 -0
- data/Rakefile +8 -0
- data/drifter.gemspec +21 -0
- data/lib/drifter.rb +92 -0
- data/lib/drifter/distance/haversine.rb +69 -0
- data/lib/drifter/geocoders.rb +3 -0
- data/lib/drifter/geocoders/base.rb +57 -0
- data/lib/drifter/geocoders/google.rb +91 -0
- data/lib/drifter/geocoders/hostip.rb +92 -0
- data/lib/drifter/geocoders/yahoo.rb +160 -0
- data/lib/drifter/location.rb +43 -0
- data/lib/drifter/location/locatable.rb +35 -0
- data/lib/drifter/version.rb +3 -0
- data/test/google_geocoder_test.rb +85 -0
- data/test/locatable_test.rb +101 -0
- data/test/location_test.rb +27 -0
- data/test/responses/google_error +4 -0
- data/test/responses/google_many_results +478 -0
- data/test/responses/google_no_results +4 -0
- data/test/responses/google_one_result +55 -0
- data/test/responses/yahoo_error +1 -0
- data/test/responses/yahoo_many_results +1 -0
- data/test/responses/yahoo_no_results +1 -0
- data/test/responses/yahoo_one_result +1 -0
- data/test/test_helper.rb +22 -0
- data/test/yahoo_geocoder_test.rb +76 -0
- metadata +110 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
== drifter
|
2
|
+
|
3
|
+
drifter is a simple geocoding library with support for the Google Geocoder API and
|
4
|
+
the Yahoo Placefinder API. It also supports IP address geocoding using the hostip.info API
|
5
|
+
|
6
|
+
=== Installation
|
7
|
+
|
8
|
+
gem install drifter
|
9
|
+
require 'rubygems'
|
10
|
+
require 'drifter'
|
11
|
+
|
12
|
+
# if you're using rails:
|
13
|
+
gem install drifter
|
14
|
+
gem 'drifter' # in Gemfile
|
15
|
+
|
16
|
+
|
17
|
+
=== Usage
|
18
|
+
|
19
|
+
Drifter.geocode() takes a string representing an address or location and returns
|
20
|
+
an array of Drifter::Location objects
|
21
|
+
|
22
|
+
>> london = Drifter.geocode("London, UK").first
|
23
|
+
=> <#Drifter::Location>
|
24
|
+
|
25
|
+
Drifter::Location objects hold common address attributes like city, state, post_code
|
26
|
+
country_code, lat and lng:
|
27
|
+
|
28
|
+
>> [london.country_code, london.lat, london.lng]
|
29
|
+
=> ['GB', 51.5001524, -0.1262362]
|
30
|
+
|
31
|
+
Reverse geocoding is also supported. Instead of passing a string to geocode(), you can
|
32
|
+
pass a two item array or an object that responds to lat() and lng()
|
33
|
+
|
34
|
+
>> loc = Drifter.geocode( [53.4807125, -2.2343765] ).first
|
35
|
+
=> [loc.city, loc.state].join(', ')
|
36
|
+
=> "Manchester, England"
|
37
|
+
|
38
|
+
IP address gecoding is supported using the hostip.info api. Just pass the IP as the
|
39
|
+
location parameter
|
40
|
+
|
41
|
+
>> loc = Drifter.geocode('1.2.3.4').first
|
42
|
+
=> <#Drifter::Location>
|
43
|
+
|
44
|
+
hostip.info only provides the city, country, lat and lng. If you need more info, you
|
45
|
+
can reverse geocode the result:
|
46
|
+
|
47
|
+
>> loc = Drifter.geocode('1.2.3.4').first
|
48
|
+
>> loc = Drifter.geocode(loc).first
|
49
|
+
>> loc.state_code
|
50
|
+
=> 'CA'
|
51
|
+
|
52
|
+
Google is the default geocoding provider and works out of the box. Yahoo's placefinder
|
53
|
+
is also supported but you'll need an api key (they call it an appid)
|
54
|
+
|
55
|
+
>> Drifter.default_geocoder = :yahoo
|
56
|
+
>> Drifter::Geocoders::Yahoo.api_key = 'my_key'
|
57
|
+
|
58
|
+
>> bh = Drifter.geocode("90210").first
|
59
|
+
=> <#Drifter::Location>
|
60
|
+
|
61
|
+
You can change the geocoder per request:
|
62
|
+
|
63
|
+
>> Drifter.geocode("springfield", :geocoder => :yahoo)
|
64
|
+
>> Drifter.geocode("springfield", :geocoder => :google)
|
65
|
+
|
66
|
+
Both Yahoo and Google return a lot more info than is held in Drifter::Location's standard
|
67
|
+
attributes. You can access the extra data using the data() method which returns a Hash
|
68
|
+
|
69
|
+
# using google as the provider:
|
70
|
+
>> london.data["geometry"]["location_type"]
|
71
|
+
=> "APPROXIMATE"
|
72
|
+
|
73
|
+
The key => value pairs in the data Hash are specific to each provider, so you'll have to
|
74
|
+
check their docs to see what's available. You can also modify the query sent to the
|
75
|
+
geocoder to customise the results. Any option other than :geocoder will be URL encoded
|
76
|
+
and sent as a query string parameter e.g. Yahoo's service returns a timezone if you pass a
|
77
|
+
'flags' parameter containing a 'T':
|
78
|
+
|
79
|
+
>> Drifter.default_geocoder = :yahoo
|
80
|
+
>> paris = Drifter.geocode("Paris", :flags => 'T').first
|
81
|
+
>> paris.data["timezone"]
|
82
|
+
=> "Europe/paris"
|
83
|
+
|
84
|
+
Finally, Drifter::Location objects have a distance_to() method
|
85
|
+
|
86
|
+
>> london.distance_to(bh)
|
87
|
+
=> 5438.60013996461
|
88
|
+
|
89
|
+
Distances are returned in miles by default. You can change this per request or change the default
|
90
|
+
|
91
|
+
>> Drifter.default_units = :km
|
92
|
+
>> london.distance_to(bh, :units => :miles)
|
93
|
+
|
94
|
+
Drifter.geocode() always returns an array if the request was processed successfully by the
|
95
|
+
geocoding service. An empty array indicates that the service returned no results.
|
96
|
+
|
97
|
+
If the geocoding service returns an error, Drifter.geocode() returns nil and Drifter.last_error()
|
98
|
+
returns a hash with the error :code and :message
|
99
|
+
|
100
|
+
=== License
|
101
|
+
|
102
|
+
MIT License. Copyright 2011 Ahmed Adam (http://github.com/ahmedrb)
|
data/README.rdoc
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
== drifter
|
2
|
+
|
3
|
+
drifter is a simple geocoding library with support for the Google Geocoder API and
|
4
|
+
the Yahoo Placefinder API. It also supports IP address geocoding using the hostip.info API
|
5
|
+
|
6
|
+
=== Installation
|
7
|
+
|
8
|
+
gem install drifter
|
9
|
+
require 'rubygems'
|
10
|
+
require 'drifter'
|
11
|
+
|
12
|
+
# if you're using rails:
|
13
|
+
gem install drifter
|
14
|
+
gem 'drifter' # in Gemfile
|
15
|
+
|
16
|
+
|
17
|
+
=== Usage
|
18
|
+
|
19
|
+
Drifter.geocode() takes a string representing an address or location and returns
|
20
|
+
an array of Drifter::Location objects
|
21
|
+
|
22
|
+
>> london = Drifter.geocode("London, UK").first
|
23
|
+
=> <#Drifter::Location>
|
24
|
+
|
25
|
+
Drifter::Location objects hold common address attributes like city, state, post_code
|
26
|
+
country_code, lat and lng:
|
27
|
+
|
28
|
+
>> [london.country_code, london.lat, london.lng]
|
29
|
+
=> ['GB', 51.5001524, -0.1262362]
|
30
|
+
|
31
|
+
Reverse geocoding is also supported. Instead of passing a string to geocode(), you can
|
32
|
+
pass a two item array or an object that responds to lat() and lng()
|
33
|
+
|
34
|
+
>> loc = Drifter.geocode( [53.4807125, -2.2343765] ).first
|
35
|
+
=> [loc.city, loc.state].join(', ')
|
36
|
+
=> "Manchester, England"
|
37
|
+
|
38
|
+
IP address gecoding is supported using the hostip.info api. Just pass the IP as the
|
39
|
+
location parameter
|
40
|
+
|
41
|
+
>> loc = Drifter.geocode('1.2.3.4').first
|
42
|
+
=> <#Drifter::Location>
|
43
|
+
|
44
|
+
hostip.info only provides the city, country, lat and lng. If you need more info, you
|
45
|
+
can reverse geocode the result:
|
46
|
+
|
47
|
+
>> loc = Drifter.geocode('1.2.3.4').first
|
48
|
+
>> loc = Drifter.geocode(loc).first
|
49
|
+
>> loc.state_code
|
50
|
+
=> 'CA'
|
51
|
+
|
52
|
+
Google is the default geocoding provider and works out of the box. Yahoo's placefinder
|
53
|
+
is also supported but you'll need an api key (they call it an appid)
|
54
|
+
|
55
|
+
>> Drifter.default_geocoder = :yahoo
|
56
|
+
>> Drifter::Geocoders::Yahoo.api_key = 'my_key'
|
57
|
+
|
58
|
+
>> bh = Drifter.geocode("90210").first
|
59
|
+
=> <#Drifter::Location>
|
60
|
+
|
61
|
+
You can change the geocoder per request:
|
62
|
+
|
63
|
+
>> Drifter.geocode("springfield", :geocoder => :yahoo)
|
64
|
+
>> Drifter.geocode("springfield", :geocoder => :google)
|
65
|
+
|
66
|
+
Both Yahoo and Google return a lot more info than is held in Drifter::Location's standard
|
67
|
+
attributes. You can access the extra data using the data() method which returns a Hash
|
68
|
+
|
69
|
+
# using google as the provider:
|
70
|
+
>> london.data["geometry"]["location_type"]
|
71
|
+
=> "APPROXIMATE"
|
72
|
+
|
73
|
+
The key => value pairs in the data Hash are specific to each provider, so you'll have to
|
74
|
+
check their docs to see what's available. You can also modify the query sent to the
|
75
|
+
geocoder to customise the results. Any option other than :geocoder will be URL encoded
|
76
|
+
and sent as a query string parameter e.g. Yahoo's service returns a timezone if you pass a
|
77
|
+
'flags' parameter containing a 'T':
|
78
|
+
|
79
|
+
>> Drifter.default_geocoder = :yahoo
|
80
|
+
>> paris = Drifter.geocode("Paris", :flags => 'T').first
|
81
|
+
>> paris.data["timezone"]
|
82
|
+
=> "Europe/paris"
|
83
|
+
|
84
|
+
Finally, Drifter::Location objects have a distance_to() method
|
85
|
+
|
86
|
+
>> london.distance_to(bh)
|
87
|
+
=> 5438.60013996461
|
88
|
+
|
89
|
+
Distances are returned in miles by default. You can change this per request or change the default
|
90
|
+
|
91
|
+
>> Drifter.default_units = :km
|
92
|
+
>> london.distance_to(bh, :units => :miles)
|
93
|
+
|
94
|
+
Drifter.geocode() always returns an array if the request was processed successfully by the
|
95
|
+
geocoding service. An empty array indicates that the service returned no results.
|
96
|
+
|
97
|
+
If the geocoding service returns an error, Drifter.geocode() returns nil and Drifter.last_error()
|
98
|
+
returns a hash with the error :code and :message
|
99
|
+
|
100
|
+
=== License
|
101
|
+
|
102
|
+
MIT License. Copyright 2011 Ahmed Adam (http://github.com/ahmedrb)
|
data/Rakefile
ADDED
data/drifter.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "drifter/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "drifter"
|
7
|
+
s.version = Drifter::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Ahmed Adam"]
|
10
|
+
s.homepage = "http://github.com/ahmedrb/drifter"
|
11
|
+
s.summary = %q{Simple geocoding library for ruby}
|
12
|
+
|
13
|
+
s.rubyforge_project = "drifter"
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.add_dependency 'json', '~> 1.4.6'
|
21
|
+
end
|
data/lib/drifter.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'drifter/distance/haversine'
|
3
|
+
require 'drifter/geocoders'
|
4
|
+
require 'drifter/location'
|
5
|
+
|
6
|
+
module Drifter
|
7
|
+
|
8
|
+
@@default_geocoder = :google
|
9
|
+
@@default_units = :miles
|
10
|
+
@@last_error = nil
|
11
|
+
|
12
|
+
|
13
|
+
# returns the default geocoder
|
14
|
+
def self.default_geocoder
|
15
|
+
@@default_geocoder
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
# Sets the default geocoder. Supported values are :google or :yahoo
|
20
|
+
# If using :yahoo, you will also need to set your yahoo appid using
|
21
|
+
# Drifter::Geocoders::Yahoo.app_id=()
|
22
|
+
def self.default_geocoder=(value)
|
23
|
+
@@default_geocoder = value
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
# Returns the default units for distance calculations
|
28
|
+
def self.default_units
|
29
|
+
@@default_units
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
# Sets the default units for distance calculations.
|
34
|
+
# Supported values are :miles and :kms
|
35
|
+
def self.default_units=(value)
|
36
|
+
@@default_units=value
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
# Helper method to extract the lat and lng from an array or from any object
|
41
|
+
# that responds to lat() and lng(). Returns nil if neither of those apply
|
42
|
+
def self.extract_latlng(loc)
|
43
|
+
return loc.first, loc.last if loc.is_a?(Array) && loc.size == 2
|
44
|
+
return loc.lat, loc.lng if loc.respond_to?(:lat) && loc.respond_to?(:lng)
|
45
|
+
return nil
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
# Same as Drifter,extract_latlng() but raises ArgumentError on failure
|
50
|
+
def self.extract_latlng!(loc)
|
51
|
+
lat, lng = extract_latlng(loc)
|
52
|
+
return lat, lng if lat && lng
|
53
|
+
raise ArgumentError, "Could not extract lat and lng from #{loc.class.name} object"
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
# Accepts a string or a set of coordinates and returns an Array of Drifter::Location
|
58
|
+
# objects with the results of the geocoding request. If there is an error, this
|
59
|
+
# method returns nil and the error can be accessed via Drifter.last_error().
|
60
|
+
#
|
61
|
+
# You can over-ride the default geocoder using the params[:geocoder] option:
|
62
|
+
# Drifter.geocode("somewhere", :geocoder => :yahoo)
|
63
|
+
#
|
64
|
+
# You can perform reverse geocoding by passing a [lat, lng] array, or an object that
|
65
|
+
# responds to lat() and lng(). Any params besides :geocoder are url encoded
|
66
|
+
# and sent to the geocoder as query string parameters. This can be used to modify
|
67
|
+
# the results of the query. See the README for an example.
|
68
|
+
#
|
69
|
+
# if location is a string containing an IP address, the :geocoder value is ignored
|
70
|
+
# and the ip is geocoded using the hostip.info web service. This only returns a
|
71
|
+
# country, city, lat and lng so you could reverse geocode the result to get more info
|
72
|
+
def self.geocode(location, params={})
|
73
|
+
geocoder = params.delete(:geocoder) || default_geocoder
|
74
|
+
geocoder = :hostip if location.to_s =~ Drifter::Geocoders::HostIP::IP_PATTERN
|
75
|
+
geocoder = case geocoder
|
76
|
+
when :google then Drifter::Geocoders::Google
|
77
|
+
when :yahoo then Drifter::Geocoders::Yahoo
|
78
|
+
when :hostip then Drifter::Geocoders::HostIP
|
79
|
+
else raise ArgumentError, "Geocoder #{geocoder} not recognised"
|
80
|
+
end
|
81
|
+
results = geocoder.geocode(location, params)
|
82
|
+
@@last_error = geocoder.last_error
|
83
|
+
return results
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
# Returns a Hash containing error code and status from a failed geocoding request
|
88
|
+
def self.last_error
|
89
|
+
@@last_error
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Drifter
|
2
|
+
module Distance
|
3
|
+
class Haversine
|
4
|
+
|
5
|
+
EarthRadiusInMiles = 3956
|
6
|
+
EarthRadiusInKms = 6371
|
7
|
+
RAD_PER_DEG = 0.017453293 # PI/180
|
8
|
+
|
9
|
+
|
10
|
+
# this method is from Landon Cox's haversine.rb (GNU Affero GPL v3):
|
11
|
+
# http://www.esawdust.com/blog/gps/files/HaversineFormulaInRuby.html:
|
12
|
+
# http://www.esawdust.com (Landon Cox)
|
13
|
+
# http://www.esawdust.com/blog/businesscard/businesscard.html
|
14
|
+
def self.between(point1, point2, options={})
|
15
|
+
lat1, lon1 = Drifter.extract_latlng!(point1)
|
16
|
+
lat2, lon2 = Drifter.extract_latlng!(point2)
|
17
|
+
|
18
|
+
dlon = lon2 - lon1
|
19
|
+
dlat = lat2 - lat1
|
20
|
+
|
21
|
+
dlon_rad = dlon * RAD_PER_DEG
|
22
|
+
dlat_rad = dlat * RAD_PER_DEG
|
23
|
+
|
24
|
+
lat1_rad = lat1 * RAD_PER_DEG
|
25
|
+
lon1_rad = lon1 * RAD_PER_DEG
|
26
|
+
|
27
|
+
lat2_rad = lat2 * RAD_PER_DEG
|
28
|
+
lon2_rad = lon2 * RAD_PER_DEG
|
29
|
+
|
30
|
+
a = (Math.sin(dlat_rad/2))**2 + Math.cos(lat1_rad) * Math.cos(lat2_rad) * (Math.sin(dlon_rad/2))**2
|
31
|
+
c = 2 * Math.atan2( Math.sqrt(a), Math.sqrt(1-a))
|
32
|
+
|
33
|
+
units = options.delete(:units) || Drifter.default_units
|
34
|
+
return EarthRadiusInKms * c if units == :km
|
35
|
+
return EarthRadiusInMiles * c
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
# haversine sql based on http://code.google.com/apis/maps/articles/phpsqlsearch.html
|
40
|
+
# you will need to add 'select' and 'AS distance' if required
|
41
|
+
def self.to_postgresql(options)
|
42
|
+
origin = options[:origin]
|
43
|
+
lat, lng = Drifter.extract_latlng!(origin)
|
44
|
+
lat_column = options[:lat_column] || :lat
|
45
|
+
lng_column = options[:lng_column] || :lng
|
46
|
+
units = options[:units] || Drifter.default_units
|
47
|
+
multiplier = EarthRadiusInMiles
|
48
|
+
multiplier = EarthRadiusInKms if units == :km
|
49
|
+
|
50
|
+
postgres = <<-EOS
|
51
|
+
#{multiplier} * ACOS(
|
52
|
+
COS( RADIANS(#{lat}) ) *
|
53
|
+
COS( RADIANS( #{lat_column} ) ) *
|
54
|
+
COS( RADIANS( #{lng_column} ) -
|
55
|
+
RADIANS(#{lng}) ) +
|
56
|
+
SIN( RADIANS(#{lat}) ) *
|
57
|
+
SIN( RADIANS( #{lat_column} ) )
|
58
|
+
)
|
59
|
+
EOS
|
60
|
+
end
|
61
|
+
|
62
|
+
# postgresql code seems to work fine fo sql
|
63
|
+
def self.to_mysql(options)
|
64
|
+
to_postgresql(options)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'json'
|
3
|
+
require 'net/http'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
module Drifter
|
7
|
+
module Geocoders
|
8
|
+
|
9
|
+
# nodoc
|
10
|
+
class Base
|
11
|
+
|
12
|
+
@@last_error = nil # used by geocode()
|
13
|
+
|
14
|
+
|
15
|
+
# takes a location parameter and returns an Array of Drifter::Location objects
|
16
|
+
# with the results of the geocoding request. If the geocoder returns an error,
|
17
|
+
# this method should store the error in @@last_error and return nil.
|
18
|
+
def self.geocode(*args)
|
19
|
+
raise "Not implemented"
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
# Returns a Hash containing error information from the last geocoding request
|
24
|
+
# or nil if there was no error. Subclasses should set @@last_error to nil
|
25
|
+
# after every successful request
|
26
|
+
def self.last_error
|
27
|
+
@@last_error
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
# Returns a URI object used to make the call to the geocoding service.
|
32
|
+
# Subclasses should implement checks for required parameters. See geocoder/yahoo.rb
|
33
|
+
# for an example
|
34
|
+
def self.query_uri(options={})
|
35
|
+
raise "Not implemented"
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
|
42
|
+
# wrapper for Net::HTTP.get
|
43
|
+
def self.fetch(uri)
|
44
|
+
Net::HTTP.get(uri.host, uri.request_uri)
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
# Converts hash to a url enoded query string
|
49
|
+
def self.hash_to_query_string(hash)
|
50
|
+
qs = hash.collect{ |k,v| k.to_s + '=' + CGI::escape(v.to_s) }
|
51
|
+
qs.join('&')
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|