drifter 0.1.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.
- 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
|