andre-geokit 1.2.3 → 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,7 +3,7 @@
3
3
  The Geokit gem provides:
4
4
 
5
5
  * Distance calculations between two points on the earth. Calculate the distance in miles, kilometers, or nautical miles, with all the trigonometry abstracted away by GeoKit.
6
- * Geocoding from multiple providers. It currently supports Google, Yahoo, Geocoder.us, and Geocoder.ca geocoders, and it provides a uniform response structure from all of them.
6
+ * Geocoding from multiple providers. It supports Google, Yahoo, Geocoder.us, and Geocoder.ca geocoders, and others. It provides a uniform response structure from all of them.
7
7
  It also provides a fail-over mechanism, in case your input fails to geocode in one service.
8
8
  * Rectangular bounds calculations: is a point within a given rectangular bounds?
9
9
  * Heading and midpoint calculations
@@ -45,7 +45,7 @@ See the RDOC more more ... there are also operations on rectangular bounds (e.g.
45
45
 
46
46
  ## CONFIGURATION
47
47
 
48
- If you're using this gem by itself, here's how to set configurations:
48
+ If you're using this gem by itself, here are the configuration options:
49
49
 
50
50
  # These defaults are used in Geokit::Mappable.distance_to and in acts_as_mappable
51
51
  Geokit::default_units = :miles
@@ -110,7 +110,7 @@ creates a template with these settings and places it in `config/initializers/geo
110
110
  * Geonames - a free geocoder
111
111
 
112
112
  ### address geocoders that also provide reverse geocoding
113
- * Google Geocoder - requires an API key.
113
+ * Google Geocoder - requires an API key. Also supports multiple results.
114
114
 
115
115
  ### IP address geocoders
116
116
  * IP Geocoder - geocodes an IP address using hostip.info's web service.
@@ -119,6 +119,25 @@ creates a template with these settings and places it in `config/initializers/geo
119
119
  ### The Multigeocoder
120
120
  * Multi Geocoder - provides failover for the physical location geocoders.
121
121
 
122
+ ## MULTIPLE RESULTS
123
+ Some geocoding services will return multple results if the there isn't one clear result.
124
+ Geoloc can capture multiple results through its "all" method. Currently only the Google geocoder
125
+ supports multiple results:
126
+
127
+ irb> geo=Geokit::Geocoders::GoogleGeocoder.geocode("900 Sycamore Drive")
128
+ irb> geo.full_address
129
+ => "900 Sycamore Dr, Arkadelphia, AR 71923, USA"
130
+ irb> geo.all.size
131
+ irb> geo.all.each { |e| puts e.full_address }
132
+ 900 Sycamore Dr, Arkadelphia, AR 71923, USA
133
+ 900 Sycamore Dr, Burkburnett, TX 76354, USA
134
+ 900 Sycamore Dr, TN 38361, USA
135
+ ....
136
+
137
+ geo.all is just an array of additional Geolocs, so do what you want with it. If you call .all on a
138
+ geoloc that doesn't have any additional results, you will get an array of one.
139
+
140
+
122
141
  ## NOTES ON WHAT'S WHERE
123
142
 
124
143
  mappable.rb contains the Mappable module, which provides basic
@@ -21,6 +21,8 @@ module Geokit
21
21
  end
22
22
  end
23
23
 
24
+ path = File.expand_path(File.dirname(__FILE__))
25
+ $: << path unless $:.include?(path)
24
26
  require 'geokit/geocoders'
25
27
  require 'geokit/mappable'
26
28
 
@@ -220,7 +220,6 @@ module Geokit
220
220
  end
221
221
  end
222
222
 
223
-
224
223
  # Geocoder Us geocoder implementation. Requires the Geokit::Geocoders::GEOCODER_US variable to
225
224
  # contain true or false based upon whether authentication is to occur. Conforms to the
226
225
  # interface set by the Geocoder class.
@@ -397,30 +396,21 @@ module Geokit
397
396
  doc=REXML::Document.new(xml)
398
397
 
399
398
  if doc.elements['//kml/Response/Status/code'].text == '200'
400
- res = GeoLoc.new
401
- coordinates=doc.elements['//coordinates'].text.to_s.split(',')
402
-
403
- #basics
404
- res.lat=coordinates[1]
405
- res.lng=coordinates[0]
406
- res.country_code=doc.elements['//CountryNameCode'].text if doc.elements['//CountryNameCode']
407
- res.provider='google'
408
-
409
- #extended -- false if not not available
410
- res.city = doc.elements['//LocalityName'].text if doc.elements['//LocalityName']
411
- res.state = doc.elements['//AdministrativeAreaName'].text if doc.elements['//AdministrativeAreaName']
412
- res.full_address = doc.elements['//address'].text if doc.elements['//address'] # google provides it
413
- res.zip = doc.elements['//PostalCodeNumber'].text if doc.elements['//PostalCodeNumber']
414
- res.street_address = doc.elements['//ThoroughfareName'].text if doc.elements['//ThoroughfareName']
415
- # Translate accuracy into Yahoo-style token address, street, zip, zip+4, city, state, country
416
- # For Google, 1=low accuracy, 8=high accuracy
417
- # old way -- address_details=doc.elements['//AddressDetails','urn:oasis:names:tc:ciq:xsdschema:xAL:2.0']
418
- address_details=doc.elements['//*[local-name() = "AddressDetails"]']
419
- accuracy = address_details ? address_details.attributes['Accuracy'].to_i : 0
420
- res.precision=%w{unknown country state state city zip zip+4 street address}[accuracy]
421
- res.success=true
422
-
423
- return res
399
+ geoloc = nil
400
+ # Google can return multiple results as //Placemark elements.
401
+ # iterate through each and extract each placemark as a geoloc
402
+ doc.each_element('//Placemark') do |e|
403
+ extracted_geoloc = extract_placemark(e) # g is now an instance of Geoloc
404
+ if geoloc.nil?
405
+ # first time through, geoloc is still nill, so we make it the geoloc we just extracted
406
+ geoloc = extracted_geoloc
407
+ else
408
+ # second (and subsequent) iterations, we push additional
409
+ # geolocs onto "geoloc.all"
410
+ geoloc.all.push(extracted_geoloc)
411
+ end
412
+ end
413
+ return geoloc
424
414
  else
425
415
  logger.info "Google was unable to geocode address: "+address
426
416
  return GeoLoc.new
@@ -430,6 +420,33 @@ module Geokit
430
420
  logger.error "Caught an error during Google geocoding call: "+$!
431
421
  return GeoLoc.new
432
422
  end
423
+
424
+ # extracts a single geoloc from a //placemark element in the google results xml
425
+ def self.extract_placemark(doc)
426
+ res = GeoLoc.new
427
+ coordinates=doc.elements['.//coordinates'].text.to_s.split(',')
428
+
429
+ #basics
430
+ res.lat=coordinates[1]
431
+ res.lng=coordinates[0]
432
+ res.country_code=doc.elements['.//CountryNameCode'].text if doc.elements['.//CountryNameCode']
433
+ res.provider='google'
434
+
435
+ #extended -- false if not not available
436
+ res.city = doc.elements['.//LocalityName'].text if doc.elements['.//LocalityName']
437
+ res.state = doc.elements['.//AdministrativeAreaName'].text if doc.elements['.//AdministrativeAreaName']
438
+ res.full_address = doc.elements['.//address'].text if doc.elements['.//address'] # google provides it
439
+ res.zip = doc.elements['.//PostalCodeNumber'].text if doc.elements['.//PostalCodeNumber']
440
+ res.street_address = doc.elements['.//ThoroughfareName'].text if doc.elements['.//ThoroughfareName']
441
+ # Translate accuracy into Yahoo-style token address, street, zip, zip+4, city, state, country
442
+ # For Google, 1=low accuracy, 8=high accuracy
443
+ 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]
446
+ res.success=true
447
+
448
+ return res
449
+ end
433
450
  end
434
451
 
435
452
 
@@ -475,6 +492,7 @@ module Geokit
475
492
  # longitude, city, and country code. Sets the success attribute to false if the ip
476
493
  # parameter does not match an ip address.
477
494
  def self.do_geocode(ip)
495
+ return Geoloc.new if '0.0.0.0' == ip
478
496
  return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
479
497
  url = "http://api.hostip.info/get_html.php?ip=#{ip}&position=true"
480
498
  response = self.call_geocoder_service(url)
@@ -505,7 +523,7 @@ module Geokit
505
523
  res
506
524
  end
507
525
  end
508
-
526
+
509
527
  # -------------------------------------------------------------------------------------------
510
528
  # The Multi Geocoder
511
529
  # -------------------------------------------------------------------------------------------
@@ -1,3 +1,4 @@
1
+ #require 'forwardable'
1
2
 
2
3
  module Geokit
3
4
  # Contains class and instance methods providing distance calcuation services. This
@@ -285,15 +286,31 @@ module Geokit
285
286
 
286
287
  end
287
288
 
288
- # This class encapsulates the result of a geocoding call
289
+ # This class encapsulates the result of a geocoding call.
289
290
  # It's primary purpose is to homogenize the results of multiple
290
291
  # geocoding providers. It also provides some additional functionality, such as
291
292
  # the "full address" method for geocoders that do not provide a
292
293
  # full address in their results (for example, Yahoo), and the "is_us" method.
294
+ #
295
+ # Some geocoders can return multple results. Geoloc can capture multiple results through
296
+ # its "all" method.
297
+ #
298
+ # For the geocoder setting the results, it would look something like this:
299
+ # geo=GeoLoc.new(first_result)
300
+ # geo.all.push(second_result)
301
+ # geo.all.push(third_result)
302
+ #
303
+ # Then, for the user of the result:
304
+ #
305
+ # puts geo.full_address # just like usual
306
+ # puts geo.all.size => 3 # there's three results total
307
+ # puts geo.all.first # all is just an array or additional geolocs,
308
+ # so do what you want with it
293
309
  class GeoLoc < LatLng
310
+
294
311
  # Location attributes. Full address is a concatenation of all values. For example:
295
312
  # 100 Spear St, San Francisco, CA, 94101, US
296
- attr_accessor :street_address, :city, :state, :zip, :country_code, :full_address
313
+ attr_accessor :street_address, :city, :state, :zip, :country_code, :full_address, :all
297
314
  # Attributes set upon return from geocoding. Success will be true for successful
298
315
  # geocode lookups. The provider will be set to the name of the providing geocoder.
299
316
  # Finally, precision is an indicator of the accuracy of the geocoding.
@@ -303,6 +320,8 @@ module Geokit
303
320
 
304
321
  # Constructor expects a hash of symbols to correspond with attributes.
305
322
  def initialize(h={})
323
+ @all = [self]
324
+
306
325
  @street_address=h[:street_address]
307
326
  @city=h[:city]
308
327
  @state=h[:state]
@@ -362,6 +381,10 @@ module Geokit
362
381
  a.delete_if { |e| !e || e == '' }
363
382
  a.join(', ')
364
383
  end
384
+
385
+ def to_yaml_properties
386
+ (instance_variables - ['@results']).sort
387
+ end
365
388
 
366
389
  # Returns a string representation of the instance.
367
390
  def to_s
@@ -46,4 +46,9 @@ class GeoLocTest < Test::Unit::TestCase #:nodoc: all
46
46
  @another = Geokit::GeoLoc.new @loc.to_hash
47
47
  assert_equal @loc, @another
48
48
  end
49
+
50
+ def test_all
51
+ assert_equal [@loc], @loc.all
52
+ end
53
+
49
54
  end
@@ -12,6 +12,8 @@ class GoogleGeocoderTest < BaseGeocoderTest #:nodoc: all
12
12
  <?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>
13
13
  EOF
14
14
 
15
+ 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
+
15
17
  def setup
16
18
  super
17
19
  @google_full_hash = {:street_address=>"100 Spear St", :city=>"San Francisco", :state=>"CA", :zip=>"94105", :country_code=>"US"}
@@ -85,4 +87,30 @@ class GoogleGeocoderTest < BaseGeocoderTest #:nodoc: all
85
87
  Geokit::Geocoders::GoogleGeocoder.expects(:call_geocoder_service).with(url).returns(response)
86
88
  assert !Geokit::Geocoders::GoogleGeocoder.geocode(@google_city_loc).success
87
89
  end
88
- end
90
+
91
+ def test_multiple_results
92
+ #Geokit::Geocoders::GoogleGeocoder.do_geocode('via Sandro Pertini 8, Ossona, MI')
93
+ response = MockSuccess.new
94
+ response.expects(:body).returns(GOOGLE_MULTI)
95
+ url = "http://maps.google.com/maps/geo?q=#{Geokit::Inflector.url_escape('via Sandro Pertini 8, Ossona, MI')}&output=xml&key=Google&oe=utf-8"
96
+ Geokit::Geocoders::GoogleGeocoder.expects(:call_geocoder_service).with(url).returns(response)
97
+ res=Geokit::Geocoders::GoogleGeocoder.geocode('via Sandro Pertini 8, Ossona, MI')
98
+ assert_equal "Lombardy", res.state
99
+ assert_equal "Mesero", res.city
100
+ assert_equal "45.4966243,8.8527131", res.ll
101
+ assert !res.is_us?
102
+ assert_equal "Via Sandro Pertini, 8, 20010 Mesero MI, Italy", res.full_address
103
+ assert_equal "8 Via Sandro Pertini", res.street_address
104
+ assert_equal "google", res.provider
105
+
106
+ assert_equal 2, res.all.size
107
+ res = res.all[1]
108
+ assert_equal "Lombardy", res.state
109
+ assert_equal "Ossona", res.city
110
+ assert_equal "45.5074444,8.90232", res.ll
111
+ assert !res.is_us?
112
+ assert_equal "Via Sandro Pertini, 20010 Ossona MI, Italy", res.full_address
113
+ assert_equal "Via Sandro Pertini", res.street_address
114
+ assert_equal "google", res.provider
115
+ end
116
+ end
@@ -35,6 +35,15 @@ class UsGeocoderTest < BaseGeocoderTest #:nodoc: all
35
35
  assert !Geokit::Geocoders::UsGeocoder.geocode(@us_full_loc).success
36
36
  end
37
37
 
38
+ def test_all_method
39
+ response = MockSuccess.new
40
+ response.expects(:body).returns(GEOCODER_US_FULL)
41
+ url = "http://geocoder.us/service/csv/geocode?address=#{Geokit::Inflector.url_escape(@address)}"
42
+ Geokit::Geocoders::UsGeocoder.expects(:call_geocoder_service).with(url).returns(response)
43
+ res=Geokit::Geocoders::UsGeocoder.geocode(@address)
44
+ assert_equal 1, res.all.size
45
+ end
46
+
38
47
  private
39
48
 
40
49
  def verify(location)
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.3
4
+ version: 1.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andre Lewis and Bill Eisenhauer