graticule 0.1.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.
Files changed (45) hide show
  1. data/CHANGELOG.txt +8 -0
  2. data/LICENSE.txt +30 -0
  3. data/Manifest.txt +44 -0
  4. data/README.txt +10 -0
  5. data/Rakefile +16 -0
  6. data/init.rb +2 -0
  7. data/lib/graticule.rb +14 -0
  8. data/lib/graticule/distance.rb +24 -0
  9. data/lib/graticule/distance/haversine.rb +65 -0
  10. data/lib/graticule/distance/spherical.rb +30 -0
  11. data/lib/graticule/distance/vincenty.rb +99 -0
  12. data/lib/graticule/geocoder.rb +26 -0
  13. data/lib/graticule/geocoders/bogus.rb +12 -0
  14. data/lib/graticule/geocoders/geocoder_us.rb +45 -0
  15. data/lib/graticule/geocoders/google.rb +96 -0
  16. data/lib/graticule/geocoders/meta_carta.rb +102 -0
  17. data/lib/graticule/geocoders/rest.rb +98 -0
  18. data/lib/graticule/geocoders/yahoo.rb +101 -0
  19. data/lib/graticule/location.rb +28 -0
  20. data/lib/graticule/version.rb +3 -0
  21. data/test/fixtures/responses/geocoder_us/success.xml +10 -0
  22. data/test/fixtures/responses/geocoder_us/unknown.xml +1 -0
  23. data/test/fixtures/responses/google/badkey.xml +10 -0
  24. data/test/fixtures/responses/google/limit.xml +10 -0
  25. data/test/fixtures/responses/google/missing_address.xml +10 -0
  26. data/test/fixtures/responses/google/server_error.xml +10 -0
  27. data/test/fixtures/responses/google/success.xml +37 -0
  28. data/test/fixtures/responses/google/unavailable.xml +10 -0
  29. data/test/fixtures/responses/google/unknown_address.xml +10 -0
  30. data/test/fixtures/responses/meta_carta/bad_address.xml +9 -0
  31. data/test/fixtures/responses/meta_carta/multiple.xml +33 -0
  32. data/test/fixtures/responses/meta_carta/success.xml +23 -0
  33. data/test/fixtures/responses/yahoo/success.xml +3 -0
  34. data/test/fixtures/responses/yahoo/unknown_address.xml +6 -0
  35. data/test/mocks/uri.rb +51 -0
  36. data/test/test_helper.rb +31 -0
  37. data/test/unit/graticule/distance_test.rb +30 -0
  38. data/test/unit/graticule/geocoder_test.rb +31 -0
  39. data/test/unit/graticule/geocoders/geocoder_us_test.rb +42 -0
  40. data/test/unit/graticule/geocoders/geocoders.rb +56 -0
  41. data/test/unit/graticule/geocoders/google_test.rb +22 -0
  42. data/test/unit/graticule/geocoders/meta_carta_test.rb +70 -0
  43. data/test/unit/graticule/geocoders/yahoo_test.rb +49 -0
  44. data/test/unit/graticule/location_test.rb +38 -0
  45. metadata +102 -0
@@ -0,0 +1,8 @@
1
+
2
+ 0.1.1
3
+ * fixed bug in Yahoo that raised error when street address not returned
4
+ * migrated to Hoe (http://seattlerb.rubyforge.org/hoe/)
5
+ * added Haversine, Spherical and Vincenty distance calculations
6
+
7
+ 0.1 (2006-10-31)
8
+ * Initial release
@@ -0,0 +1,30 @@
1
+ Copyright 2006 Brandon Keepers, Collective Idea. All rights reserved.
2
+
3
+ Original geocoding code:
4
+ Copyright 2006 Eric Hodel, The Robot Co-op. All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions
8
+ are met:
9
+
10
+ 1. Redistributions of source code must retain the above copyright
11
+ notice, this list of conditions and the following disclaimer.
12
+ 2. Redistributions in binary form must reproduce the above copyright
13
+ notice, this list of conditions and the following disclaimer in the
14
+ documentation and/or other materials provided with the distribution.
15
+ 3. Neither the names of the authors nor the names of their contributors
16
+ may be used to endorse or promote products derived from this software
17
+ without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
20
+ OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
23
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
24
+ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
25
+ OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
26
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
27
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
28
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
29
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+
@@ -0,0 +1,44 @@
1
+ CHANGELOG.txt
2
+ LICENSE.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ init.rb
7
+ lib/graticule.rb
8
+ lib/graticule/distance.rb
9
+ lib/graticule/distance/haversine.rb
10
+ lib/graticule/distance/spherical.rb
11
+ lib/graticule/distance/vincenty.rb
12
+ lib/graticule/geocoder.rb
13
+ lib/graticule/geocoders/bogus.rb
14
+ lib/graticule/geocoders/geocoder_us.rb
15
+ lib/graticule/geocoders/google.rb
16
+ lib/graticule/geocoders/meta_carta.rb
17
+ lib/graticule/geocoders/rest.rb
18
+ lib/graticule/geocoders/yahoo.rb
19
+ lib/graticule/location.rb
20
+ lib/graticule/version.rb
21
+ test/fixtures/responses/geocoder_us/success.xml
22
+ test/fixtures/responses/geocoder_us/unknown.xml
23
+ test/fixtures/responses/google/badkey.xml
24
+ test/fixtures/responses/google/limit.xml
25
+ test/fixtures/responses/google/missing_address.xml
26
+ test/fixtures/responses/google/server_error.xml
27
+ test/fixtures/responses/google/success.xml
28
+ test/fixtures/responses/google/unavailable.xml
29
+ test/fixtures/responses/google/unknown_address.xml
30
+ test/fixtures/responses/meta_carta/bad_address.xml
31
+ test/fixtures/responses/meta_carta/multiple.xml
32
+ test/fixtures/responses/meta_carta/success.xml
33
+ test/fixtures/responses/yahoo/success.xml
34
+ test/fixtures/responses/yahoo/unknown_address.xml
35
+ test/mocks/uri.rb
36
+ test/test_helper.rb
37
+ test/unit/graticule/distance_test.rb
38
+ test/unit/graticule/geocoder_test.rb
39
+ test/unit/graticule/geocoders/geocoder_us_test.rb
40
+ test/unit/graticule/geocoders/geocoders.rb
41
+ test/unit/graticule/geocoders/google_test.rb
42
+ test/unit/graticule/geocoders/meta_carta_test.rb
43
+ test/unit/graticule/geocoders/yahoo_test.rb
44
+ test/unit/graticule/location_test.rb
@@ -0,0 +1,10 @@
1
+ = Graticule
2
+
3
+ Graticule is a geocoding API for looking up address coordinates. It supports supports the Yahoo, Google, Geocoder.us, and MetaCarta APIs.
4
+
5
+ = Usage
6
+
7
+ require 'rubygems'
8
+ require 'graticule'
9
+ geocoder = Graticule.service(:google).new "api_key"
10
+ location = geocoder.locate "61 East 9th Street, Holland, MI"
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require 'hoe'
3
+ require File.join(File.dirname(__FILE__), 'lib', 'graticule', 'version.rb')
4
+
5
+ Hoe.new("graticule", Graticule::Version) do |p|
6
+ p.rubyforge_name = "graticule"
7
+ p.author = 'Brandon Keepers'
8
+ p.email = 'brandon@opensoul.org'
9
+ p.summary = "API for using all the popular geocoding services."
10
+ p.description = 'Graticule is a geocoding API that provides a common interface to all the popular services, including Google, Yahoo, Geocoder.us, and MetaCarta.'
11
+ p.url = 'http://graticule.rubyforge.org'
12
+ p.need_tar = true
13
+ p.need_zip = true
14
+ p.test_globs = ['test/**/*_test.rb']
15
+ p.changes = p.paragraphs_of('CHANGELOG.txt', 0..1).join("\n\n")
16
+ end
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+
2
+ require 'graticule'
@@ -0,0 +1,14 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+
3
+ require 'graticule/location'
4
+ require 'graticule/geocoder'
5
+ require 'graticule/geocoders/bogus'
6
+ require 'graticule/geocoders/rest'
7
+ require 'graticule/geocoders/google'
8
+ require 'graticule/geocoders/yahoo'
9
+ require 'graticule/geocoders/geocoder_us'
10
+ require 'graticule/geocoders/meta_carta'
11
+ require 'graticule/distance'
12
+ require 'graticule/distance/haversine'
13
+ require 'graticule/distance/spherical'
14
+ require 'graticule/distance/vincenty'
@@ -0,0 +1,24 @@
1
+ module Graticule
2
+ module Distance
3
+ EARTH_RADIUS = { :kilometers => 6378.135, :miles => 3963.1676 }
4
+ # WGS-84 numbers
5
+ EARTH_MAJOR_AXIS_RADIUS = { :kilometers => 6378.137, :miles => 3963.19059 }
6
+ EARTH_MINOR_AXIS_RADIUS = { :kilometers => 6356.7523142, :miles => 3949.90276 }
7
+
8
+ class DistanceFormula
9
+
10
+ def initialize
11
+ raise NotImplementedError
12
+ end
13
+
14
+ def self.deg2rad(deg)
15
+ (deg * Math::PI / 180)
16
+ end
17
+
18
+ def self.rad2deg(rad)
19
+ (rad * 180 / Math::PI)
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,65 @@
1
+
2
+ module Graticule
3
+ module Distance
4
+
5
+ #
6
+ # Thanks to Chris Veness for distance formulas.
7
+ # * http://www.movable-type.co.uk/scripts/LatLong.html
8
+ #
9
+ # Distance Measured usign the Haversine Formula
10
+ # Works better at small distances than the Spherical Law of Cosines
11
+ # R = earth’s radius (mean radius = 6,371km)
12
+ # Δlat = lat2− lat1
13
+ # Δlong = long2− long1
14
+ # a = sin²(Δlat/2) + cos(lat1).cos(lat2).sin²(Δlong/2)
15
+ # c = 2.atan2(√a, √(1−a))
16
+ # d = R.c
17
+ #
18
+ class Haversine < DistanceFormula
19
+
20
+ def self.distance(from, to, units = :miles)
21
+ from_longitude = deg2rad(from.longitude)
22
+ from_latitude = deg2rad(from.latitude)
23
+ to_longitude = deg2rad(to.longitude)
24
+ to_latitude = deg2rad(to.latitude)
25
+
26
+ latitude_delta = to_latitude - from_latitude
27
+ longitude_delta = to_longitude - from_longitude
28
+
29
+ a = Math.sin(latitude_delta/2)**2 +
30
+ Math.cos(from_latitude) *
31
+ Math.cos(to_latitude) *
32
+ Math.sin(longitude_delta/2)**2
33
+
34
+ c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a))
35
+
36
+ d = EARTH_RADIUS[units.to_sym] * c
37
+ end
38
+
39
+ # # What formula is this?
40
+ # def self.distance(from, to, units = :miles)
41
+ # from_longitude = deg2rad(from.longitude)
42
+ # from_latitude = deg2rad(from.latitude)
43
+ # to_longitude = deg2rad(to.longitude)
44
+ # to_latitude = deg2rad(to.latitude)
45
+ #
46
+ # Math.acos(
47
+ # Math.cos(from_longitude) *
48
+ # Math.cos(to_longitude) *
49
+ # Math.cos(from_latitude) *
50
+ # Math.cos(to_latitude) +
51
+ #
52
+ # Math.cos(from_latitude) *
53
+ # Math.sin(from_longitude) *
54
+ # Math.cos(to_latitude) *
55
+ # Math.sin(to_longitude) +
56
+ #
57
+ # Math.sin(from_latitude) *
58
+ # Math.sin(to_latitude)
59
+ # ) * EARTH_RADIUS[units.to_sym]
60
+ # end
61
+
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,30 @@
1
+ module Graticule
2
+ module Distance
3
+
4
+ #
5
+ # Distance Measured usign the Spherical Law of Cosines
6
+ # Simplist though least accurate (earth isn't a perfect sphere)
7
+ # d = acos(sin(lat1).sin(lat2)+cos(lat1).cos(lat2).cos(long2−long1)).R
8
+ #
9
+ class Spherical < DistanceFormula
10
+
11
+ def self.distance(from, to, units = :miles)
12
+ from_longitude = deg2rad(from.longitude)
13
+ from_latitude = deg2rad(from.latitude)
14
+ to_longitude = deg2rad(to.longitude)
15
+ to_latitude = deg2rad(to.latitude)
16
+
17
+ Math.acos(
18
+ Math.sin(from_latitude) *
19
+ Math.sin(to_latitude) +
20
+
21
+ Math.cos(from_latitude) *
22
+ Math.cos(to_latitude) *
23
+ Math.cos(to_longitude - from_longitude)
24
+ ) * EARTH_RADIUS[units.to_sym]
25
+ end
26
+
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,99 @@
1
+ module Graticule
2
+ module Distance
3
+
4
+ #
5
+ # Thanks to Chris Veness for distance formulas.
6
+ # * http://www.movable-type.co.uk/scripts/LatLongVincenty.html
7
+ #
8
+ # Distance Measured usign the Vincenty Formula
9
+ # Very accurate, using an accurate ellipsoidal model of the earth
10
+ # a, b = major & minor semiaxes of the ellipsoid
11
+ # f = flattening (a−b)/a
12
+ # φ1, φ2 = geodetic latitude
13
+ # L = difference in longitude
14
+ # U1 = atan((1−f).tanφ1) (U is ‘reduced latitude’)
15
+ # U2 = atan((1−f).tanφ2)
16
+ # λ = L, λ′ = 2π
17
+ # while abs(λ−λ′) > 10-12 { (i.e. 0.06mm)
18
+ # sinσ = √[ (cosU2.sinλ)² + (cosU1.sinU2 − sinU1.cosU2.cosλ)² ] (14)
19
+ # cosσ = sinU1.sinU2 + cosU1.cosU2.cosλ (15)
20
+ # σ = atan2(sinσ, cosσ) (16)
21
+ # sinα = cosU1.cosU2.sinλ / sinσ (17)
22
+ # cos²α = 1 − sin²α (trig identity; §6)
23
+ # cos2σm = cosσ − 2.sinU1.sinU2/cos²α (18)
24
+ # C = f/16.cos²α.[4+f.(4−3.cos²α)] (10)
25
+ # λ′ = λ
26
+ # λ = L + (1−C).f.sinα.{σ+C.sinσ.[cos2σm+C.cosσ.(−1+2.cos²2σm)]} (11)
27
+ # }
28
+ # u² = cos²α.(a²−b²)/b²
29
+ # A = 1+u²/16384.{4096+u².[−768+u².(320−175.u²)]} (3)
30
+ # B = u²/1024.{256+u².[−128+u².(74−47.u²)]} (4)
31
+ # Δσ = B.sinσ.{cos2σm+B/4.[cosσ.(−1+2.cos²2σm) − B/6.cos2σm.(−3+4.sin²σ).(−3+4.cos²2σm)]} (6)
32
+ # s = b.A.(σ−Δσ) (19)
33
+ # α1 = atan2(cosU2.sinλ, cosU1.sinU2 − sinU1.cosU2.cosλ) (20)
34
+ # α2 = atan2(cosU1.sinλ, −sinU1.cosU2 + cosU1.sinU2.cosλ) (21)
35
+ # Where:
36
+ #
37
+ # s is the distance (in the same units as a & b)
38
+ # α1 is the initial bearing, or forward azimuth
39
+ # α2 is the final bearing (in direction p1→p2)
40
+ #
41
+ class Vincenty < DistanceFormula
42
+
43
+ def self.distance(from, to, units = :miles)
44
+ from_longitude = deg2rad(from.longitude)
45
+ from_latitude = deg2rad(from.latitude)
46
+ to_longitude = deg2rad(to.longitude)
47
+ to_latitude = deg2rad(to.latitude)
48
+
49
+ earth_major_axis_radius = EARTH_MAJOR_AXIS_RADIUS[units.to_sym]
50
+ earth_minor_axis_radius = EARTH_MINOR_AXIS_RADIUS[units.to_sym]
51
+
52
+ f = (earth_major_axis_radius - earth_minor_axis_radius) / earth_major_axis_radius
53
+
54
+ l = to_longitude - from_longitude
55
+ u1 = Math.atan((1-f) * Math.tan(from_latitude))
56
+ u2 = Math.atan((1-f) * Math.tan(to_latitude))
57
+ sinU1 = Math.sin(u1)
58
+ cosU1 = Math.cos(u1)
59
+ sinU2 = Math.sin(u2)
60
+ cosU2 = Math.cos(u2)
61
+
62
+ lambda = l
63
+ lambdaP = 2*Math::PI
64
+ iterLimit = 20;
65
+ while (lambda-lambdaP).abs > 1e-12 && --iterLimit>0
66
+ sinLambda = Math.sin(lambda)
67
+ cosLambda = Math.cos(lambda)
68
+ sinSigma = Math.sqrt((cosU2*sinLambda) * (cosU2*sinLambda) +
69
+ (cosU1*sinU2-sinU1*cosU2*cosLambda) * (cosU1*sinU2-sinU1*cosU2*cosLambda))
70
+ return 0 if sinSigma==0 # co-incident points
71
+ cosSigma = sinU1*sinU2 + cosU1*cosU2*cosLambda
72
+ sigma = Math.atan2(sinSigma, cosSigma)
73
+ sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma
74
+ cosSqAlpha = 1 - sinAlpha*sinAlpha
75
+ cos2SigmaM = cosSigma - 2*sinU1*sinU2/cosSqAlpha
76
+
77
+ cos2SigmaM = 0 if cos2SigmaM.nan? # equatorial line: cosSqAlpha=0 (§6)
78
+
79
+ c = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha))
80
+ lambdaP = lambda
81
+ lambda = l + (1-c) * f * sinAlpha *
82
+ (sigma + c*sinSigma*(cos2SigmaM+c*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)))
83
+ end
84
+ return NaN if (iterLimit==0) # formula failed to converge
85
+
86
+ uSq = cosSqAlpha * (earth_major_axis_radius**2 - earth_minor_axis_radius**2) / (earth_minor_axis_radius**2);
87
+ bigA = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
88
+ bigB = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));
89
+ deltaSigma = bigB*sinSigma*(cos2SigmaM+bigB/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
90
+ bigB/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
91
+ s = earth_minor_axis_radius*bigA*(sigma-deltaSigma);
92
+
93
+ #s = s.toFixed(3) # round to 1mm precision
94
+ return s
95
+ end
96
+
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,26 @@
1
+
2
+ module Graticule
3
+
4
+ def self.service(name)
5
+ self.const_get "#{name}_geocoder".camelize
6
+ end
7
+
8
+ # The Geocode class is the base class for all geocoder implementations. The
9
+ # geocoders must implement:
10
+ #
11
+ # * locate(address)
12
+ #
13
+ class Geocoder
14
+ def initialize
15
+ raise NotImplementedError
16
+ end
17
+ end
18
+
19
+ # Base error class
20
+ class Error < RuntimeError; end
21
+ class CredentialsError < Error; end
22
+
23
+ # Raised when you try to locate an invalid address.
24
+ class AddressError < Error; end
25
+
26
+ end
@@ -0,0 +1,12 @@
1
+
2
+ module Graticule #:nodoc:
3
+
4
+ # Bogus geocoder that can be used for test purposes
5
+ class BogusGeocoder < Geocoder
6
+
7
+ def locate(address)
8
+ Location.new
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,45 @@
1
+ module Graticule #:nodoc:
2
+
3
+ # A library for lookup up coordinates with geocoder.us' API.
4
+ #
5
+ # http://geocoder.us/help/
6
+ class GeocoderUsGeocoder < RestGeocoder
7
+
8
+ # Creates a new GeocoderUs object optionally using +username+ and
9
+ # +password+.
10
+ #
11
+ # You can sign up for a geocoder.us account here:
12
+ #
13
+ # http://geocoder.us/user/signup
14
+ def initialize(user = nil, password = nil)
15
+ if user and password then
16
+ @url = URI.parse 'http://geocoder.us/member/service/rest/geocode'
17
+ @url.user = user
18
+ @url.password = password
19
+ else
20
+ @url = URI.parse 'http://rpc.geocoder.us/service/rest/geocode'
21
+ end
22
+ end
23
+
24
+ # Locates +address+ and returns the address' latitude and longitude or
25
+ # raises an AddressError.
26
+ def locate(address)
27
+ get :address => address
28
+ end
29
+
30
+ def parse_response(xml)
31
+ location = Location.new
32
+ location.street = xml.elements['rdf:RDF/geo:Point/dc:description'].text
33
+
34
+ location.latitude = xml.elements['rdf:RDF/geo:Point/geo:lat'].text.to_f
35
+ location.longitude = xml.elements['rdf:RDF/geo:Point/geo:long'].text.to_f
36
+
37
+ return location
38
+ end
39
+
40
+ def check_error(xml)
41
+ raise AddressError, xml.text if xml.text == 'couldn\'t find this address! sorry'
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,96 @@
1
+
2
+ module Graticule
3
+
4
+ # First you need a Google Maps API key. You can register for one here:
5
+ # http://www.google.com/apis/maps/signup.html
6
+ #
7
+ # Then you create a GoogleGeocode object and start locating addresses:
8
+ #
9
+ # require 'rubygems'
10
+ # require 'graticule'
11
+ #
12
+ # gg = Graticule.service(:google).new(:key => MAPS_API_KEY)
13
+ # location = gg.locate '1600 Amphitheater Pkwy, Mountain View, CA'
14
+ # p location.coordinates
15
+ #
16
+ class GoogleGeocoder < RestGeocoder
17
+ # http://www.google.com/apis/maps/documentation/#Geocoding_HTTP_Request
18
+
19
+ # http://www.google.com/apis/maps/documentation/reference.html#GGeoAddressAccuracy
20
+ PRECISION = {
21
+ 0 => :unknown, # Unknown location. (Since 2.59)
22
+ 1 => :country, # Country level accuracy. (Since 2.59)
23
+ 2 => :state, # Region (state, province, prefecture, etc.) level accuracy. (Since 2.59)
24
+ 3 => :state, # Sub-region (county, municipality, etc.) level accuracy. (Since 2.59)
25
+ 4 => :city, # Town (city, village) level accuracy. (Since 2.59)
26
+ 5 => :zip, # Post code (zip code) level accuracy. (Since 2.59)
27
+ 6 => :street, # Street level accuracy. (Since 2.59)
28
+ 7 => :street, # Intersection level accuracy. (Since 2.59)
29
+ 8 => :address # Address level accuracy. (Since 2.59)
30
+ }
31
+
32
+ # Creates a new GoogleGeocode that will use Google Maps API key +key+. You
33
+ # can sign up for an API key here:
34
+ #
35
+ # http://www.google.com/apis/maps/signup.html
36
+ def initialize(key)
37
+ @key = key
38
+ @url = URI.parse 'http://maps.google.com/maps/geo'
39
+ end
40
+
41
+ # Locates +address+ returning a Location
42
+ def locate(address)
43
+ get :q => address
44
+ end
45
+
46
+ # Extracts a Location from +xml+.
47
+ def parse_response(xml)
48
+ longitude, latitude, = xml.elements['/kml/Response/Placemark/Point/coordinates'].text.split(',').map { |v| v.to_f }
49
+ Location.new \
50
+ :street => text(xml.elements['/kml/Response/Placemark/AddressDetails/Country/AdministrativeArea/SubAdministrativeArea/Locality/Thoroughfare/ThoroughfareName']),
51
+ :city => text(xml.elements['/kml/Response/Placemark/AddressDetails/Country/AdministrativeArea/SubAdministrativeArea/Locality/LocalityName']),
52
+ :state => text(xml.elements['/kml/Response/Placemark/AddressDetails/Country/AdministrativeArea/AdministrativeAreaName']),
53
+ :zip => text(xml.elements['/kml/Response/Placemark/AddressDetails/Country/AdministrativeArea/SubAdministrativeArea/Locality/PostalCode/PostalCodeNumber']),
54
+ :country => text(xml.elements['/kml/Response/Placemark/AddressDetails/Country/CountryNameCode']),
55
+ :latitude => latitude,
56
+ :longitude => longitude,
57
+ :precision => PRECISION[xml.elements['/kml/Response/Placemark/AddressDetails'].attribute('Accuracy').value.to_i] || :unknown
58
+ end
59
+
60
+ # Extracts and raises an error from +xml+, if any.
61
+ def check_error(xml)
62
+ status ||= xml.elements['/kml/Response/Status/code'].text.to_i
63
+ case status
64
+ when 200 then # ignore, ok
65
+ when 500 then
66
+ raise Error, 'server error'
67
+ when 601 then
68
+ raise AddressError, 'missing address'
69
+ when 602 then
70
+ raise AddressError, 'unknown address'
71
+ when 603 then
72
+ raise AddressError, 'unavailable address'
73
+ when 610 then
74
+ raise CredentialsError, 'invalid key'
75
+ when 620 then
76
+ raise CredentialsError, 'too many queries'
77
+ else
78
+ raise Error, "unknown error #{status}"
79
+ end
80
+ end
81
+
82
+ # Creates a URL from the Hash +params+. Automatically adds the key and
83
+ # sets the output type to 'xml'.
84
+ def make_url(params)
85
+ params[:key] = @key
86
+ params[:output] = 'xml'
87
+
88
+ super params
89
+ end
90
+
91
+ private
92
+ def text(element)
93
+ element.text if element
94
+ end
95
+ end
96
+ end