steveh-geokit 1.6.0 → 1.6.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +6 -3
- data/geokit.gemspec +3 -1
- data/lib/geokit.rb +9 -0
- data/lib/geokit/bounds.rb +95 -0
- data/lib/geokit/geo_loc.rb +115 -0
- data/lib/geokit/geocoders.rb +2 -0
- data/lib/geokit/geocoders/google_geocoder.rb +118 -111
- data/lib/geokit/geocoders/google_premier_geocoder.rb +147 -0
- data/lib/geokit/lat_lng.rb +112 -0
- data/lib/geokit/mappable.rb +1 -319
- data/lib/geokit/version.rb +1 -1
- data/test/test_google_geocoder.rb +13 -13
- data/test/test_google_premier_geocoder.rb +88 -0
- data/test/test_google_reverse_geocoder.rb +1 -1
- metadata +22 -5
data/Gemfile.lock
CHANGED
@@ -1,16 +1,19 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
geokit (1.
|
4
|
+
steveh-geokit (1.6.0)
|
5
|
+
activesupport (~> 3)
|
5
6
|
|
6
7
|
GEM
|
7
8
|
remote: http://rubygems.org/
|
8
9
|
specs:
|
10
|
+
activesupport (3.0.4)
|
9
11
|
mocha (0.9.12)
|
10
12
|
|
11
13
|
PLATFORMS
|
12
14
|
ruby
|
13
15
|
|
14
16
|
DEPENDENCIES
|
15
|
-
|
16
|
-
mocha
|
17
|
+
activesupport (~> 3)
|
18
|
+
mocha (>= 0.9)
|
19
|
+
steveh-geokit!
|
data/geokit.gemspec
CHANGED
@@ -18,5 +18,7 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
|
-
s.
|
21
|
+
s.add_runtime_dependency("activesupport", ["~> 3"])
|
22
|
+
|
23
|
+
s.add_development_dependency("mocha", [">= 0.9"])
|
22
24
|
end
|
data/lib/geokit.rb
CHANGED
@@ -28,11 +28,19 @@ require 'rexml/document'
|
|
28
28
|
require 'yaml'
|
29
29
|
require 'timeout'
|
30
30
|
require 'logger'
|
31
|
+
require 'active_support/core_ext/hash'
|
32
|
+
require 'active_support/core_ext/object/conversions'
|
33
|
+
require 'openssl'
|
34
|
+
require 'base64'
|
35
|
+
require 'json'
|
31
36
|
|
32
37
|
require 'geokit/too_many_queries_error'
|
33
38
|
require 'geokit/inflector'
|
34
39
|
require 'geokit/geocoders'
|
35
40
|
require 'geokit/mappable'
|
41
|
+
require 'geokit/lat_lng'
|
42
|
+
require 'geokit/geo_loc'
|
43
|
+
require 'geokit/bounds'
|
36
44
|
require 'geokit/geocoders/geocode_error'
|
37
45
|
require 'geokit/geocoders/geocoder'
|
38
46
|
|
@@ -40,6 +48,7 @@ require 'geokit/geocoders/ca_geocoder'
|
|
40
48
|
require 'geokit/geocoders/geo_plugin_geocoder'
|
41
49
|
require 'geokit/geocoders/geonames_geocoder'
|
42
50
|
require 'geokit/geocoders/google_geocoder'
|
51
|
+
require 'geokit/geocoders/google_premier_geocoder'
|
43
52
|
require 'geokit/geocoders/ip_geocoder'
|
44
53
|
require 'geokit/geocoders/multi_geocoder'
|
45
54
|
require 'geokit/geocoders/us_geocoder'
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Geokit
|
2
|
+
# Bounds represents a rectangular bounds, defined by the SW and NE corners
|
3
|
+
class Bounds
|
4
|
+
# sw and ne are LatLng objects
|
5
|
+
attr_accessor :sw, :ne
|
6
|
+
|
7
|
+
# provide sw and ne to instantiate a new Bounds instance
|
8
|
+
def initialize(sw,ne)
|
9
|
+
raise ArgumentError if !(sw.is_a?(Geokit::LatLng) && ne.is_a?(Geokit::LatLng))
|
10
|
+
@sw,@ne=sw,ne
|
11
|
+
end
|
12
|
+
|
13
|
+
#returns the a single point which is the center of the rectangular bounds
|
14
|
+
def center
|
15
|
+
@sw.midpoint_to(@ne)
|
16
|
+
end
|
17
|
+
|
18
|
+
# a simple string representation:sw,ne
|
19
|
+
def to_s
|
20
|
+
"#{@sw.to_s},#{@ne.to_s}"
|
21
|
+
end
|
22
|
+
|
23
|
+
# a two-element array of two-element arrays: sw,ne
|
24
|
+
def to_a
|
25
|
+
[@sw.to_a, @ne.to_a]
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns true if the bounds contain the passed point.
|
29
|
+
# allows for bounds which cross the meridian
|
30
|
+
def contains?(point)
|
31
|
+
point=Geokit::LatLng.normalize(point)
|
32
|
+
res = point.lat > @sw.lat && point.lat < @ne.lat
|
33
|
+
if crosses_meridian?
|
34
|
+
res &= point.lng < @ne.lng || point.lng > @sw.lng
|
35
|
+
else
|
36
|
+
res &= point.lng < @ne.lng && point.lng > @sw.lng
|
37
|
+
end
|
38
|
+
res
|
39
|
+
end
|
40
|
+
|
41
|
+
# returns true if the bounds crosses the international dateline
|
42
|
+
def crosses_meridian?
|
43
|
+
@sw.lng > @ne.lng
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns true if the candidate object is logically equal. Logical equivalence
|
47
|
+
# is true if the lat and lng attributes are the same for both objects.
|
48
|
+
def ==(other)
|
49
|
+
other.is_a?(Bounds) ? self.sw == other.sw && self.ne == other.ne : false
|
50
|
+
end
|
51
|
+
|
52
|
+
# Equivalent to Google Maps API's .toSpan() method on GLatLng's.
|
53
|
+
#
|
54
|
+
# Returns a LatLng object, whose coordinates represent the size of a rectangle
|
55
|
+
# defined by these bounds.
|
56
|
+
def to_span
|
57
|
+
lat_span = (@ne.lat - @sw.lat).abs
|
58
|
+
lng_span = (crosses_meridian? ? 360 + @ne.lng - @sw.lng : @ne.lng - @sw.lng).abs
|
59
|
+
Geokit::LatLng.new(lat_span, lng_span)
|
60
|
+
end
|
61
|
+
|
62
|
+
class <<self
|
63
|
+
|
64
|
+
# returns an instance of bounds which completely encompases the given circle
|
65
|
+
def from_point_and_radius(point,radius,options={})
|
66
|
+
point=LatLng.normalize(point)
|
67
|
+
p0=point.endpoint(0,radius,options)
|
68
|
+
p90=point.endpoint(90,radius,options)
|
69
|
+
p180=point.endpoint(180,radius,options)
|
70
|
+
p270=point.endpoint(270,radius,options)
|
71
|
+
sw=Geokit::LatLng.new(p180.lat,p270.lng)
|
72
|
+
ne=Geokit::LatLng.new(p0.lat,p90.lng)
|
73
|
+
Geokit::Bounds.new(sw,ne)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Takes two main combinations of arguments to create a bounds:
|
77
|
+
# point,point (this is the only one which takes two arguments
|
78
|
+
# [point,point]
|
79
|
+
# . . . where a point is anything LatLng#normalize can handle (which is quite a lot)
|
80
|
+
#
|
81
|
+
# NOTE: everything combination is assumed to pass points in the order sw, ne
|
82
|
+
def normalize (thing,other=nil)
|
83
|
+
# maybe this will be simple -- an actual bounds object is passed, and we can all go home
|
84
|
+
return thing if thing.is_a? Bounds
|
85
|
+
|
86
|
+
# no? OK, if there's no "other," the thing better be a two-element array
|
87
|
+
thing,other=thing if !other && thing.is_a?(Array) && thing.size==2
|
88
|
+
|
89
|
+
# Now that we're set with a thing and another thing, let LatLng do the heavy lifting.
|
90
|
+
# Exceptions may be thrown
|
91
|
+
Bounds.new(Geokit::LatLng.normalize(thing),Geokit::LatLng.normalize(other))
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module Geokit
|
2
|
+
# This class encapsulates the result of a geocoding call.
|
3
|
+
# It's primary purpose is to homogenize the results of multiple
|
4
|
+
# geocoding providers. It also provides some additional functionality, such as
|
5
|
+
# the "full address" method for geocoders that do not provide a
|
6
|
+
# full address in their results (for example, Yahoo), and the "is_us" method.
|
7
|
+
#
|
8
|
+
# Some geocoders can return multple results. Geoloc can capture multiple results through
|
9
|
+
# its "all" method.
|
10
|
+
#
|
11
|
+
# For the geocoder setting the results, it would look something like this:
|
12
|
+
# geo=GeoLoc.new(first_result)
|
13
|
+
# geo.all.push(second_result)
|
14
|
+
# geo.all.push(third_result)
|
15
|
+
#
|
16
|
+
# Then, for the user of the result:
|
17
|
+
#
|
18
|
+
# puts geo.full_address # just like usual
|
19
|
+
# puts geo.all.size => 3 # there's three results total
|
20
|
+
# puts geo.all.first # all is just an array or additional geolocs,
|
21
|
+
# so do what you want with it
|
22
|
+
class GeoLoc < LatLng
|
23
|
+
|
24
|
+
# Location attributes. Full address is a concatenation of all values. For example:
|
25
|
+
# 100 Spear St, San Francisco, CA, 94101, US
|
26
|
+
attr_accessor :street_address, :city, :state, :zip, :country_code, :country, :full_address, :all, :district, :province
|
27
|
+
# Attributes set upon return from geocoding. Success will be true for successful
|
28
|
+
# geocode lookups. The provider will be set to the name of the providing geocoder.
|
29
|
+
# Finally, precision is an indicator of the accuracy of the geocoding.
|
30
|
+
attr_accessor :success, :provider, :precision, :suggested_bounds
|
31
|
+
# Street number and street name are extracted from the street address attribute.
|
32
|
+
attr_reader :street_number, :street_name
|
33
|
+
# accuracy is set for Yahoo and Google geocoders, it is a numeric value of the
|
34
|
+
# precision. see http://code.google.com/apis/maps/documentation/geocoding/#GeocodingAccuracy
|
35
|
+
attr_accessor :accuracy
|
36
|
+
|
37
|
+
# Constructor expects a hash of symbols to correspond with attributes.
|
38
|
+
def initialize(h={})
|
39
|
+
@all = [self]
|
40
|
+
|
41
|
+
@street_address=h[:street_address]
|
42
|
+
@city=h[:city]
|
43
|
+
@state=h[:state]
|
44
|
+
@zip=h[:zip]
|
45
|
+
@country_code=h[:country_code]
|
46
|
+
@province = h[:province]
|
47
|
+
@success=false
|
48
|
+
@precision='unknown'
|
49
|
+
@full_address=nil
|
50
|
+
super(h[:lat],h[:lng])
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns true if geocoded to the United States.
|
54
|
+
def is_us?
|
55
|
+
country_code == 'US'
|
56
|
+
end
|
57
|
+
|
58
|
+
def success?
|
59
|
+
success == true
|
60
|
+
end
|
61
|
+
|
62
|
+
# full_address is provided by google but not by yahoo. It is intended that the google
|
63
|
+
# geocoding method will provide the full address, whereas for yahoo it will be derived
|
64
|
+
# from the parts of the address we do have.
|
65
|
+
def full_address
|
66
|
+
@full_address ? @full_address : to_geocodeable_s
|
67
|
+
end
|
68
|
+
|
69
|
+
# Extracts the street number from the street address if the street address
|
70
|
+
# has a value.
|
71
|
+
def street_number
|
72
|
+
street_address[/(\d*)/] if street_address
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns the street name portion of the street address.
|
76
|
+
def street_name
|
77
|
+
street_address[street_number.length, street_address.length].strip if street_address
|
78
|
+
end
|
79
|
+
|
80
|
+
# gives you all the important fields as key-value pairs
|
81
|
+
def hash
|
82
|
+
res={}
|
83
|
+
[:success,:lat,:lng,:country_code,:city,:state,:zip,:street_address,:province,:district,:provider,:full_address,:is_us?,:ll,:precision].each { |s| res[s] = self.send(s.to_s) }
|
84
|
+
res
|
85
|
+
end
|
86
|
+
alias to_hash hash
|
87
|
+
|
88
|
+
# Sets the city after capitalizing each word within the city name.
|
89
|
+
def city=(city)
|
90
|
+
@city = Geokit::Inflector::titleize(city) if city
|
91
|
+
end
|
92
|
+
|
93
|
+
# Sets the street address after capitalizing each word within the street address.
|
94
|
+
def street_address=(address)
|
95
|
+
@street_address = Geokit::Inflector::titleize(address) if address
|
96
|
+
end
|
97
|
+
|
98
|
+
# Returns a comma-delimited string consisting of the street address, city, state,
|
99
|
+
# zip, and country code. Only includes those attributes that are non-blank.
|
100
|
+
def to_geocodeable_s
|
101
|
+
a=[street_address, district, city, province, state, zip, country_code].compact
|
102
|
+
a.delete_if { |e| !e || e == '' }
|
103
|
+
a.join(', ')
|
104
|
+
end
|
105
|
+
|
106
|
+
def to_yaml_properties
|
107
|
+
(instance_variables - ['@all']).sort
|
108
|
+
end
|
109
|
+
|
110
|
+
# Returns a string representation of the instance.
|
111
|
+
def to_s
|
112
|
+
"Provider: #{provider}\nStreet: #{street_address}\nCity: #{city}\nState: #{state}\nZip: #{zip}\nLatitude: #{lat}\nLongitude: #{lng}\nCountry: #{country_code}\nSuccess: #{success}"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/lib/geokit/geocoders.rb
CHANGED
@@ -26,6 +26,8 @@ module Geokit
|
|
26
26
|
@@request_timeout = nil
|
27
27
|
@@yahoo = 'REPLACE_WITH_YOUR_YAHOO_KEY'
|
28
28
|
@@google = 'REPLACE_WITH_YOUR_GOOGLE_KEY'
|
29
|
+
@@google_client = 'REPLACE_WITH_YOUR_GOOGLE_CLIENT'
|
30
|
+
@@google_channel = 'REPLACE_WITH_YOUR_GOOGLE_CHANNEL'
|
29
31
|
@@geocoder_us = false
|
30
32
|
@@geocoder_ca = false
|
31
33
|
@@geonames = false
|
@@ -4,135 +4,142 @@ module Geokit
|
|
4
4
|
# contain a Google API key. Conforms to the interface set by the Geocoder class.
|
5
5
|
class GoogleGeocoder < Geocoder
|
6
6
|
|
7
|
+
ENDPOINT = "http://maps.google.com/maps/geo"
|
8
|
+
|
7
9
|
private
|
8
10
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
11
|
+
# Template method which does the reverse-geocode lookup.
|
12
|
+
def self.do_reverse_geocode(latlng)
|
13
|
+
latlng = LatLng.normalize(latlng)
|
14
|
+
params = { :ll => latlng.ll, :output => "xml", :key => Geokit::Geocoders::google, :oe => "utf-8" }
|
15
|
+
url = "#{ENDPOINT}?" + params.to_query
|
16
|
+
res = self.call_geocoder_service(url)
|
17
|
+
return GeoLoc.new unless (res.is_a?(Net::HTTPSuccess) || res.is_a?(Net::HTTPOK))
|
18
|
+
xml = res.body
|
19
|
+
logger.debug "Google reverse-geocoding. LL: #{latlng}. Result: #{xml}"
|
20
|
+
return self.xml2GeoLoc(xml)
|
21
|
+
end
|
19
22
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
23
|
+
# Template method which does the geocode lookup.
|
24
|
+
#
|
25
|
+
# Supports viewport/country code biasing
|
26
|
+
#
|
27
|
+
# ==== OPTIONS
|
28
|
+
# * :bias - This option makes the Google Geocoder return results biased to a particular
|
29
|
+
# country or viewport. Country code biasing is achieved by passing the ccTLD
|
30
|
+
# ('uk' for .co.uk, for example) as a :bias value. For a list of ccTLD's,
|
31
|
+
# look here: http://en.wikipedia.org/wiki/CcTLD. By default, the geocoder
|
32
|
+
# will be biased to results within the US (ccTLD .com).
|
33
|
+
#
|
34
|
+
# If you'd like the Google Geocoder to prefer results within a given viewport,
|
35
|
+
# you can pass a Geokit::Bounds object as the :bias value.
|
36
|
+
#
|
37
|
+
# ==== EXAMPLES
|
38
|
+
# # By default, the geocoder will return Syracuse, NY
|
39
|
+
# Geokit::Geocoders::GoogleGeocoder.geocode('Syracuse').country_code # => 'US'
|
40
|
+
# # With country code biasing, it returns Syracuse in Sicily, Italy
|
41
|
+
# Geokit::Geocoders::GoogleGeocoder.geocode('Syracuse', :bias => :it).country_code # => 'IT'
|
42
|
+
#
|
43
|
+
# # By default, the geocoder will return Winnetka, IL
|
44
|
+
# Geokit::Geocoders::GoogleGeocoder.geocode('Winnetka').state # => 'IL'
|
45
|
+
# # When biased to an bounding box around California, it will now return the Winnetka neighbourhood, CA
|
46
|
+
# bounds = Geokit::Bounds.normalize([34.074081, -118.694401], [34.321129, -118.399487])
|
47
|
+
# Geokit::Geocoders::GoogleGeocoder.geocode('Winnetka', :bias => bounds).state # => 'CA'
|
48
|
+
def self.do_geocode(address, options = {})
|
49
|
+
address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
|
50
|
+
params = { :q => address_str, :output => "xml", :key => Geokit::Geocoders::google, :oe => "utf-8" }.merge(bias_options(options[:bias]))
|
51
|
+
url = "#{ENDPOINT}?" + params.to_query
|
52
|
+
res = self.call_geocoder_service(url)
|
53
|
+
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
54
|
+
xml = res.body
|
55
|
+
logger.debug "Google geocoding. Address: #{address}. Result: #{xml}"
|
56
|
+
return self.xml2GeoLoc(xml, address)
|
57
|
+
end
|
54
58
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
59
|
+
def self.bias_options(bias)
|
60
|
+
if bias.is_a?(String) or bias.is_a?(Symbol)
|
61
|
+
# country code biasing
|
62
|
+
{ :gl => bias.to_s.downcase }
|
63
|
+
elsif bias.is_a?(Bounds)
|
64
|
+
# viewport biasing
|
65
|
+
{ :ll => bias.center.ll, :spn => bias.to_span.ll }
|
66
|
+
else
|
67
|
+
{}
|
68
|
+
end
|
62
69
|
end
|
63
|
-
end
|
64
70
|
|
65
|
-
|
66
|
-
|
71
|
+
def self.xml2GeoLoc(xml, address="")
|
72
|
+
doc=REXML::Document.new(xml)
|
67
73
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
74
|
+
if doc.elements['//kml/Response/Status/code'].text == '200'
|
75
|
+
geoloc = nil
|
76
|
+
# Google can return multiple results as //Placemark elements.
|
77
|
+
# iterate through each and extract each placemark as a geoloc
|
78
|
+
doc.each_element('//Placemark') do |e|
|
79
|
+
extracted_geoloc = extract_placemark(e) # g is now an instance of GeoLoc
|
80
|
+
if geoloc.nil?
|
81
|
+
# first time through, geoloc is still nil, so we make it the geoloc we just extracted
|
82
|
+
geoloc = extracted_geoloc
|
83
|
+
else
|
84
|
+
# second (and subsequent) iterations, we push additional
|
85
|
+
# geolocs onto "geoloc.all"
|
86
|
+
geoloc.all.push(extracted_geoloc)
|
87
|
+
end
|
81
88
|
end
|
89
|
+
return geoloc
|
90
|
+
elsif doc.elements['//kml/Response/Status/code'].text == '620'
|
91
|
+
raise Geokit::TooManyQueriesError
|
92
|
+
else
|
93
|
+
logger.info "Google was unable to geocode address: "+address
|
94
|
+
return GeoLoc.new
|
82
95
|
end
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
96
|
+
|
97
|
+
rescue Geokit::TooManyQueriesError
|
98
|
+
# re-raise because of other rescue
|
99
|
+
raise Geokit::TooManyQueriesError, "Google returned a 620 status, too many queries. The given key has gone over the requests limit in the 24 hour period or has submitted too many requests in too short a period of time. If you're sending multiple requests in parallel or in a tight loop, use a timer or pause in your code to make sure you don't send the requests too quickly."
|
100
|
+
rescue
|
101
|
+
logger.error "Caught an error during Google geocoding call: "+$!
|
88
102
|
return GeoLoc.new
|
89
103
|
end
|
90
104
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
105
|
+
# extracts a single geoloc from a //placemark element in the google results xml
|
106
|
+
def self.extract_placemark(doc)
|
107
|
+
res = GeoLoc.new
|
108
|
+
coordinates=doc.elements['.//coordinates'].text.to_s.split(',')
|
109
|
+
|
110
|
+
#basics
|
111
|
+
res.lat=coordinates[1]
|
112
|
+
res.lng=coordinates[0]
|
113
|
+
res.country_code=doc.elements['.//CountryNameCode'].text if doc.elements['.//CountryNameCode']
|
114
|
+
res.provider='google'
|
98
115
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
116
|
+
#extended -- false if not not available
|
117
|
+
res.city = doc.elements['.//LocalityName'].text if doc.elements['.//LocalityName']
|
118
|
+
res.state = doc.elements['.//AdministrativeAreaName'].text if doc.elements['.//AdministrativeAreaName']
|
119
|
+
res.province = doc.elements['.//SubAdministrativeAreaName'].text if doc.elements['.//SubAdministrativeAreaName']
|
120
|
+
res.full_address = doc.elements['.//address'].text if doc.elements['.//address'] # google provides it
|
121
|
+
res.zip = doc.elements['.//PostalCodeNumber'].text if doc.elements['.//PostalCodeNumber']
|
122
|
+
res.street_address = doc.elements['.//ThoroughfareName'].text if doc.elements['.//ThoroughfareName']
|
123
|
+
res.country = doc.elements['.//CountryName'].text if doc.elements['.//CountryName']
|
124
|
+
res.district = doc.elements['.//DependentLocalityName'].text if doc.elements['.//DependentLocalityName']
|
125
|
+
# Translate accuracy into Yahoo-style token address, street, zip, zip+4, city, state, country
|
126
|
+
# For Google, 1=low accuracy, 8=high accuracy
|
127
|
+
address_details=doc.elements['.//*[local-name() = "AddressDetails"]']
|
128
|
+
res.accuracy = address_details ? address_details.attributes['Accuracy'].to_i : 0
|
129
|
+
res.precision=%w{unknown country state state city zip zip+4 street address building}[res.accuracy]
|
103
130
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
131
|
+
# google returns a set of suggested boundaries for the geocoded result
|
132
|
+
if suggested_bounds = doc.elements['//LatLonBox']
|
133
|
+
res.suggested_bounds = Bounds.normalize(
|
134
|
+
[suggested_bounds.attributes['south'], suggested_bounds.attributes['west']],
|
135
|
+
[suggested_bounds.attributes['north'], suggested_bounds.attributes['east']])
|
136
|
+
end
|
109
137
|
|
110
|
-
|
111
|
-
res.city = doc.elements['.//LocalityName'].text if doc.elements['.//LocalityName']
|
112
|
-
res.state = doc.elements['.//AdministrativeAreaName'].text if doc.elements['.//AdministrativeAreaName']
|
113
|
-
res.province = doc.elements['.//SubAdministrativeAreaName'].text if doc.elements['.//SubAdministrativeAreaName']
|
114
|
-
res.full_address = doc.elements['.//address'].text if doc.elements['.//address'] # google provides it
|
115
|
-
res.zip = doc.elements['.//PostalCodeNumber'].text if doc.elements['.//PostalCodeNumber']
|
116
|
-
res.street_address = doc.elements['.//ThoroughfareName'].text if doc.elements['.//ThoroughfareName']
|
117
|
-
res.country = doc.elements['.//CountryName'].text if doc.elements['.//CountryName']
|
118
|
-
res.district = doc.elements['.//DependentLocalityName'].text if doc.elements['.//DependentLocalityName']
|
119
|
-
# Translate accuracy into Yahoo-style token address, street, zip, zip+4, city, state, country
|
120
|
-
# For Google, 1=low accuracy, 8=high accuracy
|
121
|
-
address_details=doc.elements['.//*[local-name() = "AddressDetails"]']
|
122
|
-
res.accuracy = address_details ? address_details.attributes['Accuracy'].to_i : 0
|
123
|
-
res.precision=%w{unknown country state state city zip zip+4 street address building}[res.accuracy]
|
138
|
+
res.success=true
|
124
139
|
|
125
|
-
|
126
|
-
if suggested_bounds = doc.elements['//LatLonBox']
|
127
|
-
res.suggested_bounds = Bounds.normalize(
|
128
|
-
[suggested_bounds.attributes['south'], suggested_bounds.attributes['west']],
|
129
|
-
[suggested_bounds.attributes['north'], suggested_bounds.attributes['east']])
|
140
|
+
return res
|
130
141
|
end
|
131
142
|
|
132
|
-
res.success=true
|
133
|
-
|
134
|
-
return res
|
135
|
-
end
|
136
143
|
end
|
137
144
|
end
|
138
145
|
end
|