steveh-geokit 1.6.0 → 1.6.1
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/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
|