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