andre-geokit 1.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/History.txt +5 -0
- data/Manifest.txt +20 -0
- data/README.markdown +66 -0
- data/Rakefile +20 -0
- data/lib/geocoders.rb +386 -0
- data/lib/geokit.rb +30 -0
- data/lib/mappable.rb +432 -0
- data/test/test_base_geocoder.rb +56 -0
- data/test/test_bounds.rb +74 -0
- data/test/test_ca_geocoder.rb +41 -0
- data/test/test_geoloc.rb +49 -0
- data/test/test_google_geocoder.rb +88 -0
- data/test/test_latlng.rb +111 -0
- data/test/test_multi_geocoder.rb +44 -0
- data/test/test_us_geocoder.rb +47 -0
- data/test/test_yahoo_geocoder.rb +87 -0
- metadata +88 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
.loadpath
|
2
|
+
.project
|
3
|
+
History.txt
|
4
|
+
Manifest.txt
|
5
|
+
README.markdown
|
6
|
+
Rakefile
|
7
|
+
geokit.gemspec
|
8
|
+
lib/geocoders.rb
|
9
|
+
lib/geokit.rb
|
10
|
+
lib/mappable.rb
|
11
|
+
test/test_base_geocoder.rb
|
12
|
+
test/test_bounds.rb
|
13
|
+
test/test_ca_geocoder.rb
|
14
|
+
test/test_geoloc.rb
|
15
|
+
test/test_google_geocoder.rb
|
16
|
+
test/test_ipgeocoder.rb
|
17
|
+
test/test_latlng.rb
|
18
|
+
test/test_multi_geocoder.rb
|
19
|
+
test/test_us_geocoder.rb
|
20
|
+
test/test_yahoo_geocoder.rb
|
data/README.markdown
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# Geokit gem
|
2
|
+
|
3
|
+
[http://geokit.rubyforge.org](http://geokit.rubyforge.org)
|
4
|
+
|
5
|
+
## DESCRIPTION:
|
6
|
+
|
7
|
+
The Geokit gem provides the following:
|
8
|
+
|
9
|
+
* Distance calculations between two points on the earth. Calculate the distance in miles or KM, with all the trigonometry abstracted away by GeoKit.
|
10
|
+
* Geocoding from multiple providers. It currently supports Google, Yahoo, Geocoder.us, and Geocoder.ca geocoders, and it provides a uniform response structure from all of them. It also provides a fail-over mechanism, in case your input fails to geocode in one service.
|
11
|
+
|
12
|
+
Combine this with gem with the geokit-rails plugin to get location-based finders for your Rails app. Plugins for other web frameworks and ORMs will provide similar functionality.
|
13
|
+
|
14
|
+
|
15
|
+
## FEATURES/PROBLEMS:
|
16
|
+
|
17
|
+
* none currently
|
18
|
+
|
19
|
+
## SYNOPSIS:
|
20
|
+
|
21
|
+
irb> require 'rubygems'
|
22
|
+
irb> require 'geokit'
|
23
|
+
irb> a=Geokit::Geocoders::YahooGeocoder.geocode '140 Market St, San Francisco, CA'
|
24
|
+
irb> a.ll
|
25
|
+
=> 37.79363,-122.396116
|
26
|
+
irb> b=Geokit::Geocoders::YahooGeocoder.geocode '789 Geary St, San Francisco, CA'
|
27
|
+
irb> b.ll
|
28
|
+
=> 37.786217,-122.41619
|
29
|
+
irb> a.distance_to(b)
|
30
|
+
=> 1.21120007413626
|
31
|
+
irb> a.heading_to(b)
|
32
|
+
=> 244.959832435678
|
33
|
+
|
34
|
+
|
35
|
+
## REQUIREMENTS:
|
36
|
+
|
37
|
+
|
38
|
+
## INSTALL:
|
39
|
+
|
40
|
+
* gem sources -a http://gems.github.com
|
41
|
+
* sudo gem install
|
42
|
+
|
43
|
+
## LICENSE:
|
44
|
+
|
45
|
+
(The MIT License)
|
46
|
+
|
47
|
+
Copyright (c) 2007-2008 Andre Lewis and Bill Eisenhauer
|
48
|
+
|
49
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
50
|
+
a copy of this software and associated documentation files (the
|
51
|
+
'Software'), to deal in the Software without restriction, including
|
52
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
53
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
54
|
+
permit persons to whom the Software is furnished to do so, subject to
|
55
|
+
the following conditions:
|
56
|
+
|
57
|
+
The above copyright notice and this permission notice shall be
|
58
|
+
included in all copies or substantial portions of the Software.
|
59
|
+
|
60
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
61
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
62
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
63
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
64
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
65
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
66
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hoe'
|
5
|
+
require './lib/geokit.rb'
|
6
|
+
|
7
|
+
Hoe.new('Geokit', Geokit::VERSION) do |p|
|
8
|
+
# p.rubyforge_name = 'Geokitx' # if different than lowercase project name
|
9
|
+
p.developer('Andre Lewis and Bill Eisenhauer', 'andre@earthcode.com / bill_eisenhauer@yahoo.com')
|
10
|
+
end
|
11
|
+
|
12
|
+
task :generate_gemspec do
|
13
|
+
system "rake debug_gem | grep -v \"(in \" > `basename \\`pwd\\``.gemspec"
|
14
|
+
end
|
15
|
+
|
16
|
+
task :update_manifest do
|
17
|
+
system "touch Manifest.txt; rake check_manifest | grep -v \"(in \" | patch"
|
18
|
+
end
|
19
|
+
|
20
|
+
# vim: syntax=Ruby
|
data/lib/geocoders.rb
ADDED
@@ -0,0 +1,386 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'rexml/document'
|
3
|
+
require 'yaml'
|
4
|
+
require 'timeout'
|
5
|
+
require 'logger'
|
6
|
+
|
7
|
+
module Geokit
|
8
|
+
module Inflector
|
9
|
+
|
10
|
+
extend self
|
11
|
+
|
12
|
+
def titleize(word)
|
13
|
+
humanize(underscore(word)).gsub(/\b([a-z])/) { $1.capitalize }
|
14
|
+
end
|
15
|
+
|
16
|
+
def underscore(camel_cased_word)
|
17
|
+
camel_cased_word.to_s.gsub(/::/, '/').
|
18
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
19
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
20
|
+
tr("-", "_").
|
21
|
+
downcase
|
22
|
+
end
|
23
|
+
|
24
|
+
def humanize(lower_case_and_underscored_word)
|
25
|
+
lower_case_and_underscored_word.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
|
26
|
+
end
|
27
|
+
|
28
|
+
def snake_case(s)
|
29
|
+
return s.downcase if s =~ /^[A-Z]+$/
|
30
|
+
s.gsub(/([A-Z]+)(?=[A-Z][a-z]?)|\B[A-Z]/, '_\&') =~ /_*(.*)/
|
31
|
+
return $+.downcase
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
def url_escape(s)
|
36
|
+
s.gsub(/([^ a-zA-Z0-9_.-]+)/n) do
|
37
|
+
'%' + $1.unpack('H2' * $1.size).join('%').upcase
|
38
|
+
end.tr(' ', '+')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
# Contains a set of geocoders which can be used independently if desired. The list contains:
|
42
|
+
#
|
43
|
+
# * Google Geocoder - requires an API key.
|
44
|
+
# * Yahoo Geocoder - requires an API key.
|
45
|
+
# * Geocoder.us - may require authentication if performing more than the free request limit.
|
46
|
+
# * Geocoder.ca - for Canada; may require authentication as well.
|
47
|
+
# * IP Geocoder - geocodes an IP address using hostip.info's web service.
|
48
|
+
# * Multi Geocoder - provides failover for the physical location geocoders.
|
49
|
+
#
|
50
|
+
# Some configuration is required for these geocoders and can be located in the environment
|
51
|
+
# configuration files.
|
52
|
+
module Geocoders
|
53
|
+
@@proxy_addr = nil
|
54
|
+
@@proxy_port = nil
|
55
|
+
@@proxy_user = nil
|
56
|
+
@@proxy_pass = nil
|
57
|
+
@@timeout = nil
|
58
|
+
@@yahoo = 'REPLACE_WITH_YOUR_YAHOO_KEY'
|
59
|
+
@@google = 'REPLACE_WITH_YOUR_GOOGLE_KEY'
|
60
|
+
@@geocoder_us = false
|
61
|
+
@@geocoder_ca = false
|
62
|
+
@@provider_order = [:google,:us]
|
63
|
+
@@logger=Logger.new(STDOUT)
|
64
|
+
@@logger.level=Logger::INFO
|
65
|
+
|
66
|
+
[:yahoo, :google, :geocoder_us, :geocoder_ca, :provider_order, :timeout,
|
67
|
+
:proxy_addr, :proxy_port, :proxy_user, :proxy_pass,:logger].each do |sym|
|
68
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
69
|
+
def self.#{sym}
|
70
|
+
if defined?(#{sym.to_s.upcase})
|
71
|
+
#{sym.to_s.upcase}
|
72
|
+
else
|
73
|
+
@@#{sym}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.#{sym}=(obj)
|
78
|
+
@@#{sym} = obj
|
79
|
+
end
|
80
|
+
EOS
|
81
|
+
end
|
82
|
+
|
83
|
+
# Error which is thrown in the event a geocoding error occurs.
|
84
|
+
class GeocodeError < StandardError; end
|
85
|
+
|
86
|
+
# The Geocoder base class which defines the interface to be used by all
|
87
|
+
# other geocoders.
|
88
|
+
class Geocoder
|
89
|
+
# Main method which calls the do_geocode template method which subclasses
|
90
|
+
# are responsible for implementing. Returns a populated GeoLoc or an
|
91
|
+
# empty one with a failed success code.
|
92
|
+
def self.geocode(address)
|
93
|
+
res = do_geocode(address)
|
94
|
+
return res.success ? res : GeoLoc.new
|
95
|
+
end
|
96
|
+
|
97
|
+
# Call the geocoder service using the timeout if configured.
|
98
|
+
def self.call_geocoder_service(url)
|
99
|
+
timeout(Geokit::Geocoders::timeout) { return self.do_get(url) } if Geokit::Geocoders::timeout
|
100
|
+
return self.do_get(url)
|
101
|
+
rescue TimeoutError
|
102
|
+
return nil
|
103
|
+
end
|
104
|
+
|
105
|
+
protected
|
106
|
+
|
107
|
+
def self.logger()
|
108
|
+
Geokit::Geocoders::logger
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
# Wraps the geocoder call around a proxy if necessary.
|
114
|
+
def self.do_get(url)
|
115
|
+
return Net::HTTP::Proxy(Geokit::Geocoders::proxy_addr, Geokit::Geocoders::proxy_port,
|
116
|
+
Geokit::Geocoders::proxy_user, Geokit::Geocoders::proxy_pass).get_response(URI.parse(url))
|
117
|
+
end
|
118
|
+
|
119
|
+
# Adds subclass' geocode method making it conveniently available through
|
120
|
+
# the base class.
|
121
|
+
def self.inherited(clazz)
|
122
|
+
class_name = clazz.name.split('::').last
|
123
|
+
src = <<-END_SRC
|
124
|
+
def self.#{Geokit::Inflector.underscore(class_name)}(address)
|
125
|
+
#{class_name}.geocode(address)
|
126
|
+
end
|
127
|
+
END_SRC
|
128
|
+
class_eval(src)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Geocoder CA geocoder implementation. Requires the Geokit::Geocoders::GEOCODER_CA variable to
|
133
|
+
# contain true or false based upon whether authentication is to occur. Conforms to the
|
134
|
+
# interface set by the Geocoder class.
|
135
|
+
#
|
136
|
+
# Returns a response like:
|
137
|
+
# <?xml version="1.0" encoding="UTF-8" ?>
|
138
|
+
# <geodata>
|
139
|
+
# <latt>49.243086</latt>
|
140
|
+
# <longt>-123.153684</longt>
|
141
|
+
# </geodata>
|
142
|
+
class CaGeocoder < Geocoder
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
# Template method which does the geocode lookup.
|
147
|
+
def self.do_geocode(address)
|
148
|
+
raise ArgumentError('Geocoder.ca requires a GeoLoc argument') unless address.is_a?(GeoLoc)
|
149
|
+
url = construct_request(address)
|
150
|
+
res = self.call_geocoder_service(url)
|
151
|
+
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
152
|
+
xml = res.body
|
153
|
+
logger.debug "Geocoder.ca geocoding. Address: #{address}. Result: #{xml}"
|
154
|
+
# Parse the document.
|
155
|
+
doc = REXML::Document.new(xml)
|
156
|
+
address.lat = doc.elements['//latt'].text
|
157
|
+
address.lng = doc.elements['//longt'].text
|
158
|
+
address.success = true
|
159
|
+
return address
|
160
|
+
rescue
|
161
|
+
logger.error "Caught an error during Geocoder.ca geocoding call: "+$!
|
162
|
+
return GeoLoc.new
|
163
|
+
end
|
164
|
+
|
165
|
+
# Formats the request in the format acceptable by the CA geocoder.
|
166
|
+
def self.construct_request(location)
|
167
|
+
url = ""
|
168
|
+
url += add_ampersand(url) + "stno=#{location.street_number}" if location.street_address
|
169
|
+
url += add_ampersand(url) + "addresst=#{Geokit::Inflector::url_escape(location.street_name)}" if location.street_address
|
170
|
+
url += add_ampersand(url) + "city=#{Geokit::Inflector::url_escape(location.city)}" if location.city
|
171
|
+
url += add_ampersand(url) + "prov=#{location.state}" if location.state
|
172
|
+
url += add_ampersand(url) + "postal=#{location.zip}" if location.zip
|
173
|
+
url += add_ampersand(url) + "auth=#{Geokit::Geocoders::geocoder_ca}" if Geokit::Geocoders::geocoder_ca
|
174
|
+
url += add_ampersand(url) + "geoit=xml"
|
175
|
+
'http://geocoder.ca/?' + url
|
176
|
+
end
|
177
|
+
|
178
|
+
def self.add_ampersand(url)
|
179
|
+
url && url.length > 0 ? "&" : ""
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Google geocoder implementation. Requires the Geokit::Geocoders::GOOGLE variable to
|
184
|
+
# contain a Google API key. Conforms to the interface set by the Geocoder class.
|
185
|
+
class GoogleGeocoder < Geocoder
|
186
|
+
|
187
|
+
private
|
188
|
+
|
189
|
+
# Template method which does the geocode lookup.
|
190
|
+
def self.do_geocode(address)
|
191
|
+
address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
|
192
|
+
res = self.call_geocoder_service("http://maps.google.com/maps/geo?q=#{Geokit::Inflector::url_escape(address_str)}&output=xml&key=#{Geokit::Geocoders::google}&oe=utf-8")
|
193
|
+
# res = Net::HTTP.get_response(URI.parse("http://maps.google.com/maps/geo?q=#{Geokit::Inflector::url_escape(address_str)}&output=xml&key=#{Geokit::Geocoders::google}&oe=utf-8"))
|
194
|
+
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
195
|
+
xml=res.body
|
196
|
+
logger.debug "Google geocoding. Address: #{address}. Result: #{xml}"
|
197
|
+
doc=REXML::Document.new(xml)
|
198
|
+
|
199
|
+
if doc.elements['//kml/Response/Status/code'].text == '200'
|
200
|
+
res = GeoLoc.new
|
201
|
+
coordinates=doc.elements['//coordinates'].text.to_s.split(',')
|
202
|
+
|
203
|
+
#basics
|
204
|
+
res.lat=coordinates[1]
|
205
|
+
res.lng=coordinates[0]
|
206
|
+
res.country_code=doc.elements['//CountryNameCode'].text
|
207
|
+
res.provider='google'
|
208
|
+
|
209
|
+
#extended -- false if not not available
|
210
|
+
res.city = doc.elements['//LocalityName'].text if doc.elements['//LocalityName']
|
211
|
+
res.state = doc.elements['//AdministrativeAreaName'].text if doc.elements['//AdministrativeAreaName']
|
212
|
+
res.full_address = doc.elements['//address'].text if doc.elements['//address'] # google provides it
|
213
|
+
res.zip = doc.elements['//PostalCodeNumber'].text if doc.elements['//PostalCodeNumber']
|
214
|
+
res.street_address = doc.elements['//ThoroughfareName'].text if doc.elements['//ThoroughfareName']
|
215
|
+
# Translate accuracy into Yahoo-style token address, street, zip, zip+4, city, state, country
|
216
|
+
# For Google, 1=low accuracy, 8=high accuracy
|
217
|
+
# old way -- address_details=doc.elements['//AddressDetails','urn:oasis:names:tc:ciq:xsdschema:xAL:2.0']
|
218
|
+
address_details=doc.elements['//*[local-name() = "AddressDetails"]']
|
219
|
+
accuracy = address_details ? address_details.attributes['Accuracy'].to_i : 0
|
220
|
+
res.precision=%w{unknown country state state city zip zip+4 street address}[accuracy]
|
221
|
+
res.success=true
|
222
|
+
|
223
|
+
return res
|
224
|
+
else
|
225
|
+
logger.info "Google was unable to geocode address: "+address
|
226
|
+
return GeoLoc.new
|
227
|
+
end
|
228
|
+
|
229
|
+
rescue
|
230
|
+
logger.error "Caught an error during Google geocoding call: "+$!
|
231
|
+
return GeoLoc.new
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
# Provides geocoding based upon an IP address. The underlying web service is a hostip.info
|
236
|
+
# which sources their data through a combination of publicly available information as well
|
237
|
+
# as community contributions.
|
238
|
+
class IpGeocoder < Geocoder
|
239
|
+
|
240
|
+
private
|
241
|
+
|
242
|
+
# Given an IP address, returns a GeoLoc instance which contains latitude,
|
243
|
+
# longitude, city, and country code. Sets the success attribute to false if the ip
|
244
|
+
# parameter does not match an ip address.
|
245
|
+
def self.do_geocode(ip)
|
246
|
+
return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
|
247
|
+
url = "http://api.hostip.info/get_html.php?ip=#{ip}&position=true"
|
248
|
+
response = self.call_geocoder_service(url)
|
249
|
+
response.is_a?(Net::HTTPSuccess) ? parse_body(response.body) : GeoLoc.new
|
250
|
+
rescue
|
251
|
+
logger.error "Caught an error during HostIp geocoding call: "+$!
|
252
|
+
return GeoLoc.new
|
253
|
+
end
|
254
|
+
|
255
|
+
# Converts the body to YAML since its in the form of:
|
256
|
+
#
|
257
|
+
# Country: UNITED STATES (US)
|
258
|
+
# City: Sugar Grove, IL
|
259
|
+
# Latitude: 41.7696
|
260
|
+
# Longitude: -88.4588
|
261
|
+
#
|
262
|
+
# then instantiates a GeoLoc instance to populate with location data.
|
263
|
+
def self.parse_body(body) # :nodoc:
|
264
|
+
yaml = YAML.load(body)
|
265
|
+
res = GeoLoc.new
|
266
|
+
res.provider = 'hostip'
|
267
|
+
res.city, res.state = yaml['City'].split(', ')
|
268
|
+
country, res.country_code = yaml['Country'].split(' (')
|
269
|
+
res.lat = yaml['Latitude']
|
270
|
+
res.lng = yaml['Longitude']
|
271
|
+
res.country_code.chop!
|
272
|
+
res.success = res.city != "(Private Address)"
|
273
|
+
res
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
# Geocoder Us geocoder implementation. Requires the Geokit::Geocoders::GEOCODER_US variable to
|
278
|
+
# contain true or false based upon whether authentication is to occur. Conforms to the
|
279
|
+
# interface set by the Geocoder class.
|
280
|
+
class UsGeocoder < Geocoder
|
281
|
+
|
282
|
+
private
|
283
|
+
|
284
|
+
# For now, the geocoder_method will only geocode full addresses -- not zips or cities in isolation
|
285
|
+
def self.do_geocode(address)
|
286
|
+
address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
|
287
|
+
url = "http://"+(Geokit::Geocoders::geocoder_us || '')+"geocoder.us/service/csv/geocode?address=#{Geokit::Inflector::url_escape(address_str)}"
|
288
|
+
res = self.call_geocoder_service(url)
|
289
|
+
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
290
|
+
data = res.body
|
291
|
+
logger.debug "Geocoder.us geocoding. Address: #{address}. Result: #{data}"
|
292
|
+
array = data.chomp.split(',')
|
293
|
+
|
294
|
+
if array.length == 6
|
295
|
+
res=GeoLoc.new
|
296
|
+
res.lat,res.lng,res.street_address,res.city,res.state,res.zip=array
|
297
|
+
res.country_code='US'
|
298
|
+
res.success=true
|
299
|
+
return res
|
300
|
+
else
|
301
|
+
logger.info "geocoder.us was unable to geocode address: "+address
|
302
|
+
return GeoLoc.new
|
303
|
+
end
|
304
|
+
rescue
|
305
|
+
logger.error "Caught an error during geocoder.us geocoding call: "+$!
|
306
|
+
return GeoLoc.new
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
# Yahoo geocoder implementation. Requires the Geokit::Geocoders::YAHOO variable to
|
311
|
+
# contain a Yahoo API key. Conforms to the interface set by the Geocoder class.
|
312
|
+
class YahooGeocoder < Geocoder
|
313
|
+
|
314
|
+
private
|
315
|
+
|
316
|
+
# Template method which does the geocode lookup.
|
317
|
+
def self.do_geocode(address)
|
318
|
+
address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
|
319
|
+
url="http://api.local.yahoo.com/MapsService/V1/geocode?appid=#{Geokit::Geocoders::yahoo}&location=#{Geokit::Inflector::url_escape(address_str)}"
|
320
|
+
res = self.call_geocoder_service(url)
|
321
|
+
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
322
|
+
xml = res.body
|
323
|
+
doc = REXML::Document.new(xml)
|
324
|
+
logger.debug "Yahoo geocoding. Address: #{address}. Result: #{xml}"
|
325
|
+
|
326
|
+
if doc.elements['//ResultSet']
|
327
|
+
res=GeoLoc.new
|
328
|
+
|
329
|
+
#basic
|
330
|
+
res.lat=doc.elements['//Latitude'].text
|
331
|
+
res.lng=doc.elements['//Longitude'].text
|
332
|
+
res.country_code=doc.elements['//Country'].text
|
333
|
+
res.provider='yahoo'
|
334
|
+
|
335
|
+
#extended - false if not available
|
336
|
+
res.city=doc.elements['//City'].text if doc.elements['//City'] && doc.elements['//City'].text != nil
|
337
|
+
res.state=doc.elements['//State'].text if doc.elements['//State'] && doc.elements['//State'].text != nil
|
338
|
+
res.zip=doc.elements['//Zip'].text if doc.elements['//Zip'] && doc.elements['//Zip'].text != nil
|
339
|
+
res.street_address=doc.elements['//Address'].text if doc.elements['//Address'] && doc.elements['//Address'].text != nil
|
340
|
+
res.precision=doc.elements['//Result'].attributes['precision'] if doc.elements['//Result']
|
341
|
+
res.success=true
|
342
|
+
return res
|
343
|
+
else
|
344
|
+
logger.info "Yahoo was unable to geocode address: "+address
|
345
|
+
return GeoLoc.new
|
346
|
+
end
|
347
|
+
|
348
|
+
rescue
|
349
|
+
logger.info "Caught an error during Yahoo geocoding call: "+$!
|
350
|
+
return GeoLoc.new
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
# Provides methods to geocode with a variety of geocoding service providers, plus failover
|
355
|
+
# among providers in the order you configure.
|
356
|
+
#
|
357
|
+
# Goal:
|
358
|
+
# - homogenize the results of multiple geocoders
|
359
|
+
#
|
360
|
+
# Limitations:
|
361
|
+
# - currently only provides the first result. Sometimes geocoders will return multiple results.
|
362
|
+
# - currently discards the "accuracy" component of the geocoding calls
|
363
|
+
class MultiGeocoder < Geocoder
|
364
|
+
private
|
365
|
+
|
366
|
+
# This method will call one or more geocoders in the order specified in the
|
367
|
+
# configuration until one of the geocoders work.
|
368
|
+
#
|
369
|
+
# The failover approach is crucial for production-grade apps, but is rarely used.
|
370
|
+
# 98% of your geocoding calls will be successful with the first call
|
371
|
+
def self.do_geocode(address)
|
372
|
+
Geokit::Geocoders::provider_order.each do |provider|
|
373
|
+
begin
|
374
|
+
klass = Geokit::Geocoders.const_get "#{provider.to_s.capitalize}Geocoder"
|
375
|
+
res = klass.send :geocode, address
|
376
|
+
return res if res.success
|
377
|
+
rescue
|
378
|
+
logger.error("Something has gone very wrong during geocoding, OR you have configured an invalid class name in Geokit::Geocoders::provider_order. Address: #{address}. Provider: #{provider}")
|
379
|
+
end
|
380
|
+
end
|
381
|
+
# If we get here, we failed completely.
|
382
|
+
GeoLoc.new
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|
386
|
+
end
|