darrell-geokit 1.2.4.1 → 1.4.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/Manifest.txt CHANGED
@@ -1,4 +1,4 @@
1
- .project
1
+ History.txt
2
2
  Manifest.txt
3
3
  README.markdown
4
4
  Rakefile
@@ -10,8 +10,10 @@ test/test_base_geocoder.rb
10
10
  test/test_bounds.rb
11
11
  test/test_ca_geocoder.rb
12
12
  test/test_geoloc.rb
13
+ test/test_geoplugin_geocoder.rb
13
14
  test/test_google_geocoder.rb
14
15
  test/test_google_reverse_geocoder.rb
16
+ test/test_inflector.rb
15
17
  test/test_ipgeocoder.rb
16
18
  test/test_latlng.rb
17
19
  test/test_multi_geocoder.rb
data/README.markdown CHANGED
@@ -8,15 +8,15 @@ The Geokit gem provides:
8
8
  * Rectangular bounds calculations: is a point within a given rectangular bounds?
9
9
  * Heading and midpoint calculations
10
10
 
11
- Combine this with gem with the [geokit-rails plugin](http://github.com/andre/geokit-rails/tree/master) to get location-based finders for your Rails app.
11
+ Combine this gem with the [geokit-rails plugin](http://github.com/andre/geokit-rails/tree/master) to get location-based finders for your Rails app.
12
12
 
13
13
  * Geokit Documentation at Rubyforge [http://geokit.rubyforge.org](http://geokit.rubyforge.org).
14
14
  * Repository at Github: [http://github.com/andre/geokit-gem/tree/master](http://github.com/andre/geokit-gem/tree/master).
15
+ * Follow the Google Group for updates and discussion on Geokit: [http://groups.google.com/group/geokit](http://groups.google.com/group/geokit)
15
16
 
16
17
  ## INSTALL
17
18
 
18
- gem sources -a http://gems.github.com
19
- sudo gem install andre-geokit
19
+ sudo gem install geokit
20
20
 
21
21
  ## QUICK START
22
22
 
@@ -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`.
@@ -110,14 +124,54 @@ creates a template with these settings and places it in `config/initializers/geo
110
124
  * Geonames - a free geocoder
111
125
 
112
126
  ### address geocoders that also provide reverse geocoding
113
- * Google Geocoder - requires an API key. Also supports multiple results.
127
+ * Google Geocoder - requires an API key. Also supports multiple results and bounding box/country code biasing.
114
128
 
115
129
  ### IP address geocoders
116
130
  * IP Geocoder - geocodes an IP address using hostip.info's web service.
117
131
  * Geoplugin.net -- another IP address geocoder
118
132
 
133
+ ### Google Geocoder Tricks
134
+
135
+ The Google Geocoder sports a number of useful tricks that elevate it a little bit above the rest of the currently supported geocoders. For starters, it returns a `suggested_bounds` property for all your geocoded results, so you can more easily decide where and how to center a map on the places you geocode. Here's a quick example:
136
+
137
+ irb> res = Geokit::Geocoders::GoogleGeocoder.geocode('140 Market St, San Francisco, CA')
138
+ irb> pp res.suggested_bounds
139
+ #<Geokit::Bounds:0x53b36c
140
+ @ne=#<Geokit::LatLng:0x53b204 @lat=37.7968528, @lng=-122.3926933>,
141
+ @sw=#<Geokit::LatLng:0x53b2b8 @lat=37.7905576, @lng=-122.3989885>>
142
+
143
+ In addition, you can use viewport or country code biasing to make sure the geocoders prefers results within a specific area. Say we wanted to geocode the city of Syracuse in Italy. A normal geocoding query would look like this:
144
+
145
+ irb> res = Geokit::Geocoder::GoogleGeocoder.geocode('Syracuse')
146
+ irb> res.full_address
147
+ => "Syracuse, NY, USA"
148
+
149
+ Not exactly what we were looking for. We know that Syracuse is in Italy, so we can tell the Google Geocoder to prefer results from Italy first, and then wander the Syracuses of the world. To do that, we have to pass Italy's ccTLD (country code top-level domain) to the `:bias` option of the `geocode` method. You can find a comprehensive list of all ccTLDs here: http://en.wikipedia.org/wiki/CcTLD.
150
+
151
+ irb> res = Geokit::Geocoder::GoogleGeocoder.geocode('Syracuse', :bias => 'it')
152
+ irb> res.full_address
153
+ => "Syracuse, Italy"
154
+
155
+ Alternatively, we can speficy the geocoding bias as a bounding box object. Say we wanted to geocode the Winnetka district in Los Angeles.
156
+
157
+ irb> res = Geokit::Geocoder::GoogleGeocoder.geocode('Winnetka')
158
+ irb> res.full_address
159
+ => "Winnetka, IL, USA"
160
+
161
+ Not it. What we can do is tell the geocoder to return results only from in and around LA.
162
+
163
+ irb> la_bounds = Geokit::Geocoder::GoogleGeocoder.geocode('Los Angeles').suggested_bounds
164
+ irb> res = Geokit::Geocoder::GoogleGeocoder.geocode('Winnetka', :bias => la_bounds)
165
+ irb> res.full_address
166
+ => "Winnetka, California, USA"
167
+
168
+
119
169
  ### The Multigeocoder
120
- * Multi Geocoder - provides failover for the physical location geocoders.
170
+ 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:
171
+
172
+ Geokit::Geocoders::MultiGeocoder.geocode("900 Sycamore Drive")
173
+
174
+ Geokit::Geocoders::MultiGeocoder.geocode("12.12.12.12")
121
175
 
122
176
  ## MULTIPLE RESULTS
123
177
  Some geocoding services will return multple results if the there isn't one clear result.
@@ -159,6 +213,36 @@ geocoders.rb contains all the geocoder implemenations. All the gercoders
159
213
  inherit from a common base (class Geocoder) and implement the private method
160
214
  do_geocode.
161
215
 
216
+ ## WRITING YOUR OWN GEOCODERS
217
+
218
+ 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").
219
+ You must then also require such extenal file back in your main geokit configuration.
220
+
221
+ require "geokit"
222
+
223
+ module Geokit
224
+ module Geocoders
225
+
226
+ # Should be overriden as Geokit::Geocoders::external_key in your configuration file
227
+ @@external_key = 'REPLACE_WITH_YOUR_API_KEY'
228
+ __define_accessors
229
+
230
+ # Replace name 'External' (below) with the name of your custom geocoder class
231
+ # and use :external to specify this geocoder in your list of geocoders.
232
+ class ExternalGeocoder < Geocoder
233
+ private
234
+ def self.do_geocode(address, options = {})
235
+ # Main geocoding method
236
+ end
237
+
238
+ def self.parse_http_resp(body) # :nodoc:
239
+ # Helper method to parse http response. See geokit/geocoders.rb.
240
+ end
241
+ end
242
+
243
+ end
244
+ end
245
+
162
246
  ## GOOGLE GROUP
163
247
 
164
248
  Follow the Google Group for updates and discussion on Geokit: http://groups.google.com/group/geokit
data/Rakefile CHANGED
@@ -2,19 +2,21 @@
2
2
 
3
3
  require 'rubygems'
4
4
  require 'hoe'
5
- require './lib/geokit'
5
+ require './lib/geokit.rb'
6
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
7
+ # undefined method `empty?' for nil:NilClass
8
+ # /Library/Ruby/Site/1.8/rubygems/specification.rb:886:in `validate'
9
+ class NilClass
10
+ def empty?
11
+ true
12
+ end
13
+ end
11
14
 
12
- task :generate_gemspec do
13
- system "rake debug_gem | grep -v \"(in \" > `basename \\`pwd\\``.gemspec"
15
+ project=Hoe.new('geokit', Geokit::VERSION) do |p|
16
+ #p.rubyforge_name = 'geokit' # if different than lowercase project name
17
+ p.developer('Andre Lewis', 'andre@earthcode.com')
18
+ p.summary="Geokit provides geocoding and distance calculation in an easy-to-use API"
14
19
  end
15
20
 
16
- task :update_manifest do
17
- system "touch Manifest.txt; rake check_manifest | grep -v \"(in \" | patch"
18
- end
19
21
 
20
22
  # vim: syntax=Ruby
data/lib/geokit.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Geokit
2
- VERSION = '1.2.0'
2
+ VERSION = '1.4.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
@@ -22,9 +22,9 @@ module Geokit
22
22
  end
23
23
 
24
24
  path = File.expand_path(File.dirname(__FILE__))
25
- $: << path unless $:.include?(path)
25
+ $:.unshift path unless $:.include?(path)
26
26
  require 'geokit/geocoders'
27
27
  require 'geokit/mappable'
28
28
 
29
- # make old-style module name "GeoKit" equivilent to new-style "Geokit"
29
+ # make old-style module name "GeoKit" equivalent to new-style "Geokit"
30
30
  GeoKit=Geokit
@@ -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
85
- end
86
-
87
- def self.#{sym}=(obj)
88
- @@#{sym} = obj
82
+ def self.__define_accessors
83
+ class_variables.each do |v|
84
+ sym = v.to_s.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
89
103
  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
@@ -103,17 +119,16 @@ module Geokit
103
119
  # Main method which calls the do_geocode template method which subclasses
104
120
  # are responsible for implementing. Returns a populated GeoLoc or an
105
121
  # empty one with a failed success code.
106
- def self.geocode(address)
107
- res = do_geocode(address)
108
- return res.success ? res : GeoLoc.new
122
+ def self.geocode(address, options = {})
123
+ res = do_geocode(address, options)
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.
114
129
  def self.reverse_geocode(latlng)
115
130
  res = do_reverse_geocode(latlng)
116
- return res.success ? res : GeoLoc.new
131
+ return res.success? ? res : GeoLoc.new
117
132
  end
118
133
 
119
134
  # Call the geocoder service using the timeout if configured.
@@ -157,8 +172,8 @@ module Geokit
157
172
  def self.inherited(clazz)
158
173
  class_name = clazz.name.split('::').last
159
174
  src = <<-END_SRC
160
- def self.#{Geokit::Inflector.underscore(class_name)}(address)
161
- #{class_name}.geocode(address)
175
+ def self.#{Geokit::Inflector.underscore(class_name)}(address, options = {})
176
+ #{class_name}.geocode(address, options)
162
177
  end
163
178
  END_SRC
164
179
  class_eval(src)
@@ -184,7 +199,7 @@ module Geokit
184
199
  private
185
200
 
186
201
  # Template method which does the geocode lookup.
187
- def self.do_geocode(address)
202
+ def self.do_geocode(address, options = {})
188
203
  raise ArgumentError('Geocoder.ca requires a GeoLoc argument') unless address.is_a?(GeoLoc)
189
204
  url = construct_request(address)
190
205
  res = self.call_geocoder_service(url)
@@ -226,7 +241,7 @@ module Geokit
226
241
  class UsGeocoder < Geocoder
227
242
 
228
243
  private
229
- def self.do_geocode(address)
244
+ def self.do_geocode(address, options = {})
230
245
  address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
231
246
 
232
247
  query = (address_str =~ /^\d{5}(?:-\d{4})?$/ ? "zip" : "address") + "=#{Geokit::Inflector::url_escape(address_str)}"
@@ -274,7 +289,7 @@ module Geokit
274
289
  private
275
290
 
276
291
  # Template method which does the geocode lookup.
277
- def self.do_geocode(address)
292
+ def self.do_geocode(address, options = {})
278
293
  address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
279
294
  url="http://api.local.yahoo.com/MapsService/V1/geocode?appid=#{Geokit::Geocoders::yahoo}&location=#{Geokit::Inflector::url_escape(address_str)}"
280
295
  res = self.call_geocoder_service(url)
@@ -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
@@ -318,7 +335,7 @@ module Geokit
318
335
  private
319
336
 
320
337
  # Template method which does the geocode lookup.
321
- def self.do_geocode(address)
338
+ def self.do_geocode(address, options = {})
322
339
  address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
323
340
  # geonames need a space seperated search string
324
341
  address_str.gsub!(/,/, " ")
@@ -383,16 +400,51 @@ module Geokit
383
400
  end
384
401
 
385
402
  # Template method which does the geocode lookup.
386
- def self.do_geocode(address)
403
+ #
404
+ # Supports viewport/country code biasing
405
+ #
406
+ # ==== OPTIONS
407
+ # * :bias - This option makes the Google Geocoder return results biased to a particular
408
+ # country or viewport. Country code biasing is achieved by passing the ccTLD
409
+ # ('uk' for .co.uk, for example) as a :bias value. For a list of ccTLD's,
410
+ # look here: http://en.wikipedia.org/wiki/CcTLD. By default, the geocoder
411
+ # will be biased to results within the US (ccTLD .com).
412
+ #
413
+ # If you'd like the Google Geocoder to prefer results within a given viewport,
414
+ # you can pass a Geokit::Bounds object as the :bias value.
415
+ #
416
+ # ==== EXAMPLES
417
+ # # By default, the geocoder will return Syracuse, NY
418
+ # Geokit::Geocoders::GoogleGeocoder.geocode('Syracuse').country_code # => 'US'
419
+ # # With country code biasing, it returns Syracuse in Sicily, Italy
420
+ # Geokit::Geocoders::GoogleGeocoder.geocode('Syracuse', :bias => :it).country_code # => 'IT'
421
+ #
422
+ # # By default, the geocoder will return Winnetka, IL
423
+ # Geokit::Geocoders::GoogleGeocoder.geocode('Winnetka').state # => 'IL'
424
+ # # When biased to an bounding box around California, it will now return the Winnetka neighbourhood, CA
425
+ # bounds = Geokit::Bounds.normalize([34.074081, -118.694401], [34.321129, -118.399487])
426
+ # Geokit::Geocoders::GoogleGeocoder.geocode('Winnetka', :bias => bounds).state # => 'CA'
427
+ def self.do_geocode(address, options = {})
428
+ bias_str = options[:bias] ? construct_bias_string_from_options(options[:bias]) : ''
387
429
  address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
388
- 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")
430
+ res = self.call_geocoder_service("http://maps.google.com/maps/geo?q=#{Geokit::Inflector::url_escape(address_str)}&output=xml#{bias_str}&key=#{Geokit::Geocoders::google}&oe=utf-8")
389
431
  return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
390
432
  xml = res.body
391
433
  logger.debug "Google geocoding. Address: #{address}. Result: #{xml}"
392
- return self.xml2GeoLoc(xml)
434
+ return self.xml2GeoLoc(xml, address)
393
435
  end
394
436
 
395
- def self.xml2GeoLoc(xml)
437
+ def self.construct_bias_string_from_options(bias)
438
+ if bias.is_a?(String) or bias.is_a?(Symbol)
439
+ # country code biasing
440
+ "&gl=#{bias.to_s.downcase}"
441
+ elsif bias.is_a?(Bounds)
442
+ # viewport biasing
443
+ "&ll=#{bias.center.ll}&spn=#{bias.to_span.ll}"
444
+ end
445
+ end
446
+
447
+ def self.xml2GeoLoc(xml, address="")
396
448
  doc=REXML::Document.new(xml)
397
449
 
398
450
  if doc.elements['//kml/Response/Status/code'].text == '200'
@@ -400,9 +452,9 @@ module Geokit
400
452
  # Google can return multiple results as //Placemark elements.
401
453
  # iterate through each and extract each placemark as a geoloc
402
454
  doc.each_element('//Placemark') do |e|
403
- extracted_geoloc = extract_placemark(e) # g is now an instance of Geoloc
455
+ extracted_geoloc = extract_placemark(e) # g is now an instance of GeoLoc
404
456
  if geoloc.nil?
405
- # first time through, geoloc is still nill, so we make it the geoloc we just extracted
457
+ # first time through, geoloc is still nil, so we make it the geoloc we just extracted
406
458
  geoloc = extracted_geoloc
407
459
  else
408
460
  # second (and subsequent) iterations, we push additional
@@ -441,8 +493,16 @@ module Geokit
441
493
  # Translate accuracy into Yahoo-style token address, street, zip, zip+4, city, state, country
442
494
  # For Google, 1=low accuracy, 8=high accuracy
443
495
  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]
496
+ res.accuracy = address_details ? address_details.attributes['Accuracy'].to_i : 0
497
+ res.precision=%w{unknown country state state city zip zip+4 street address building}[res.accuracy]
498
+
499
+ # google returns a set of suggested boundaries for the geocoded result
500
+ if suggested_bounds = doc.elements['//LatLonBox']
501
+ res.suggested_bounds = Bounds.normalize(
502
+ [suggested_bounds.attributes['south'], suggested_bounds.attributes['west']],
503
+ [suggested_bounds.attributes['north'], suggested_bounds.attributes['east']])
504
+ end
505
+
446
506
  res.success=true
447
507
 
448
508
  return res
@@ -458,12 +518,12 @@ module Geokit
458
518
  class GeoPluginGeocoder < Geocoder
459
519
  private
460
520
 
461
- def self.do_geocode(ip)
521
+ def self.do_geocode(ip, options = {})
462
522
  return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
463
523
  response = self.call_geocoder_service("http://www.geoplugin.net/xml.gp?ip=#{ip}")
464
524
  return response.is_a?(Net::HTTPSuccess) ? parse_xml(response.body) : GeoLoc.new
465
525
  rescue
466
- logger.error "Caught an error during GeloPluginGeocoder geocoding call: "+$!
526
+ logger.error "Caught an error during GeoPluginGeocoder geocoding call: "+$!
467
527
  return GeoLoc.new
468
528
  end
469
529
 
@@ -476,7 +536,7 @@ module Geokit
476
536
  geo.country_code = xml.elements['//geoplugin_countryCode'].text
477
537
  geo.lat = xml.elements['//geoplugin_latitude'].text.to_f
478
538
  geo.lng = xml.elements['//geoplugin_longitude'].text.to_f
479
- geo.success = !geo.city.empty?
539
+ geo.success = !!geo.city && !geo.city.empty?
480
540
  return geo
481
541
  end
482
542
  end
@@ -491,8 +551,8 @@ module Geokit
491
551
  # Given an IP address, returns a GeoLoc instance which contains latitude,
492
552
  # longitude, city, and country code. Sets the success attribute to false if the ip
493
553
  # parameter does not match an ip address.
494
- def self.do_geocode(ip)
495
- return Geoloc.new if '0.0.0.0' == ip
554
+ def self.do_geocode(ip, options = {})
555
+ return GeoLoc.new if '0.0.0.0' == ip
496
556
  return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
497
557
  url = "http://api.hostip.info/get_html.php?ip=#{ip}&position=true"
498
558
  response = self.call_geocoder_service(url)
@@ -529,7 +589,8 @@ module Geokit
529
589
  # -------------------------------------------------------------------------------------------
530
590
 
531
591
  # Provides methods to geocode with a variety of geocoding service providers, plus failover
532
- # among providers in the order you configure.
592
+ # among providers in the order you configure. When 2nd parameter is set 'true', perform
593
+ # ip location lookup with 'address' as the ip address.
533
594
  #
534
595
  # Goal:
535
596
  # - homogenize the results of multiple geocoders
@@ -537,20 +598,23 @@ module Geokit
537
598
  # Limitations:
538
599
  # - currently only provides the first result. Sometimes geocoders will return multiple results.
539
600
  # - currently discards the "accuracy" component of the geocoding calls
540
- class MultiGeocoder < Geocoder
541
- private
601
+ class MultiGeocoder < Geocoder
542
602
 
603
+ private
543
604
  # This method will call one or more geocoders in the order specified in the
544
605
  # configuration until one of the geocoders work.
545
606
  #
546
607
  # The failover approach is crucial for production-grade apps, but is rarely used.
547
608
  # 98% of your geocoding calls will be successful with the first call
548
- def self.do_geocode(address)
549
- Geokit::Geocoders::provider_order.each do |provider|
609
+ def self.do_geocode(address, options = {})
610
+ geocode_ip = /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.match(address)
611
+ provider_order = geocode_ip ? Geokit::Geocoders::ip_provider_order : Geokit::Geocoders::provider_order
612
+
613
+ provider_order.each do |provider|
550
614
  begin
551
- klass = Geokit::Geocoders.const_get "#{provider.to_s.capitalize}Geocoder"
552
- res = klass.send :geocode, address
553
- return res if res.success
615
+ klass = Geokit::Geocoders.const_get "#{Geokit::Inflector::camelize(provider.to_s)}Geocoder"
616
+ res = klass.send :geocode, address, options
617
+ return res if res.success?
554
618
  rescue
555
619
  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}")
556
620
  end
@@ -558,6 +622,23 @@ module Geokit
558
622
  # If we get here, we failed completely.
559
623
  GeoLoc.new
560
624
  end
625
+
626
+ # This method will call one or more geocoders in the order specified in the
627
+ # configuration until one of the geocoders work, only this time it's going
628
+ # to try to reverse geocode a geographical point.
629
+ def self.do_reverse_geocode(latlng)
630
+ Geokit::Geocoders::provider_order.each do |provider|
631
+ begin
632
+ klass = Geokit::Geocoders.const_get "#{Geokit::Inflector::camelize(provider.to_s)}Geocoder"
633
+ res = klass.send :reverse_geocode, latlng
634
+ return res if res.success?
635
+ rescue
636
+ logger.error("Something has gone very wrong during reverse geocoding, OR you have configured an invalid class name in Geokit::Geocoders::provider_order. LatLng: #{latlng}. Provider: #{provider}")
637
+ end
638
+ end
639
+ # If we get here, we failed completely.
640
+ GeoLoc.new
641
+ end
561
642
  end
562
643
  end
563
644
  end