andre-geokit 1.2.6 → 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|