andre-geokit 1.2.6 → 1.3.1
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/README.markdown +49 -1
- data/lib/geokit.rb +1 -1
- data/lib/geokit/geocoders.rb +46 -25
- data/lib/geokit/mappable.rb +12 -1
- data/test/test_google_geocoder.rb +18 -0
- data/test/test_latlng.rb +17 -1
- data/test/test_yahoo_geocoder.rb +18 -0
- metadata +2 -2
data/README.markdown
CHANGED
|
@@ -74,6 +74,12 @@ If you're using this gem by itself, here are the configuration options:
|
|
|
74
74
|
# See http://www.google.com/apis/maps/signup.html
|
|
75
75
|
# and http://www.google.com/apis/maps/documentation/#Geocoding_Examples
|
|
76
76
|
Geokit::Geocoders::google = 'REPLACE_WITH_YOUR_GOOGLE_KEY'
|
|
77
|
+
|
|
78
|
+
# You can also set multiple API KEYS for different domains that may be directed to this same application.
|
|
79
|
+
# The domain from which the current user is being directed will automatically be updated for Geokit via
|
|
80
|
+
# the GeocoderControl class, which gets it's begin filter mixed into the ActionController.
|
|
81
|
+
# You define these keys with a Hash as follows:
|
|
82
|
+
#Geokit::Geocoders::google = { 'rubyonrails.org' => 'RUBY_ON_RAILS_API_KEY', 'ruby-docs.org' => 'RUBY_DOCS_API_KEY' }
|
|
77
83
|
|
|
78
84
|
# This is your username and password for geocoder.us.
|
|
79
85
|
# To use the free service, the value can be set to nil or false. For
|
|
@@ -90,6 +96,10 @@ If you're using this gem by itself, here are the configuration options:
|
|
|
90
96
|
# and http://geocoder.ca/?register=1
|
|
91
97
|
Geokit::Geocoders::geocoder_ca = false
|
|
92
98
|
|
|
99
|
+
# require "external_geocoder.rb"
|
|
100
|
+
# Please see the section "writing your own geocoders" for more information.
|
|
101
|
+
# Geokit::Geocoders::external_key = 'REPLACE_WITH_YOUR_API_KEY'
|
|
102
|
+
|
|
93
103
|
# This is the order in which the geocoders are called in a failover scenario
|
|
94
104
|
# If you only want to use a single geocoder, put a single symbol in the array.
|
|
95
105
|
# Valid symbols are :google, :yahoo, :us, and :ca.
|
|
@@ -97,6 +107,10 @@ If you're using this gem by itself, here are the configuration options:
|
|
|
97
107
|
# various geocoders. Make sure you read up on relevant Terms of Use for each
|
|
98
108
|
# geocoder you are going to use.
|
|
99
109
|
Geokit::Geocoders::provider_order = [:google,:us]
|
|
110
|
+
|
|
111
|
+
# The IP provider order. Valid symbols are :ip,:geo_plugin.
|
|
112
|
+
# As before, make sure you read up on relevant Terms of Use for each.
|
|
113
|
+
# Geokit::Geocoders::ip_provider_order = [:external,:geo_plugin,:ip]
|
|
100
114
|
|
|
101
115
|
If you're using this gem with the [geokit-rails plugin](http://github.com/andre/geokit-rails/tree/master), the plugin
|
|
102
116
|
creates a template with these settings and places it in `config/initializers/geokit_config.rb`.
|
|
@@ -117,7 +131,11 @@ creates a template with these settings and places it in `config/initializers/geo
|
|
|
117
131
|
* Geoplugin.net -- another IP address geocoder
|
|
118
132
|
|
|
119
133
|
### The Multigeocoder
|
|
120
|
-
|
|
134
|
+
Multi Geocoder - provides failover for the physical location geocoders, and also IP address geocoders. Its configured by setting Geokit::Geocoders::provider_order, and Geokit::Geocoders::ip_provider_order. You should call the Multi-Geocoder with its :geocode method, supplying one address parameter which is either a real street address, or an ip address. For example:
|
|
135
|
+
|
|
136
|
+
Geokit::Geocoders::MultiGeocoder.geocode("900 Sycamore Drive")
|
|
137
|
+
|
|
138
|
+
Geokit::Geocoders::MultiGeocoder.geocode("12.12.12.12")
|
|
121
139
|
|
|
122
140
|
## MULTIPLE RESULTS
|
|
123
141
|
Some geocoding services will return multple results if the there isn't one clear result.
|
|
@@ -159,6 +177,36 @@ geocoders.rb contains all the geocoder implemenations. All the gercoders
|
|
|
159
177
|
inherit from a common base (class Geocoder) and implement the private method
|
|
160
178
|
do_geocode.
|
|
161
179
|
|
|
180
|
+
## WRITING YOUR OWN GEOCODERS
|
|
181
|
+
|
|
182
|
+
If you would like to write your own geocoders, you can do so by requiring 'geokit' or 'geokit/geocoders.rb' in a new file and subclassing the base class (which is class "Geocoder").
|
|
183
|
+
You must then also require such extenal file back in your main geokit configuration.
|
|
184
|
+
|
|
185
|
+
require "geokit"
|
|
186
|
+
|
|
187
|
+
module Geokit
|
|
188
|
+
module Geocoders
|
|
189
|
+
|
|
190
|
+
# Should be overriden as Geokit::Geocoders::external_key in your configuration file
|
|
191
|
+
@@external_key = 'REPLACE_WITH_YOUR_API_KEY'
|
|
192
|
+
__define_accessors
|
|
193
|
+
|
|
194
|
+
# Replace name 'External' (below) with the name of your custom geocoder class
|
|
195
|
+
# and use :external to specify this geocoder in your list of geocoders.
|
|
196
|
+
class ExternalGeocoder < Geocoder
|
|
197
|
+
private
|
|
198
|
+
def self.do_geocode(address)
|
|
199
|
+
# Main geocoding method
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def self.parse_http_resp(body) # :nodoc:
|
|
203
|
+
# Helper method to parse http response. See geokit/geocoders.rb.
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
162
210
|
## GOOGLE GROUP
|
|
163
211
|
|
|
164
212
|
Follow the Google Group for updates and discussion on Geokit: http://groups.google.com/group/geokit
|
data/lib/geokit.rb
CHANGED
data/lib/geokit/geocoders.rb
CHANGED
|
@@ -37,6 +37,10 @@ module Geokit
|
|
|
37
37
|
'%' + $1.unpack('H2' * $1.size).join('%').upcase
|
|
38
38
|
end.tr(' ', '+')
|
|
39
39
|
end
|
|
40
|
+
|
|
41
|
+
def camelize(str)
|
|
42
|
+
str.split('_').map {|w| w.capitalize}.join
|
|
43
|
+
end
|
|
40
44
|
end
|
|
41
45
|
|
|
42
46
|
# Contains a range of geocoders:
|
|
@@ -70,25 +74,37 @@ module Geokit
|
|
|
70
74
|
@@geocoder_ca = false
|
|
71
75
|
@@geonames = false
|
|
72
76
|
@@provider_order = [:google,:us]
|
|
77
|
+
@@ip_provider_order = [:geo_plugin,:ip]
|
|
73
78
|
@@logger=Logger.new(STDOUT)
|
|
74
79
|
@@logger.level=Logger::INFO
|
|
80
|
+
@@domain = nil
|
|
75
81
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
def self.__define_accessors
|
|
83
|
+
class_variables.each do |v|
|
|
84
|
+
sym = v.delete("@").to_sym
|
|
85
|
+
unless self.respond_to? sym
|
|
86
|
+
module_eval <<-EOS, __FILE__, __LINE__
|
|
87
|
+
def self.#{sym}
|
|
88
|
+
value = if defined?(#{sym.to_s.upcase})
|
|
89
|
+
#{sym.to_s.upcase}
|
|
90
|
+
else
|
|
91
|
+
@@#{sym}
|
|
92
|
+
end
|
|
93
|
+
if value.is_a?(Hash)
|
|
94
|
+
value = (self.domain.nil? ? nil : value[self.domain]) || value.values.first
|
|
95
|
+
end
|
|
96
|
+
value
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def self.#{sym}=(obj)
|
|
100
|
+
@@#{sym} = obj
|
|
101
|
+
end
|
|
102
|
+
EOS
|
|
85
103
|
end
|
|
86
|
-
|
|
87
|
-
def self.#{sym}=(obj)
|
|
88
|
-
@@#{sym} = obj
|
|
89
|
-
end
|
|
90
|
-
EOS
|
|
104
|
+
end
|
|
91
105
|
end
|
|
106
|
+
|
|
107
|
+
__define_accessors
|
|
92
108
|
|
|
93
109
|
# Error which is thrown in the event a geocoding error occurs.
|
|
94
110
|
class GeocodeError < StandardError; end
|
|
@@ -105,9 +121,8 @@ module Geokit
|
|
|
105
121
|
# empty one with a failed success code.
|
|
106
122
|
def self.geocode(address)
|
|
107
123
|
res = do_geocode(address)
|
|
108
|
-
return res.
|
|
124
|
+
return res.nil? ? GeoLoc.new : res
|
|
109
125
|
end
|
|
110
|
-
|
|
111
126
|
# Main method which calls the do_reverse_geocode template method which subclasses
|
|
112
127
|
# are responsible for implementing. Returns a populated GeoLoc or an
|
|
113
128
|
# empty one with a failed success code.
|
|
@@ -298,6 +313,8 @@ module Geokit
|
|
|
298
313
|
res.zip=doc.elements['//Zip'].text if doc.elements['//Zip'] && doc.elements['//Zip'].text != nil
|
|
299
314
|
res.street_address=doc.elements['//Address'].text if doc.elements['//Address'] && doc.elements['//Address'].text != nil
|
|
300
315
|
res.precision=doc.elements['//Result'].attributes['precision'] if doc.elements['//Result']
|
|
316
|
+
# set the accuracy as google does (added by Andruby)
|
|
317
|
+
res.accuracy=%w{unknown country state state city zip zip+4 street address building}.index(res.precision)
|
|
301
318
|
res.success=true
|
|
302
319
|
return res
|
|
303
320
|
else
|
|
@@ -441,8 +458,8 @@ module Geokit
|
|
|
441
458
|
# Translate accuracy into Yahoo-style token address, street, zip, zip+4, city, state, country
|
|
442
459
|
# For Google, 1=low accuracy, 8=high accuracy
|
|
443
460
|
address_details=doc.elements['.//*[local-name() = "AddressDetails"]']
|
|
444
|
-
accuracy = address_details ? address_details.attributes['Accuracy'].to_i : 0
|
|
445
|
-
res.precision=%w{unknown country state state city zip zip+4 street address building}[accuracy]
|
|
461
|
+
res.accuracy = address_details ? address_details.attributes['Accuracy'].to_i : 0
|
|
462
|
+
res.precision=%w{unknown country state state city zip zip+4 street address building}[res.accuracy]
|
|
446
463
|
res.success=true
|
|
447
464
|
|
|
448
465
|
return res
|
|
@@ -463,7 +480,7 @@ module Geokit
|
|
|
463
480
|
response = self.call_geocoder_service("http://www.geoplugin.net/xml.gp?ip=#{ip}")
|
|
464
481
|
return response.is_a?(Net::HTTPSuccess) ? parse_xml(response.body) : GeoLoc.new
|
|
465
482
|
rescue
|
|
466
|
-
logger.error "Caught an error during
|
|
483
|
+
logger.error "Caught an error during GeoPluginGeocoder geocoding call: "+$!
|
|
467
484
|
return GeoLoc.new
|
|
468
485
|
end
|
|
469
486
|
|
|
@@ -476,7 +493,7 @@ module Geokit
|
|
|
476
493
|
geo.country_code = xml.elements['//geoplugin_countryCode'].text
|
|
477
494
|
geo.lat = xml.elements['//geoplugin_latitude'].text.to_f
|
|
478
495
|
geo.lng = xml.elements['//geoplugin_longitude'].text.to_f
|
|
479
|
-
geo.success = !geo.city.empty?
|
|
496
|
+
geo.success = !!geo.city && !geo.city.empty?
|
|
480
497
|
return geo
|
|
481
498
|
end
|
|
482
499
|
end
|
|
@@ -529,7 +546,8 @@ module Geokit
|
|
|
529
546
|
# -------------------------------------------------------------------------------------------
|
|
530
547
|
|
|
531
548
|
# Provides methods to geocode with a variety of geocoding service providers, plus failover
|
|
532
|
-
# among providers in the order you configure.
|
|
549
|
+
# among providers in the order you configure. When 2nd parameter is set 'true', perform
|
|
550
|
+
# ip location lookup with 'address' as the ip address.
|
|
533
551
|
#
|
|
534
552
|
# Goal:
|
|
535
553
|
# - homogenize the results of multiple geocoders
|
|
@@ -537,18 +555,21 @@ module Geokit
|
|
|
537
555
|
# Limitations:
|
|
538
556
|
# - currently only provides the first result. Sometimes geocoders will return multiple results.
|
|
539
557
|
# - currently discards the "accuracy" component of the geocoding calls
|
|
540
|
-
class MultiGeocoder < Geocoder
|
|
541
|
-
private
|
|
558
|
+
class MultiGeocoder < Geocoder
|
|
542
559
|
|
|
560
|
+
private
|
|
543
561
|
# This method will call one or more geocoders in the order specified in the
|
|
544
562
|
# configuration until one of the geocoders work.
|
|
545
563
|
#
|
|
546
564
|
# The failover approach is crucial for production-grade apps, but is rarely used.
|
|
547
565
|
# 98% of your geocoding calls will be successful with the first call
|
|
548
566
|
def self.do_geocode(address)
|
|
549
|
-
|
|
567
|
+
geocode_ip = !!/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(address)
|
|
568
|
+
provider_order = geocode_ip ? Geokit::Geocoders::ip_provider_order : Geokit::Geocoders::provider_order
|
|
569
|
+
|
|
570
|
+
provider_order.each do |provider|
|
|
550
571
|
begin
|
|
551
|
-
klass = Geokit::Geocoders.const_get "#{provider.to_s
|
|
572
|
+
klass = Geokit::Geocoders.const_get "#{Geokit::Inflector::camelize(provider.to_s)}Geocoder"
|
|
552
573
|
res = klass.send :geocode, address
|
|
553
574
|
return res if res.success?
|
|
554
575
|
rescue
|
data/lib/geokit/mappable.rb
CHANGED
|
@@ -251,6 +251,14 @@ module Geokit
|
|
|
251
251
|
other.is_a?(LatLng) ? self.lat == other.lat && self.lng == other.lng : false
|
|
252
252
|
end
|
|
253
253
|
|
|
254
|
+
def hash
|
|
255
|
+
lat.hash + lng.hash
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def eql?(other)
|
|
259
|
+
self == other
|
|
260
|
+
end
|
|
261
|
+
|
|
254
262
|
# A *class* method to take anything which can be inferred as a point and generate
|
|
255
263
|
# a LatLng from it. You should use this anything you're not sure what the input is,
|
|
256
264
|
# and want to deal with it as a LatLng if at all possible. Can take:
|
|
@@ -317,6 +325,9 @@ module Geokit
|
|
|
317
325
|
attr_accessor :success, :provider, :precision
|
|
318
326
|
# Street number and street name are extracted from the street address attribute.
|
|
319
327
|
attr_reader :street_number, :street_name
|
|
328
|
+
# accuracy is set for Yahoo and Google geocoders, it is a numeric value of the
|
|
329
|
+
# precision. see http://code.google.com/apis/maps/documentation/geocoding/#GeocodingAccuracy
|
|
330
|
+
attr_accessor :accuracy
|
|
320
331
|
|
|
321
332
|
# Constructor expects a hash of symbols to correspond with attributes.
|
|
322
333
|
def initialize(h={})
|
|
@@ -392,7 +403,7 @@ module Geokit
|
|
|
392
403
|
|
|
393
404
|
# Returns a string representation of the instance.
|
|
394
405
|
def to_s
|
|
395
|
-
"Provider: #{provider}\
|
|
406
|
+
"Provider: #{provider}\nStreet: #{street_address}\nCity: #{city}\nState: #{state}\nZip: #{zip}\nLatitude: #{lat}\nLongitude: #{lng}\nCountry: #{country_code}\nSuccess: #{success}"
|
|
396
407
|
end
|
|
397
408
|
end
|
|
398
409
|
|
|
@@ -50,6 +50,15 @@ class GoogleGeocoderTest < BaseGeocoderTest #:nodoc: all
|
|
|
50
50
|
assert_equal "100 Spear St, San Francisco, CA 94105, USA", res.full_address #slightly different from yahoo
|
|
51
51
|
assert_equal "google", res.provider
|
|
52
52
|
end
|
|
53
|
+
|
|
54
|
+
def test_google_full_address_accuracy
|
|
55
|
+
response = MockSuccess.new
|
|
56
|
+
response.expects(:body).returns(GOOGLE_FULL)
|
|
57
|
+
url = "http://maps.google.com/maps/geo?q=#{Geokit::Inflector.url_escape(@full_address_short_zip)}&output=xml&key=Google&oe=utf-8"
|
|
58
|
+
Geokit::Geocoders::GoogleGeocoder.expects(:call_geocoder_service).with(url).returns(response)
|
|
59
|
+
res=Geokit::Geocoders::GoogleGeocoder.geocode(@google_full_loc)
|
|
60
|
+
assert_equal 8, res.accuracy
|
|
61
|
+
end
|
|
53
62
|
|
|
54
63
|
def test_google_city
|
|
55
64
|
response = MockSuccess.new
|
|
@@ -66,6 +75,15 @@ class GoogleGeocoderTest < BaseGeocoderTest #:nodoc: all
|
|
|
66
75
|
assert_equal "google", res.provider
|
|
67
76
|
end
|
|
68
77
|
|
|
78
|
+
def test_google_city_accuracy
|
|
79
|
+
response = MockSuccess.new
|
|
80
|
+
response.expects(:body).returns(GOOGLE_CITY)
|
|
81
|
+
url = "http://maps.google.com/maps/geo?q=#{Geokit::Inflector.url_escape(@address)}&output=xml&key=Google&oe=utf-8"
|
|
82
|
+
Geokit::Geocoders::GoogleGeocoder.expects(:call_geocoder_service).with(url).returns(response)
|
|
83
|
+
res=Geokit::Geocoders::GoogleGeocoder.geocode(@address)
|
|
84
|
+
assert_equal 4, res.accuracy
|
|
85
|
+
end
|
|
86
|
+
|
|
69
87
|
def test_google_city_with_geo_loc
|
|
70
88
|
response = MockSuccess.new
|
|
71
89
|
response.expects(:body).returns(GOOGLE_CITY)
|
data/test/test_latlng.rb
CHANGED
|
@@ -128,5 +128,21 @@ class LatLngTest < Test::Unit::TestCase #:nodoc: all
|
|
|
128
128
|
res=Geokit::LatLng.normalize([lat,lng])
|
|
129
129
|
assert_equal res,Geokit::LatLng.new(lat,lng)
|
|
130
130
|
end
|
|
131
|
-
|
|
131
|
+
|
|
132
|
+
def test_hash
|
|
133
|
+
lat=37.7690
|
|
134
|
+
lng=-122.443
|
|
135
|
+
first = Geokit::LatLng.new(lat,lng)
|
|
136
|
+
second = Geokit::LatLng.new(lat,lng)
|
|
137
|
+
assert_equal first.hash, second.hash
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def test_eql?
|
|
141
|
+
lat=37.7690
|
|
142
|
+
lng=-122.443
|
|
143
|
+
first = Geokit::LatLng.new(lat,lng)
|
|
144
|
+
second = Geokit::LatLng.new(lat,lng)
|
|
145
|
+
assert first.eql?(second)
|
|
146
|
+
assert second.eql?(first)
|
|
147
|
+
end
|
|
132
148
|
end
|
data/test/test_yahoo_geocoder.rb
CHANGED
|
@@ -32,6 +32,15 @@ class YahooGeocoderTest < BaseGeocoderTest #:nodoc: all
|
|
|
32
32
|
do_full_address_assertions(Geokit::Geocoders::YahooGeocoder.geocode(@address))
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
+
def test_yahoo_full_address_accuracy
|
|
36
|
+
response = MockSuccess.new
|
|
37
|
+
response.expects(:body).returns(YAHOO_FULL)
|
|
38
|
+
url = "http://api.local.yahoo.com/MapsService/V1/geocode?appid=Yahoo&location=#{Geokit::Inflector.url_escape(@address)}"
|
|
39
|
+
Geokit::Geocoders::YahooGeocoder.expects(:call_geocoder_service).with(url).returns(response)
|
|
40
|
+
res = Geokit::Geocoders::YahooGeocoder.geocode(@address)
|
|
41
|
+
assert_equal 8, res.accuracy
|
|
42
|
+
end
|
|
43
|
+
|
|
35
44
|
def test_yahoo_full_address_with_geo_loc
|
|
36
45
|
response = MockSuccess.new
|
|
37
46
|
response.expects(:body).returns(YAHOO_FULL)
|
|
@@ -48,6 +57,15 @@ class YahooGeocoderTest < BaseGeocoderTest #:nodoc: all
|
|
|
48
57
|
do_city_assertions(Geokit::Geocoders::YahooGeocoder.geocode(@address))
|
|
49
58
|
end
|
|
50
59
|
|
|
60
|
+
def test_yahoo_city_accuracy
|
|
61
|
+
response = MockSuccess.new
|
|
62
|
+
response.expects(:body).returns(YAHOO_CITY)
|
|
63
|
+
url = "http://api.local.yahoo.com/MapsService/V1/geocode?appid=Yahoo&location=#{Geokit::Inflector.url_escape(@address)}"
|
|
64
|
+
Geokit::Geocoders::YahooGeocoder.expects(:call_geocoder_service).with(url).returns(response)
|
|
65
|
+
res = Geokit::Geocoders::YahooGeocoder.geocode(@address)
|
|
66
|
+
assert_equal 4, res.accuracy
|
|
67
|
+
end
|
|
68
|
+
|
|
51
69
|
def test_yahoo_city_with_geo_loc
|
|
52
70
|
response = MockSuccess.new
|
|
53
71
|
response.expects(:body).returns(YAHOO_CITY)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: andre-geokit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andre Lewis and Bill Eisenhauer
|
|
@@ -9,7 +9,7 @@ autorequire:
|
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
11
|
|
|
12
|
-
date: 2009-
|
|
12
|
+
date: 2009-04-11 00:00:00 -07:00
|
|
13
13
|
default_executable:
|
|
14
14
|
dependencies: []
|
|
15
15
|
|