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.
@@ -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