geokit 1.3.2 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,8 @@
1
+ === 1.4.0 / 2009-05-27
2
+ * Added country code/viewport biasing to GoogleGeocoder. Added Bounds#to_span method
3
+ * Added suggested_bounds (Geokit::Bounds) property to GeoLoc. (Google geocoder only)
4
+ * Added LatLng#reverse_geocode convenience method (thanks Tisho Georgiev for all three)
5
+
1
6
  === 1.3.2 / 2009-05-27
2
7
  * Fixed blank address geocoding bug
3
8
 
@@ -124,12 +124,48 @@ creates a template with these settings and places it in `config/initializers/geo
124
124
  * Geonames - a free geocoder
125
125
 
126
126
  ### address geocoders that also provide reverse geocoding
127
- * 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.
128
128
 
129
129
  ### IP address geocoders
130
130
  * IP Geocoder - geocodes an IP address using hostip.info's web service.
131
131
  * Geoplugin.net -- another IP address geocoder
132
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
+
133
169
  ### The Multigeocoder
134
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:
135
171
 
@@ -195,7 +231,7 @@ You must then also require such extenal file back in your main geokit configurat
195
231
  # and use :external to specify this geocoder in your list of geocoders.
196
232
  class ExternalGeocoder < Geocoder
197
233
  private
198
- def self.do_geocode(address)
234
+ def self.do_geocode(address, options = {})
199
235
  # Main geocoding method
200
236
  end
201
237
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{geokit}
5
- s.version = "1.3.2"
5
+ s.version = "1.4.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Andre Lewis and Bill Eisenhauer"]
@@ -1,5 +1,5 @@
1
1
  module Geokit
2
- VERSION = '1.3.2'
2
+ VERSION = '1.4.0'
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
@@ -119,8 +119,8 @@ module Geokit
119
119
  # Main method which calls the do_geocode template method which subclasses
120
120
  # are responsible for implementing. Returns a populated GeoLoc or an
121
121
  # empty one with a failed success code.
122
- def self.geocode(address)
123
- res = do_geocode(address)
122
+ def self.geocode(address, options = {})
123
+ res = do_geocode(address, options)
124
124
  return res.nil? ? GeoLoc.new : res
125
125
  end
126
126
  # Main method which calls the do_reverse_geocode template method which subclasses
@@ -172,8 +172,8 @@ module Geokit
172
172
  def self.inherited(clazz)
173
173
  class_name = clazz.name.split('::').last
174
174
  src = <<-END_SRC
175
- def self.#{Geokit::Inflector.underscore(class_name)}(address)
176
- #{class_name}.geocode(address)
175
+ def self.#{Geokit::Inflector.underscore(class_name)}(address, options = {})
176
+ #{class_name}.geocode(address, options)
177
177
  end
178
178
  END_SRC
179
179
  class_eval(src)
@@ -199,7 +199,7 @@ module Geokit
199
199
  private
200
200
 
201
201
  # Template method which does the geocode lookup.
202
- def self.do_geocode(address)
202
+ def self.do_geocode(address, options = {})
203
203
  raise ArgumentError('Geocoder.ca requires a GeoLoc argument') unless address.is_a?(GeoLoc)
204
204
  url = construct_request(address)
205
205
  res = self.call_geocoder_service(url)
@@ -241,7 +241,7 @@ module Geokit
241
241
  class UsGeocoder < Geocoder
242
242
 
243
243
  private
244
- def self.do_geocode(address)
244
+ def self.do_geocode(address, options = {})
245
245
  address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
246
246
 
247
247
  query = (address_str =~ /^\d{5}(?:-\d{4})?$/ ? "zip" : "address") + "=#{Geokit::Inflector::url_escape(address_str)}"
@@ -289,7 +289,7 @@ module Geokit
289
289
  private
290
290
 
291
291
  # Template method which does the geocode lookup.
292
- def self.do_geocode(address)
292
+ def self.do_geocode(address, options = {})
293
293
  address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
294
294
  url="http://api.local.yahoo.com/MapsService/V1/geocode?appid=#{Geokit::Geocoders::yahoo}&location=#{Geokit::Inflector::url_escape(address_str)}"
295
295
  res = self.call_geocoder_service(url)
@@ -335,7 +335,7 @@ module Geokit
335
335
  private
336
336
 
337
337
  # Template method which does the geocode lookup.
338
- def self.do_geocode(address)
338
+ def self.do_geocode(address, options = {})
339
339
  address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
340
340
  # geonames need a space seperated search string
341
341
  address_str.gsub!(/,/, " ")
@@ -400,15 +400,50 @@ module Geokit
400
400
  end
401
401
 
402
402
  # Template method which does the geocode lookup.
403
- 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]) : ''
404
429
  address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
405
- 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")
406
431
  return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
407
432
  xml = res.body
408
433
  logger.debug "Google geocoding. Address: #{address}. Result: #{xml}"
409
434
  return self.xml2GeoLoc(xml, address)
410
435
  end
411
436
 
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
+
412
447
  def self.xml2GeoLoc(xml, address="")
413
448
  doc=REXML::Document.new(xml)
414
449
 
@@ -460,6 +495,14 @@ module Geokit
460
495
  address_details=doc.elements['.//*[local-name() = "AddressDetails"]']
461
496
  res.accuracy = address_details ? address_details.attributes['Accuracy'].to_i : 0
462
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
+
463
506
  res.success=true
464
507
 
465
508
  return res
@@ -475,7 +518,7 @@ module Geokit
475
518
  class GeoPluginGeocoder < Geocoder
476
519
  private
477
520
 
478
- def self.do_geocode(ip)
521
+ def self.do_geocode(ip, options = {})
479
522
  return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
480
523
  response = self.call_geocoder_service("http://www.geoplugin.net/xml.gp?ip=#{ip}")
481
524
  return response.is_a?(Net::HTTPSuccess) ? parse_xml(response.body) : GeoLoc.new
@@ -508,7 +551,7 @@ module Geokit
508
551
  # Given an IP address, returns a GeoLoc instance which contains latitude,
509
552
  # longitude, city, and country code. Sets the success attribute to false if the ip
510
553
  # parameter does not match an ip address.
511
- def self.do_geocode(ip)
554
+ def self.do_geocode(ip, options = {})
512
555
  return GeoLoc.new if '0.0.0.0' == ip
513
556
  return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
514
557
  url = "http://api.hostip.info/get_html.php?ip=#{ip}&position=true"
@@ -563,14 +606,14 @@ module Geokit
563
606
  #
564
607
  # The failover approach is crucial for production-grade apps, but is rarely used.
565
608
  # 98% of your geocoding calls will be successful with the first call
566
- def self.do_geocode(address)
609
+ def self.do_geocode(address, options = {})
567
610
  geocode_ip = /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.match(address)
568
611
  provider_order = geocode_ip ? Geokit::Geocoders::ip_provider_order : Geokit::Geocoders::provider_order
569
612
 
570
613
  provider_order.each do |provider|
571
614
  begin
572
615
  klass = Geokit::Geocoders.const_get "#{Geokit::Inflector::camelize(provider.to_s)}Geocoder"
573
- res = klass.send :geocode, address
616
+ res = klass.send :geocode, address, options
574
617
  return res if res.success?
575
618
  rescue
576
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}")
@@ -579,6 +622,23 @@ module Geokit
579
622
  # If we get here, we failed completely.
580
623
  GeoLoc.new
581
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
582
642
  end
583
643
  end
584
644
  end
@@ -110,8 +110,8 @@ module Geokit
110
110
  end
111
111
 
112
112
  # Geocodes a location using the multi geocoder.
113
- def geocode(location)
114
- res = Geocoders::MultiGeocoder.geocode(location)
113
+ def geocode(location, options = {})
114
+ res = Geocoders::MultiGeocoder.geocode(location, options)
115
115
  return res if res.success?
116
116
  raise Geokit::Geocoders::GeocodeError
117
117
  end
@@ -292,6 +292,30 @@ module Geokit
292
292
  raise ArgumentError.new("#{thing} (#{thing.class}) cannot be normalized to a LatLng. We tried interpreting it as an array, string, Mappable, etc., but no dice.")
293
293
  end
294
294
 
295
+ # Reverse geocodes a LatLng object using the MultiGeocoder (default), or optionally
296
+ # using a geocoder of your choosing. Returns a new Geokit::GeoLoc object
297
+ #
298
+ # ==== Options
299
+ # * :using - Specifies the geocoder to use for reverse geocoding. Defaults to
300
+ # MultiGeocoder. Can be either the geocoder class (or any class that
301
+ # implements do_reverse_geocode for that matter), or the name of
302
+ # the class without the "Geocoder" part (e.g. :google)
303
+ #
304
+ # ==== Examples
305
+ # LatLng.new(51.4578329, 7.0166848).reverse_geocode # => #<Geokit::GeoLoc:0x12dac20 @state...>
306
+ # LatLng.new(51.4578329, 7.0166848).reverse_geocode(:using => :google) # => #<Geokit::GeoLoc:0x12dac20 @state...>
307
+ # LatLng.new(51.4578329, 7.0166848).reverse_geocode(:using => Geokit::Geocoders::GoogleGeocoder) # => #<Geokit::GeoLoc:0x12dac20 @state...>
308
+ def reverse_geocode(options = { :using => Geokit::Geocoders::MultiGeocoder })
309
+ if options[:using].is_a?(String) or options[:using].is_a?(Symbol)
310
+ provider = Geokit::Geocoders.const_get("#{Geokit::Inflector::camelize(options[:using].to_s)}Geocoder")
311
+ elsif options[:using].respond_to?(:do_reverse_geocode)
312
+ provider = options[:using]
313
+ else
314
+ raise ArgumentError.new("#{options[:using]} is not a valid geocoder.")
315
+ end
316
+
317
+ provider.send(:reverse_geocode, self)
318
+ end
295
319
  end
296
320
 
297
321
  # This class encapsulates the result of a geocoding call.
@@ -322,7 +346,7 @@ module Geokit
322
346
  # Attributes set upon return from geocoding. Success will be true for successful
323
347
  # geocode lookups. The provider will be set to the name of the providing geocoder.
324
348
  # Finally, precision is an indicator of the accuracy of the geocoding.
325
- attr_accessor :success, :provider, :precision
349
+ attr_accessor :success, :provider, :precision, :suggested_bounds
326
350
  # Street number and street name are extracted from the street address attribute.
327
351
  attr_reader :street_number, :street_name
328
352
  # accuracy is set for Yahoo and Google geocoders, it is a numeric value of the
@@ -387,8 +411,8 @@ module Geokit
387
411
  # Sets the street address after capitalizing each word within the street address.
388
412
  def street_address=(address)
389
413
  @street_address = Geokit::Inflector::titleize(address) if address
390
- end
391
-
414
+ end
415
+
392
416
  # Returns a comma-delimited string consisting of the street address, city, state,
393
417
  # zip, and country code. Only includes those attributes that are non-blank.
394
418
  def to_geocodeable_s
@@ -457,6 +481,16 @@ module Geokit
457
481
  other.is_a?(Bounds) ? self.sw == other.sw && self.ne == other.ne : false
458
482
  end
459
483
 
484
+ # Equivalent to Google Maps API's .toSpan() method on GLatLng's.
485
+ #
486
+ # Returns a LatLng object, whose coordinates represent the size of a rectangle
487
+ # defined by these bounds.
488
+ def to_span
489
+ lat_span = (@ne.lat - @sw.lat).abs
490
+ lng_span = (crosses_meridian? ? 360 + @ne.lng - @sw.lng : @ne.lng - @sw.lng).abs
491
+ Geokit::LatLng.new(lat_span, lng_span)
492
+ end
493
+
460
494
  class <<self
461
495
 
462
496
  # returns an instance of bounds which completely encompases the given circle
@@ -29,7 +29,8 @@ class BaseGeocoderTest < Test::Unit::TestCase #:nodoc: all
29
29
  @full_address = '100 Spear St, San Francisco, CA, 94105-1522, US'
30
30
  @full_address_short_zip = '100 Spear St, San Francisco, CA, 94105, US'
31
31
 
32
- @success = Geokit::GeoLoc.new({:city=>"SAN FRANCISCO", :state=>"CA", :country_code=>"US", :lat=>37.7742, :lng=>-122.417068})
32
+ @latlng = Geokit::LatLng.new(37.7742, -122.417068)
33
+ @success = Geokit::GeoLoc.new({:city=>"SAN FRANCISCO", :state=>"CA", :country_code=>"US", :lat=>@latlng.lat, :lng=>@latlng.lng})
33
34
  @success.success = true
34
35
  end
35
36
 
@@ -71,4 +71,27 @@ class BoundsTest < Test::Unit::TestCase #:nodoc: all
71
71
  assert !bounds.contains?(outside)
72
72
  end
73
73
 
74
+ def test_bounds_to_span
75
+ sw = Geokit::LatLng.new(32, -96)
76
+ ne = Geokit::LatLng.new(40, -70)
77
+ bounds = Geokit::Bounds.new(sw, ne)
78
+
79
+ assert_equal Geokit::LatLng.new(8, 26), bounds.to_span
80
+ end
81
+
82
+ def test_bounds_to_span_with_bounds_crossing_prime_meridian
83
+ sw = Geokit::LatLng.new(20, -70)
84
+ ne = Geokit::LatLng.new(40, 100)
85
+ bounds = Geokit::Bounds.new(sw, ne)
86
+
87
+ assert_equal Geokit::LatLng.new(20, 170), bounds.to_span
88
+ end
89
+
90
+ def test_bounds_to_span_with_bounds_crossing_dateline
91
+ sw = Geokit::LatLng.new(20, 100)
92
+ ne = Geokit::LatLng.new(40, -70)
93
+ bounds = Geokit::Bounds.new(sw, ne)
94
+
95
+ assert_equal Geokit::LatLng.new(20, 190), bounds.to_span
96
+ end
74
97
  end
@@ -7,6 +7,10 @@ class GoogleGeocoderTest < BaseGeocoderTest #:nodoc: all
7
7
  GOOGLE_FULL=<<-EOF.strip
8
8
  <?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://earth.google.com/kml/2.0"><Response><name>100 spear st, san francisco, ca</name><Status><code>200</code><request>geocode</request></Status><Placemark><address>100 Spear St, San Francisco, CA 94105, USA</address><AddressDetails Accuracy="8" xmlns="urn:oasis:names:tc:ciq:xsdschema:xAL:2.0"><Country><CountryNameCode>US</CountryNameCode><AdministrativeArea><AdministrativeAreaName>CA</AdministrativeAreaName><SubAdministrativeArea><SubAdministrativeAreaName>San Francisco</SubAdministrativeAreaName><Locality><LocalityName>San Francisco</LocalityName><Thoroughfare><ThoroughfareName>100 Spear St</ThoroughfareName></Thoroughfare><PostalCode><PostalCodeNumber>94105</PostalCodeNumber></PostalCode></Locality></SubAdministrativeArea></AdministrativeArea></Country></AddressDetails><Point><coordinates>-122.393985,37.792501,0</coordinates></Point></Placemark></Response></kml>
9
9
  EOF
10
+
11
+ GOOGLE_RESULT_WITH_SUGGESTED_BOUNDS=<<-EOF.strip
12
+ <?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://earth.google.com/kml/2.0"><Response><name>100 spear st, san francisco, ca</name><Status><code>200</code><request>geocode</request></Status><Placemark><address>100 Spear St, San Francisco, CA 94105, USA</address><AddressDetails Accuracy="8" xmlns="urn:oasis:names:tc:ciq:xsdschema:xAL:2.0"><Country><CountryNameCode>US</CountryNameCode><AdministrativeArea><AdministrativeAreaName>CA</AdministrativeAreaName><SubAdministrativeArea><SubAdministrativeAreaName>San Francisco</SubAdministrativeAreaName><Locality><LocalityName>San Francisco</LocalityName><Thoroughfare><ThoroughfareName>100 Spear St</ThoroughfareName></Thoroughfare><PostalCode><PostalCodeNumber>94105</PostalCodeNumber></PostalCode></Locality></SubAdministrativeArea></AdministrativeArea></Country></AddressDetails><ExtendedData><LatLonBox north="37.7956328" south="37.7893376" east="-122.3908573" west="-122.3971525" /></ExtendedData><Point><coordinates>-122.393985,37.792501,0</coordinates></Point></Placemark></Response></kml>
13
+ EOF
10
14
 
11
15
  GOOGLE_CITY=<<-EOF.strip
12
16
  <?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://earth.google.com/kml/2.0"><Response><name>San Francisco</name><Status><code>200</code><request>geocode</request></Status><Placemark><address>San Francisco, CA, USA</address><AddressDetails Accuracy="4" xmlns="urn:oasis:names:tc:ciq:xsdschema:xAL:2.0"><Country><CountryNameCode>US</CountryNameCode><AdministrativeArea><AdministrativeAreaName>CA</AdministrativeAreaName><Locality><LocalityName>San Francisco</LocalityName></Locality></AdministrativeArea></Country></AddressDetails><Point><coordinates>-122.418333,37.775000,0</coordinates></Point></Placemark></Response></kml>
@@ -14,6 +18,14 @@ class GoogleGeocoderTest < BaseGeocoderTest #:nodoc: all
14
18
 
15
19
  GOOGLE_MULTI="<?xml version='1.0' encoding='UTF-8'?>\n<kml xmlns='http://earth.google.com/kml/2.0'><Response>\n <name>via Sandro Pertini 8, Ossona, MI</name>\n <Status>\n <code>200</code>\n <request>geocode</request>\n </Status>\n <Placemark id='p1'>\n <address>Via Sandro Pertini, 8, 20010 Mesero MI, Italy</address>\n <AddressDetails Accuracy='8' xmlns='urn:oasis:names:tc:ciq:xsdschema:xAL:2.0'><Country><CountryNameCode>IT</CountryNameCode><CountryName>Italy</CountryName><AdministrativeArea><AdministrativeAreaName>Lombardy</AdministrativeAreaName><SubAdministrativeArea><SubAdministrativeAreaName>Milan</SubAdministrativeAreaName><Locality><LocalityName>Mesero</LocalityName><Thoroughfare><ThoroughfareName>8 Via Sandro Pertini</ThoroughfareName></Thoroughfare><PostalCode><PostalCodeNumber>20010</PostalCodeNumber></PostalCode></Locality></SubAdministrativeArea></AdministrativeArea></Country></AddressDetails>\n <Point><coordinates>8.8527131,45.4966243,0</coordinates></Point>\n </Placemark>\n <Placemark id='p2'>\n <address>Via Sandro Pertini, 20010 Ossona MI, Italy</address>\n <AddressDetails Accuracy='6' xmlns='urn:oasis:names:tc:ciq:xsdschema:xAL:2.0'><Country><CountryNameCode>IT</CountryNameCode><CountryName>Italy</CountryName><AdministrativeArea><AdministrativeAreaName>Lombardy</AdministrativeAreaName><SubAdministrativeArea><SubAdministrativeAreaName>Milan</SubAdministrativeAreaName><Locality><LocalityName>Ossona</LocalityName><Thoroughfare><ThoroughfareName>Via Sandro Pertini</ThoroughfareName></Thoroughfare><PostalCode><PostalCodeNumber>20010</PostalCodeNumber></PostalCode></Locality></SubAdministrativeArea></AdministrativeArea></Country></AddressDetails>\n <Point><coordinates>8.9023200,45.5074444,0</coordinates></Point>\n </Placemark>\n</Response></kml>\n"
16
20
 
21
+ GOOGLE_COUNTRY_CODE_BIASED_RESULT = <<-EOF.strip
22
+ <?xml version="1.0" encoding="UTF-8" ?><kml xmlns="http://earth.google.com/kml/2.0"><Response><name>Syracuse</name><Status><code>200</code><request>geocode</request></Status><Placemark id="p1"><address>Syracuse, Italy</address><AddressDetails Accuracy="3" xmlns="urn:oasis:names:tc:ciq:xsdschema:xAL:2.0"><Country><CountryNameCode>IT</CountryNameCode><CountryName>Italy</CountryName><AdministrativeArea><AdministrativeAreaName>Sicily</AdministrativeAreaName><SubAdministrativeArea><SubAdministrativeAreaName>Syracuse</SubAdministrativeAreaName></SubAdministrativeArea></AdministrativeArea></Country></AddressDetails><ExtendedData><LatLonBox north="37.4125978" south="36.6441736" east="15.3367367" west="14.7724913" /></ExtendedData><Point><coordinates>14.9856176,37.0630218,0</coordinates></Point></Placemark></Response></kml>
23
+ EOF
24
+
25
+ GOOGLE_BOUNDS_BIASED_RESULT = <<-EOF.strip
26
+ <?xml version="1.0" encoding="UTF-8" ?><kml xmlns="http://earth.google.com/kml/2.0"><Response><name>Winnetka</name><Status><code>200</code><request>geocode</request></Status><Placemark id="p1"><address>Winnetka, California, USA</address><AddressDetails Accuracy="4" xmlns="urn:oasis:names:tc:ciq:xsdschema:xAL:2.0"><Country><CountryNameCode>US</CountryNameCode><CountryName>USA</CountryName><AdministrativeArea><AdministrativeAreaName>CA</AdministrativeAreaName><AddressLine>Winnetka</AddressLine></AdministrativeArea></Country></AddressDetails><ExtendedData><LatLonBox north="34.2353090" south="34.1791050" east="-118.5534191" west="-118.5883200" /></ExtendedData><Point><coordinates>-118.5710220,34.2131710,0</coordinates></Point></Placemark></Response></kml>
27
+ EOF
28
+
17
29
  def setup
18
30
  super
19
31
  @google_full_hash = {:street_address=>"100 Spear St", :city=>"San Francisco", :state=>"CA", :zip=>"94105", :country_code=>"US"}
@@ -99,6 +111,17 @@ class GoogleGeocoderTest < BaseGeocoderTest #:nodoc: all
99
111
  assert_equal "google", res.provider
100
112
  end
101
113
 
114
+ def test_google_suggested_bounds
115
+ response = MockSuccess.new
116
+ response.expects(:body).returns(GOOGLE_RESULT_WITH_SUGGESTED_BOUNDS)
117
+ url = "http://maps.google.com/maps/geo?q=#{Geokit::Inflector.url_escape(@full_address_short_zip)}&output=xml&key=Google&oe=utf-8"
118
+ Geokit::Geocoders::GoogleGeocoder.expects(:call_geocoder_service).with(url).returns(response)
119
+ res = Geokit::Geocoders::GoogleGeocoder.geocode(@google_full_loc)
120
+
121
+ assert_instance_of Geokit::Bounds, res.suggested_bounds
122
+ assert_equal Geokit::Bounds.new(Geokit::LatLng.new(37.7893376, -122.3971525), Geokit::LatLng.new(37.7956328, -122.3908573)), res.suggested_bounds
123
+ end
124
+
102
125
  def test_service_unavailable
103
126
  response = MockFailure.new
104
127
  url = "http://maps.google.com/maps/geo?q=#{Geokit::Inflector.url_escape(@address)}&output=xml&key=Google&oe=utf-8"
@@ -131,4 +154,30 @@ class GoogleGeocoderTest < BaseGeocoderTest #:nodoc: all
131
154
  assert_equal "Via Sandro Pertini", res.street_address
132
155
  assert_equal "google", res.provider
133
156
  end
157
+
158
+ def test_country_code_biasing
159
+ response = MockSuccess.new
160
+ response.expects(:body).returns(GOOGLE_COUNTRY_CODE_BIASED_RESULT)
161
+
162
+ url = "http://maps.google.com/maps/geo?q=Syracuse&output=xml&gl=it&key=Google&oe=utf-8"
163
+ Geokit::Geocoders::GoogleGeocoder.expects(:call_geocoder_service).with(url).returns(response)
164
+ biased_result = Geokit::Geocoders::GoogleGeocoder.geocode('Syracuse', :bias => 'it')
165
+
166
+ assert_equal 'IT', biased_result.country_code
167
+ assert_equal 'Sicily', biased_result.state
168
+ end
169
+
170
+ def test_bounds_biasing
171
+ response = MockSuccess.new
172
+ response.expects(:body).returns(GOOGLE_BOUNDS_BIASED_RESULT)
173
+
174
+ url = "http://maps.google.com/maps/geo?q=Winnetka&output=xml&ll=34.197693208849,-118.547160027785&spn=0.247047999999999,0.294914000000006&key=Google&oe=utf-8"
175
+ Geokit::Geocoders::GoogleGeocoder.expects(:call_geocoder_service).with(url).returns(response)
176
+
177
+ bounds = Geokit::Bounds.normalize([34.074081, -118.694401], [34.321129, -118.399487])
178
+ biased_result = Geokit::Geocoders::GoogleGeocoder.geocode('Winnetka', :bias => bounds)
179
+
180
+ assert_equal 'US', biased_result.country_code
181
+ assert_equal 'CA', biased_result.state
182
+ end
134
183
  end
@@ -1,5 +1,6 @@
1
1
  require 'test/unit'
2
2
  require 'lib/geokit'
3
+ require 'mocha'
3
4
 
4
5
  class LatLngTest < Test::Unit::TestCase #:nodoc: all
5
6
 
@@ -9,6 +10,25 @@ class LatLngTest < Test::Unit::TestCase #:nodoc: all
9
10
  @point = Geokit::LatLng.new(@loc_a.lat, @loc_a.lng)
10
11
  end
11
12
 
13
+ def valid_reverse_geocoding_result
14
+ location = Geokit::GeoLoc.new({
15
+ :city => "Essen",
16
+ :country_code => "DE",
17
+ :lat => 51.4578329,
18
+ :lng => 7.0166848,
19
+ :provider => "google",
20
+ :state => "Nordrhein-Westfalen",
21
+ :street_address => "Porscheplatz 1",
22
+ :zip => "45127"
23
+ })
24
+
25
+ location.full_address = "Porscheplatz 1, 45127 Essen, Deutschland"
26
+ location.precision = 'address'
27
+ location.provider = 'google'
28
+ location.success = true
29
+ location
30
+ end
31
+
12
32
  def test_distance_between_same_using_defaults
13
33
  assert_equal 0, Geokit::LatLng.distance_between(@loc_a, @loc_a)
14
34
  assert_equal 0, @loc_a.distance_to(@loc_a)
@@ -145,4 +165,45 @@ class LatLngTest < Test::Unit::TestCase #:nodoc: all
145
165
  assert first.eql?(second)
146
166
  assert second.eql?(first)
147
167
  end
168
+
169
+ def test_reverse_geocode
170
+ point = Geokit::LatLng.new(51.4578329, 7.0166848)
171
+ Geokit::Geocoders::MultiGeocoder.expects(:reverse_geocode).with(point).returns(valid_reverse_geocoding_result)
172
+ res = point.reverse_geocode
173
+
174
+ assert_equal "Nordrhein-Westfalen", res.state
175
+ assert_equal "Essen", res.city
176
+ assert_equal "45127", res.zip
177
+ assert_equal "51.4578329,7.0166848", res.ll # slightly dif from yahoo
178
+ assert res.is_us? == false
179
+ assert_equal "Porscheplatz 1, 45127 Essen, Deutschland", res.full_address #slightly different from yahoo
180
+ end
181
+
182
+ def test_reverse_geocoding_using_specific_geocoder
183
+ point = Geokit::LatLng.new(51.4578329, 7.0166848)
184
+ Geokit::Geocoders::GoogleGeocoder.expects(:reverse_geocode).with(point).returns(valid_reverse_geocoding_result)
185
+ res = point.reverse_geocode(:using => Geokit::Geocoders::GoogleGeocoder)
186
+
187
+ assert_equal "Nordrhein-Westfalen", res.state
188
+ assert_equal "Essen", res.city
189
+ assert_equal "45127", res.zip
190
+ assert_equal "51.4578329,7.0166848", res.ll # slightly dif from yahoo
191
+ assert res.is_us? == false
192
+ assert_equal "Porscheplatz 1, 45127 Essen, Deutschland", res.full_address #slightly different from yahoo
193
+ assert_equal "google", res.provider
194
+ end
195
+
196
+ def test_reverse_geocoding_using_specific_geocoder_short_syntax
197
+ point = Geokit::LatLng.new(51.4578329, 7.0166848)
198
+ Geokit::Geocoders::GoogleGeocoder.expects(:reverse_geocode).with(point).returns(valid_reverse_geocoding_result)
199
+ res = point.reverse_geocode(:using => :google)
200
+
201
+ assert_equal "Nordrhein-Westfalen", res.state
202
+ assert_equal "Essen", res.city
203
+ assert_equal "45127", res.zip
204
+ assert_equal "51.4578329,7.0166848", res.ll # slightly dif from yahoo
205
+ assert res.is_us? == false
206
+ assert_equal "Porscheplatz 1, 45127 Essen, Deutschland", res.full_address #slightly different from yahoo
207
+ assert_equal "google", res.provider
208
+ end
148
209
  end
@@ -10,27 +10,27 @@ class MultiGeocoderTest < BaseGeocoderTest #:nodoc: all
10
10
  end
11
11
 
12
12
  def test_successful_first
13
- Geokit::Geocoders::GoogleGeocoder.expects(:geocode).with(@address).returns(@success)
13
+ Geokit::Geocoders::GoogleGeocoder.expects(:geocode).with(@address, {}).returns(@success)
14
14
  assert_equal @success, Geokit::Geocoders::MultiGeocoder.geocode(@address)
15
15
  end
16
16
 
17
17
  def test_failover
18
- Geokit::Geocoders::GoogleGeocoder.expects(:geocode).with(@address).returns(@failure)
19
- Geokit::Geocoders::YahooGeocoder.expects(:geocode).with(@address).returns(@success)
18
+ Geokit::Geocoders::GoogleGeocoder.expects(:geocode).with(@address, {}).returns(@failure)
19
+ Geokit::Geocoders::YahooGeocoder.expects(:geocode).with(@address, {}).returns(@success)
20
20
  assert_equal @success, Geokit::Geocoders::MultiGeocoder.geocode(@address)
21
21
  end
22
22
 
23
23
  def test_double_failover
24
- Geokit::Geocoders::GoogleGeocoder.expects(:geocode).with(@address).returns(@failure)
25
- Geokit::Geocoders::YahooGeocoder.expects(:geocode).with(@address).returns(@failure)
26
- Geokit::Geocoders::UsGeocoder.expects(:geocode).with(@address).returns(@success)
24
+ Geokit::Geocoders::GoogleGeocoder.expects(:geocode).with(@address, {}).returns(@failure)
25
+ Geokit::Geocoders::YahooGeocoder.expects(:geocode).with(@address, {}).returns(@failure)
26
+ Geokit::Geocoders::UsGeocoder.expects(:geocode).with(@address, {}).returns(@success)
27
27
  assert_equal @success, Geokit::Geocoders::MultiGeocoder.geocode(@address)
28
28
  end
29
29
 
30
30
  def test_failure
31
- Geokit::Geocoders::GoogleGeocoder.expects(:geocode).with(@address).returns(@failure)
32
- Geokit::Geocoders::YahooGeocoder.expects(:geocode).with(@address).returns(@failure)
33
- Geokit::Geocoders::UsGeocoder.expects(:geocode).with(@address).returns(@failure)
31
+ Geokit::Geocoders::GoogleGeocoder.expects(:geocode).with(@address, {}).returns(@failure)
32
+ Geokit::Geocoders::YahooGeocoder.expects(:geocode).with(@address, {}).returns(@failure)
33
+ Geokit::Geocoders::UsGeocoder.expects(:geocode).with(@address, {}).returns(@failure)
34
34
  assert_equal @failure, Geokit::Geocoders::MultiGeocoder.geocode(@address)
35
35
  end
36
36
 
@@ -45,10 +45,49 @@ class MultiGeocoderTest < BaseGeocoderTest #:nodoc: all
45
45
  t1, t2 = Geokit::Geocoders.provider_order, Geokit::Geocoders.ip_provider_order # will need to reset after
46
46
  Geokit::Geocoders.provider_order = [:google]
47
47
  Geokit::Geocoders.ip_provider_order = [:geo_plugin]
48
- Geokit::Geocoders::GoogleGeocoder.expects(:geocode).with("").returns(@failure)
48
+ Geokit::Geocoders::GoogleGeocoder.expects(:geocode).with("", {}).returns(@failure)
49
49
  Geokit::Geocoders::GeoPluginGeocoder.expects(:geocode).never
50
50
  assert_equal @failure, Geokit::Geocoders::MultiGeocoder.geocode("")
51
51
  Geokit::Geocoders.provider_order, Geokit::Geocoders.ip_provider_order = t1, t2 # reset to orig values
52
52
  end
53
53
 
54
+ def test_reverse_geocode_successful_first
55
+ Geokit::Geocoders::GoogleGeocoder.expects(:reverse_geocode).with(@latlng).returns(@success)
56
+ assert_equal @success, Geokit::Geocoders::MultiGeocoder.reverse_geocode(@latlng)
57
+ end
58
+
59
+ def test_reverse_geocode_failover
60
+ Geokit::Geocoders::GoogleGeocoder.expects(:reverse_geocode).with(@latlng).returns(@failure)
61
+ Geokit::Geocoders::YahooGeocoder.expects(:reverse_geocode).with(@latlng).returns(@success)
62
+ assert_equal @success, Geokit::Geocoders::MultiGeocoder.reverse_geocode(@latlng)
63
+ end
64
+
65
+ def test_reverse_geocode_double_failover
66
+ Geokit::Geocoders::GoogleGeocoder.expects(:reverse_geocode).with(@latlng).returns(@failure)
67
+ Geokit::Geocoders::YahooGeocoder.expects(:reverse_geocode).with(@latlng).returns(@failure)
68
+ Geokit::Geocoders::UsGeocoder.expects(:reverse_geocode).with(@latlng).returns(@success)
69
+ assert_equal @success, Geokit::Geocoders::MultiGeocoder.reverse_geocode(@latlng)
70
+ end
71
+
72
+ def test_reverse_geocode_failure
73
+ Geokit::Geocoders::GoogleGeocoder.expects(:reverse_geocode).with(@latlng).returns(@failure)
74
+ Geokit::Geocoders::YahooGeocoder.expects(:reverse_geocode).with(@latlng).returns(@failure)
75
+ Geokit::Geocoders::UsGeocoder.expects(:reverse_geocode).with(@latlng).returns(@failure)
76
+ assert_equal @failure, Geokit::Geocoders::MultiGeocoder.reverse_geocode(@latlng)
77
+ end
78
+
79
+ def test_reverse_geocode_with_invalid_provider
80
+ temp = Geokit::Geocoders::provider_order
81
+ Geokit::Geocoders.provider_order = [:bogus]
82
+ assert_equal @failure, Geokit::Geocoders::MultiGeocoder.reverse_geocode(@latlng)
83
+ Geokit::Geocoders.provider_order = temp
84
+ end
85
+
86
+ def test_reverse_geocode_with_blank_latlng
87
+ t1 = Geokit::Geocoders.provider_order # will need to reset after
88
+ Geokit::Geocoders.provider_order = [:google]
89
+ Geokit::Geocoders::GoogleGeocoder.expects(:reverse_geocode).with("").returns(@failure)
90
+ assert_equal @failure, Geokit::Geocoders::MultiGeocoder.reverse_geocode("")
91
+ Geokit::Geocoders.provider_order = t1 # reset to orig values
92
+ end
54
93
  end
@@ -12,19 +12,19 @@ class MultiIpGeocoderTest < BaseGeocoderTest #:nodoc: all
12
12
  end
13
13
 
14
14
  def test_successful_first
15
- Geokit::Geocoders::GeoPluginGeocoder.expects(:geocode).with(@ip_address).returns(@success)
15
+ Geokit::Geocoders::GeoPluginGeocoder.expects(:geocode).with(@ip_address, {}).returns(@success)
16
16
  assert_equal @success, Geokit::Geocoders::MultiGeocoder.geocode(@ip_address)
17
17
  end
18
18
 
19
19
  def test_failover
20
- Geokit::Geocoders::GeoPluginGeocoder.expects(:geocode).with(@ip_address).returns(@failure)
21
- Geokit::Geocoders::IpGeocoder.expects(:geocode).with(@ip_address).returns(@success)
20
+ Geokit::Geocoders::GeoPluginGeocoder.expects(:geocode).with(@ip_address, {}).returns(@failure)
21
+ Geokit::Geocoders::IpGeocoder.expects(:geocode).with(@ip_address, {}).returns(@success)
22
22
  assert_equal @success, Geokit::Geocoders::MultiGeocoder.geocode(@ip_address)
23
23
  end
24
24
 
25
25
  def test_failure
26
- Geokit::Geocoders::GeoPluginGeocoder.expects(:geocode).with(@ip_address).returns(@failure)
27
- Geokit::Geocoders::IpGeocoder.expects(:geocode).with(@ip_address).returns(@failure)
26
+ Geokit::Geocoders::GeoPluginGeocoder.expects(:geocode).with(@ip_address, {}).returns(@failure)
27
+ Geokit::Geocoders::IpGeocoder.expects(:geocode).with(@ip_address, {}).returns(@failure)
28
28
  assert_equal @failure, Geokit::Geocoders::MultiGeocoder.geocode(@ip_address)
29
29
  end
30
30
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: geokit
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.2
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andre Lewis
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-05-27 00:00:00 -07:00
12
+ date: 2009-06-08 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency