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.
- data/README.markdown +22 -3
- data/lib/geokit.rb +2 -0
- data/lib/geokit/geocoders.rb +44 -26
- data/lib/geokit/mappable.rb +25 -2
- data/test/test_geoloc.rb +5 -0
- data/test/test_google_geocoder.rb +29 -1
- data/test/test_us_geocoder.rb +9 -0
- metadata +1 -1
data/README.markdown
CHANGED
@@ -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
|
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
|
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
|
data/lib/geokit.rb
CHANGED
data/lib/geokit/geocoders.rb
CHANGED
@@ -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
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
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
|
# -------------------------------------------------------------------------------------------
|
data/lib/geokit/mappable.rb
CHANGED
@@ -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
|
data/test/test_geoloc.rb
CHANGED
@@ -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
|
-
|
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
|
data/test/test_us_geocoder.rb
CHANGED
@@ -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)
|