abuiles-geokit 1.6.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.bundle/config +2 -0
- data/.gitignore +4 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +19 -0
- data/History.txt +77 -0
- data/Manifest.txt +21 -0
- data/README.markdown +273 -0
- data/Rakefile +13 -0
- data/geokit.gemspec +24 -0
- data/lib/geokit.rb +55 -0
- data/lib/geokit/bounds.rb +95 -0
- data/lib/geokit/geo_loc.rb +115 -0
- data/lib/geokit/geocoders.rb +68 -0
- data/lib/geokit/geocoders/ca_geocoder.rb +54 -0
- data/lib/geokit/geocoders/geo_plugin_geocoder.rb +30 -0
- data/lib/geokit/geocoders/geocode_error.rb +7 -0
- data/lib/geokit/geocoders/geocoder.rb +75 -0
- data/lib/geokit/geocoders/geonames_geocoder.rb +53 -0
- data/lib/geokit/geocoders/google_geocoder.rb +145 -0
- data/lib/geokit/geocoders/google_premier_geocoder.rb +147 -0
- data/lib/geokit/geocoders/ip_geocoder.rb +76 -0
- data/lib/geokit/geocoders/multi_geocoder.rb +60 -0
- data/lib/geokit/geocoders/us_geocoder.rb +50 -0
- data/lib/geokit/geocoders/yahoo_geocoder.rb +49 -0
- data/lib/geokit/inflector.rb +39 -0
- data/lib/geokit/lat_lng.rb +112 -0
- data/lib/geokit/mappable.rb +210 -0
- data/lib/geokit/too_many_queries_error.rb +4 -0
- data/lib/geokit/version.rb +3 -0
- data/test/test_base_geocoder.rb +58 -0
- data/test/test_bounds.rb +97 -0
- data/test/test_ca_geocoder.rb +39 -0
- data/test/test_geoloc.rb +72 -0
- data/test/test_geoplugin_geocoder.rb +58 -0
- data/test/test_google_geocoder.rb +225 -0
- data/test/test_google_premier_geocoder.rb +88 -0
- data/test/test_google_reverse_geocoder.rb +47 -0
- data/test/test_inflector.rb +24 -0
- data/test/test_ipgeocoder.rb +109 -0
- data/test/test_latlng.rb +209 -0
- data/test/test_multi_geocoder.rb +91 -0
- data/test/test_multi_ip_geocoder.rb +36 -0
- data/test/test_us_geocoder.rb +54 -0
- data/test/test_yahoo_geocoder.rb +103 -0
- metadata +141 -0
data/lib/geokit.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
module Geokit
|
2
|
+
VERSION = '1.5.0'
|
3
|
+
|
4
|
+
# These defaults are used in Geokit::Mappable.distance_to and in acts_as_mappable
|
5
|
+
@@default_units = :miles
|
6
|
+
@@default_formula = :sphere
|
7
|
+
|
8
|
+
[:default_units, :default_formula].each do |sym|
|
9
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
10
|
+
def self.#{sym}
|
11
|
+
if defined?(#{sym.to_s.upcase})
|
12
|
+
#{sym.to_s.upcase}
|
13
|
+
else
|
14
|
+
@@#{sym}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.#{sym}=(obj)
|
19
|
+
@@#{sym} = obj
|
20
|
+
end
|
21
|
+
EOS
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'net/http'
|
26
|
+
require 'ipaddr'
|
27
|
+
require 'rexml/document'
|
28
|
+
require 'yaml'
|
29
|
+
require 'timeout'
|
30
|
+
require 'logger'
|
31
|
+
require 'active_support/core_ext/hash'
|
32
|
+
require 'active_support/core_ext/object/conversions'
|
33
|
+
require 'openssl'
|
34
|
+
require 'base64'
|
35
|
+
require 'json'
|
36
|
+
|
37
|
+
require 'geokit/too_many_queries_error'
|
38
|
+
require 'geokit/inflector'
|
39
|
+
require 'geokit/geocoders'
|
40
|
+
require 'geokit/mappable'
|
41
|
+
require 'geokit/lat_lng'
|
42
|
+
require 'geokit/geo_loc'
|
43
|
+
require 'geokit/bounds'
|
44
|
+
require 'geokit/geocoders/geocode_error'
|
45
|
+
require 'geokit/geocoders/geocoder'
|
46
|
+
|
47
|
+
require 'geokit/geocoders/ca_geocoder'
|
48
|
+
require 'geokit/geocoders/geo_plugin_geocoder'
|
49
|
+
require 'geokit/geocoders/geonames_geocoder'
|
50
|
+
require 'geokit/geocoders/google_geocoder'
|
51
|
+
require 'geokit/geocoders/google_premier_geocoder'
|
52
|
+
require 'geokit/geocoders/ip_geocoder'
|
53
|
+
require 'geokit/geocoders/multi_geocoder'
|
54
|
+
require 'geokit/geocoders/us_geocoder'
|
55
|
+
require 'geokit/geocoders/yahoo_geocoder'
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Geokit
|
2
|
+
# Bounds represents a rectangular bounds, defined by the SW and NE corners
|
3
|
+
class Bounds
|
4
|
+
# sw and ne are LatLng objects
|
5
|
+
attr_accessor :sw, :ne
|
6
|
+
|
7
|
+
# provide sw and ne to instantiate a new Bounds instance
|
8
|
+
def initialize(sw,ne)
|
9
|
+
raise ArgumentError if !(sw.is_a?(Geokit::LatLng) && ne.is_a?(Geokit::LatLng))
|
10
|
+
@sw,@ne=sw,ne
|
11
|
+
end
|
12
|
+
|
13
|
+
#returns the a single point which is the center of the rectangular bounds
|
14
|
+
def center
|
15
|
+
@sw.midpoint_to(@ne)
|
16
|
+
end
|
17
|
+
|
18
|
+
# a simple string representation:sw,ne
|
19
|
+
def to_s
|
20
|
+
"#{@sw.to_s},#{@ne.to_s}"
|
21
|
+
end
|
22
|
+
|
23
|
+
# a two-element array of two-element arrays: sw,ne
|
24
|
+
def to_a
|
25
|
+
[@sw.to_a, @ne.to_a]
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns true if the bounds contain the passed point.
|
29
|
+
# allows for bounds which cross the meridian
|
30
|
+
def contains?(point)
|
31
|
+
point=Geokit::LatLng.normalize(point)
|
32
|
+
res = point.lat > @sw.lat && point.lat < @ne.lat
|
33
|
+
if crosses_meridian?
|
34
|
+
res &= point.lng < @ne.lng || point.lng > @sw.lng
|
35
|
+
else
|
36
|
+
res &= point.lng < @ne.lng && point.lng > @sw.lng
|
37
|
+
end
|
38
|
+
res
|
39
|
+
end
|
40
|
+
|
41
|
+
# returns true if the bounds crosses the international dateline
|
42
|
+
def crosses_meridian?
|
43
|
+
@sw.lng > @ne.lng
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns true if the candidate object is logically equal. Logical equivalence
|
47
|
+
# is true if the lat and lng attributes are the same for both objects.
|
48
|
+
def ==(other)
|
49
|
+
other.is_a?(Bounds) ? self.sw == other.sw && self.ne == other.ne : false
|
50
|
+
end
|
51
|
+
|
52
|
+
# Equivalent to Google Maps API's .toSpan() method on GLatLng's.
|
53
|
+
#
|
54
|
+
# Returns a LatLng object, whose coordinates represent the size of a rectangle
|
55
|
+
# defined by these bounds.
|
56
|
+
def to_span
|
57
|
+
lat_span = (@ne.lat - @sw.lat).abs
|
58
|
+
lng_span = (crosses_meridian? ? 360 + @ne.lng - @sw.lng : @ne.lng - @sw.lng).abs
|
59
|
+
Geokit::LatLng.new(lat_span, lng_span)
|
60
|
+
end
|
61
|
+
|
62
|
+
class <<self
|
63
|
+
|
64
|
+
# returns an instance of bounds which completely encompases the given circle
|
65
|
+
def from_point_and_radius(point,radius,options={})
|
66
|
+
point=LatLng.normalize(point)
|
67
|
+
p0=point.endpoint(0,radius,options)
|
68
|
+
p90=point.endpoint(90,radius,options)
|
69
|
+
p180=point.endpoint(180,radius,options)
|
70
|
+
p270=point.endpoint(270,radius,options)
|
71
|
+
sw=Geokit::LatLng.new(p180.lat,p270.lng)
|
72
|
+
ne=Geokit::LatLng.new(p0.lat,p90.lng)
|
73
|
+
Geokit::Bounds.new(sw,ne)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Takes two main combinations of arguments to create a bounds:
|
77
|
+
# point,point (this is the only one which takes two arguments
|
78
|
+
# [point,point]
|
79
|
+
# . . . where a point is anything LatLng#normalize can handle (which is quite a lot)
|
80
|
+
#
|
81
|
+
# NOTE: everything combination is assumed to pass points in the order sw, ne
|
82
|
+
def normalize (thing,other=nil)
|
83
|
+
# maybe this will be simple -- an actual bounds object is passed, and we can all go home
|
84
|
+
return thing if thing.is_a? Bounds
|
85
|
+
|
86
|
+
# no? OK, if there's no "other," the thing better be a two-element array
|
87
|
+
thing,other=thing if !other && thing.is_a?(Array) && thing.size==2
|
88
|
+
|
89
|
+
# Now that we're set with a thing and another thing, let LatLng do the heavy lifting.
|
90
|
+
# Exceptions may be thrown
|
91
|
+
Bounds.new(Geokit::LatLng.normalize(thing),Geokit::LatLng.normalize(other))
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module Geokit
|
2
|
+
# This class encapsulates the result of a geocoding call.
|
3
|
+
# It's primary purpose is to homogenize the results of multiple
|
4
|
+
# geocoding providers. It also provides some additional functionality, such as
|
5
|
+
# the "full address" method for geocoders that do not provide a
|
6
|
+
# full address in their results (for example, Yahoo), and the "is_us" method.
|
7
|
+
#
|
8
|
+
# Some geocoders can return multple results. Geoloc can capture multiple results through
|
9
|
+
# its "all" method.
|
10
|
+
#
|
11
|
+
# For the geocoder setting the results, it would look something like this:
|
12
|
+
# geo=GeoLoc.new(first_result)
|
13
|
+
# geo.all.push(second_result)
|
14
|
+
# geo.all.push(third_result)
|
15
|
+
#
|
16
|
+
# Then, for the user of the result:
|
17
|
+
#
|
18
|
+
# puts geo.full_address # just like usual
|
19
|
+
# puts geo.all.size => 3 # there's three results total
|
20
|
+
# puts geo.all.first # all is just an array or additional geolocs,
|
21
|
+
# so do what you want with it
|
22
|
+
class GeoLoc < LatLng
|
23
|
+
|
24
|
+
# Location attributes. Full address is a concatenation of all values. For example:
|
25
|
+
# 100 Spear St, San Francisco, CA, 94101, US
|
26
|
+
attr_accessor :street_address, :city, :state, :zip, :country_code, :country, :full_address, :all, :district, :province
|
27
|
+
# Attributes set upon return from geocoding. Success will be true for successful
|
28
|
+
# geocode lookups. The provider will be set to the name of the providing geocoder.
|
29
|
+
# Finally, precision is an indicator of the accuracy of the geocoding.
|
30
|
+
attr_accessor :success, :provider, :precision, :suggested_bounds
|
31
|
+
# Street number and street name are extracted from the street address attribute.
|
32
|
+
attr_reader :street_number, :street_name
|
33
|
+
# accuracy is set for Yahoo and Google geocoders, it is a numeric value of the
|
34
|
+
# precision. see http://code.google.com/apis/maps/documentation/geocoding/#GeocodingAccuracy
|
35
|
+
attr_accessor :accuracy
|
36
|
+
|
37
|
+
# Constructor expects a hash of symbols to correspond with attributes.
|
38
|
+
def initialize(h={})
|
39
|
+
@all = [self]
|
40
|
+
|
41
|
+
@street_address=h[:street_address]
|
42
|
+
@city=h[:city]
|
43
|
+
@state=h[:state]
|
44
|
+
@zip=h[:zip]
|
45
|
+
@country_code=h[:country_code]
|
46
|
+
@province = h[:province]
|
47
|
+
@success=false
|
48
|
+
@precision='unknown'
|
49
|
+
@full_address=nil
|
50
|
+
super(h[:lat],h[:lng])
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns true if geocoded to the United States.
|
54
|
+
def is_us?
|
55
|
+
country_code == 'US'
|
56
|
+
end
|
57
|
+
|
58
|
+
def success?
|
59
|
+
success == true
|
60
|
+
end
|
61
|
+
|
62
|
+
# full_address is provided by google but not by yahoo. It is intended that the google
|
63
|
+
# geocoding method will provide the full address, whereas for yahoo it will be derived
|
64
|
+
# from the parts of the address we do have.
|
65
|
+
def full_address
|
66
|
+
@full_address ? @full_address : to_geocodeable_s
|
67
|
+
end
|
68
|
+
|
69
|
+
# Extracts the street number from the street address if the street address
|
70
|
+
# has a value.
|
71
|
+
def street_number
|
72
|
+
street_address[/(\d*)/] if street_address
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns the street name portion of the street address.
|
76
|
+
def street_name
|
77
|
+
street_address[street_number.length, street_address.length].strip if street_address
|
78
|
+
end
|
79
|
+
|
80
|
+
# gives you all the important fields as key-value pairs
|
81
|
+
def hash
|
82
|
+
res={}
|
83
|
+
[:success,:lat,:lng,:country_code,:city,:state,:zip,:street_address,:province,:district,:provider,:full_address,:is_us?,:ll,:precision].each { |s| res[s] = self.send(s.to_s) }
|
84
|
+
res
|
85
|
+
end
|
86
|
+
alias to_hash hash
|
87
|
+
|
88
|
+
# Sets the city after capitalizing each word within the city name.
|
89
|
+
def city=(city)
|
90
|
+
@city = Geokit::Inflector::titleize(city) if city
|
91
|
+
end
|
92
|
+
|
93
|
+
# Sets the street address after capitalizing each word within the street address.
|
94
|
+
def street_address=(address)
|
95
|
+
@street_address = Geokit::Inflector::titleize(address) if address
|
96
|
+
end
|
97
|
+
|
98
|
+
# Returns a comma-delimited string consisting of the street address, city, state,
|
99
|
+
# zip, and country code. Only includes those attributes that are non-blank.
|
100
|
+
def to_geocodeable_s
|
101
|
+
a=[street_address, district, city, province, state, zip, country_code].compact
|
102
|
+
a.delete_if { |e| !e || e == '' }
|
103
|
+
a.join(', ')
|
104
|
+
end
|
105
|
+
|
106
|
+
def to_yaml_properties
|
107
|
+
(instance_variables - ['@all']).sort
|
108
|
+
end
|
109
|
+
|
110
|
+
# Returns a string representation of the instance.
|
111
|
+
def to_s
|
112
|
+
"Provider: #{provider}\nStreet: #{street_address}\nCity: #{city}\nState: #{state}\nZip: #{zip}\nLatitude: #{lat}\nLongitude: #{lng}\nCountry: #{country_code}\nSuccess: #{success}"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Geokit
|
2
|
+
# Contains a range of geocoders:
|
3
|
+
#
|
4
|
+
# ### "regular" address geocoders
|
5
|
+
# * Yahoo Geocoder - requires an API key.
|
6
|
+
# * Geocoder.us - may require authentication if performing more than the free request limit.
|
7
|
+
# * Geocoder.ca - for Canada; may require authentication as well.
|
8
|
+
# * Geonames - a free geocoder
|
9
|
+
#
|
10
|
+
# ### address geocoders that also provide reverse geocoding
|
11
|
+
# * Google Geocoder - requires an API key.
|
12
|
+
#
|
13
|
+
# ### IP address geocoders
|
14
|
+
# * IP Geocoder - geocodes an IP address using hostip.info's web service.
|
15
|
+
# * Geoplugin.net -- another IP address geocoder
|
16
|
+
#
|
17
|
+
# ### The Multigeocoder
|
18
|
+
# * Multi Geocoder - provides failover for the physical location geocoders.
|
19
|
+
#
|
20
|
+
# Some of these geocoders require configuration. You don't have to provide it here. See the README.
|
21
|
+
module Geocoders
|
22
|
+
@@proxy_addr = nil
|
23
|
+
@@proxy_port = nil
|
24
|
+
@@proxy_user = nil
|
25
|
+
@@proxy_pass = nil
|
26
|
+
@@request_timeout = nil
|
27
|
+
@@yahoo = 'REPLACE_WITH_YOUR_YAHOO_KEY'
|
28
|
+
@@google = 'REPLACE_WITH_YOUR_GOOGLE_KEY'
|
29
|
+
@@google_client = 'REPLACE_WITH_YOUR_GOOGLE_CLIENT'
|
30
|
+
@@google_channel = 'REPLACE_WITH_YOUR_GOOGLE_CHANNEL'
|
31
|
+
@@geocoder_us = false
|
32
|
+
@@geocoder_ca = false
|
33
|
+
@@geonames = false
|
34
|
+
@@provider_order = [:google,:us]
|
35
|
+
@@ip_provider_order = [:geo_plugin,:ip]
|
36
|
+
@@logger=Logger.new(STDOUT)
|
37
|
+
@@logger.level=Logger::INFO
|
38
|
+
@@domain = nil
|
39
|
+
|
40
|
+
def self.__define_accessors
|
41
|
+
class_variables.each do |v|
|
42
|
+
sym = v.to_s.delete("@").to_sym
|
43
|
+
unless self.respond_to? sym
|
44
|
+
module_eval <<-EOS, __FILE__, __LINE__
|
45
|
+
def self.#{sym}
|
46
|
+
value = if defined?(#{sym.to_s.upcase})
|
47
|
+
#{sym.to_s.upcase}
|
48
|
+
else
|
49
|
+
@@#{sym}
|
50
|
+
end
|
51
|
+
if value.is_a?(Hash)
|
52
|
+
value = (self.domain.nil? ? nil : value[self.domain]) || value.values.first
|
53
|
+
end
|
54
|
+
value
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.#{sym}=(obj)
|
58
|
+
@@#{sym} = obj
|
59
|
+
end
|
60
|
+
EOS
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
__define_accessors
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Geokit
|
2
|
+
module Geocoders
|
3
|
+
# Geocoder CA geocoder implementation. Requires the Geokit::Geocoders::GEOCODER_CA variable to
|
4
|
+
# contain true or false based upon whether authentication is to occur. Conforms to the
|
5
|
+
# interface set by the Geocoder class.
|
6
|
+
#
|
7
|
+
# Returns a response like:
|
8
|
+
# <?xml version="1.0" encoding="UTF-8" ?>
|
9
|
+
# <geodata>
|
10
|
+
# <latt>49.243086</latt>
|
11
|
+
# <longt>-123.153684</longt>
|
12
|
+
# </geodata>
|
13
|
+
class CaGeocoder < Geocoder
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
# Template method which does the geocode lookup.
|
18
|
+
def self.do_geocode(address, options = {})
|
19
|
+
raise ArgumentError('Geocoder.ca requires a GeoLoc argument') unless address.is_a?(GeoLoc)
|
20
|
+
url = construct_request(address)
|
21
|
+
res = self.call_geocoder_service(url)
|
22
|
+
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
23
|
+
xml = res.body
|
24
|
+
logger.debug "Geocoder.ca geocoding. Address: #{address}. Result: #{xml}"
|
25
|
+
# Parse the document.
|
26
|
+
doc = REXML::Document.new(xml)
|
27
|
+
address.lat = doc.elements['//latt'].text
|
28
|
+
address.lng = doc.elements['//longt'].text
|
29
|
+
address.success = true
|
30
|
+
return address
|
31
|
+
rescue
|
32
|
+
logger.error "Caught an error during Geocoder.ca geocoding call: "+$!
|
33
|
+
return GeoLoc.new
|
34
|
+
end
|
35
|
+
|
36
|
+
# Formats the request in the format acceptable by the CA geocoder.
|
37
|
+
def self.construct_request(location)
|
38
|
+
url = ""
|
39
|
+
url += add_ampersand(url) + "stno=#{location.street_number}" if location.street_address
|
40
|
+
url += add_ampersand(url) + "addresst=#{Geokit::Inflector::url_escape(location.street_name)}" if location.street_address
|
41
|
+
url += add_ampersand(url) + "city=#{Geokit::Inflector::url_escape(location.city)}" if location.city
|
42
|
+
url += add_ampersand(url) + "prov=#{location.state}" if location.state
|
43
|
+
url += add_ampersand(url) + "postal=#{location.zip}" if location.zip
|
44
|
+
url += add_ampersand(url) + "auth=#{Geokit::Geocoders::geocoder_ca}" if Geokit::Geocoders::geocoder_ca
|
45
|
+
url += add_ampersand(url) + "geoit=xml"
|
46
|
+
'http://geocoder.ca/?' + url
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.add_ampersand(url)
|
50
|
+
url && url.length > 0 ? "&" : ""
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,30 @@
|
|
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
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Geokit
|
2
|
+
module Geocoders
|
3
|
+
|
4
|
+
|
5
|
+
# -------------------------------------------------------------------------------------------
|
6
|
+
# Geocoder Base class -- every geocoder should inherit from this
|
7
|
+
# -------------------------------------------------------------------------------------------
|
8
|
+
|
9
|
+
# The Geocoder base class which defines the interface to be used by all
|
10
|
+
# other geocoders.
|
11
|
+
class Geocoder
|
12
|
+
# Main method which calls the do_geocode template method which subclasses
|
13
|
+
# are responsible for implementing. Returns a populated GeoLoc or an
|
14
|
+
# empty one with a failed success code.
|
15
|
+
def self.geocode(address, options = {})
|
16
|
+
res = do_geocode(address, options)
|
17
|
+
return res.nil? ? GeoLoc.new : res
|
18
|
+
end
|
19
|
+
# Main method which calls the do_reverse_geocode template method which subclasses
|
20
|
+
# are responsible for implementing. Returns a populated GeoLoc or an
|
21
|
+
# empty one with a failed success code.
|
22
|
+
def self.reverse_geocode(latlng)
|
23
|
+
res = do_reverse_geocode(latlng)
|
24
|
+
return res.success? ? res : GeoLoc.new
|
25
|
+
end
|
26
|
+
|
27
|
+
# Call the geocoder service using the timeout if configured.
|
28
|
+
def self.call_geocoder_service(url)
|
29
|
+
Timeout::timeout(Geokit::Geocoders::request_timeout) { return self.do_get(url) } if Geokit::Geocoders::request_timeout
|
30
|
+
return self.do_get(url)
|
31
|
+
rescue TimeoutError
|
32
|
+
return nil
|
33
|
+
end
|
34
|
+
|
35
|
+
# Not all geocoders can do reverse geocoding. So, unless the subclass explicitly overrides this method,
|
36
|
+
# a call to reverse_geocode will return an empty GeoLoc. If you happen to be using MultiGeocoder,
|
37
|
+
# this will cause it to failover to the next geocoder, which will hopefully be one which supports reverse geocoding.
|
38
|
+
def self.do_reverse_geocode(latlng)
|
39
|
+
return GeoLoc.new
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def self.logger()
|
45
|
+
Geokit::Geocoders::logger
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
# Wraps the geocoder call around a proxy if necessary.
|
51
|
+
def self.do_get(url)
|
52
|
+
uri = URI.parse(url)
|
53
|
+
req = Net::HTTP::Get.new(url)
|
54
|
+
req.basic_auth(uri.user, uri.password) if uri.userinfo
|
55
|
+
res = Net::HTTP::Proxy(Geokit::Geocoders::proxy_addr,
|
56
|
+
Geokit::Geocoders::proxy_port,
|
57
|
+
Geokit::Geocoders::proxy_user,
|
58
|
+
Geokit::Geocoders::proxy_pass).start(uri.host, uri.port) { |http| http.get(uri.path + "?" + uri.query) }
|
59
|
+
return res
|
60
|
+
end
|
61
|
+
|
62
|
+
# Adds subclass' geocode method making it conveniently available through
|
63
|
+
# the base class.
|
64
|
+
def self.inherited(clazz)
|
65
|
+
class_name = clazz.name.split('::').last
|
66
|
+
src = <<-END_SRC
|
67
|
+
def self.#{Geokit::Inflector.underscore(class_name)}(address, options = {})
|
68
|
+
#{class_name}.geocode(address, options)
|
69
|
+
end
|
70
|
+
END_SRC
|
71
|
+
class_eval(src)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|