andre-geokit 1.2.3 → 1.2.4

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