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.
@@ -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
- * Multi Geocoder - provides failover for the physical location geocoders.
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
@@ -1,5 +1,5 @@
1
1
  module Geokit
2
- VERSION = '1.2.6'
2
+ VERSION = '1.3.1'
3
3
  # These defaults are used in Geokit::Mappable.distance_to and in acts_as_mappable
4
4
  @@default_units = :miles
5
5
  @@default_formula = :sphere
@@ -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
- [:yahoo, :google, :geocoder_us, :geocoder_ca, :geonames, :provider_order, :timeout,
77
- :proxy_addr, :proxy_port, :proxy_user, :proxy_pass,:logger].each do |sym|
78
- class_eval <<-EOS, __FILE__, __LINE__
79
- def self.#{sym}
80
- if defined?(#{sym.to_s.upcase})
81
- #{sym.to_s.upcase}
82
- else
83
- @@#{sym}
84
- end
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.success? ? res : GeoLoc.new
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 GeloPluginGeocoder geocoding call: "+$!
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
- Geokit::Geocoders::provider_order.each do |provider|
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.capitalize}Geocoder"
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
@@ -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}\n Street: #{street_address}\nCity: #{city}\nState: #{state}\nZip: #{zip}\nLatitude: #{lat}\nLongitude: #{lng}\nCountry: #{country_code}\nSuccess: #{success}"
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)
@@ -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
@@ -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.2.6
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-02-09 00:00:00 -08:00
12
+ date: 2009-04-11 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15