graticule 2.3.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. data/.autotest +13 -0
  2. data/.gitignore +10 -0
  3. data/.travis.yml +14 -0
  4. data/CHANGELOG.txt +4 -1
  5. data/Gemfile +7 -0
  6. data/LICENSE.txt +1 -1
  7. data/README.md +122 -0
  8. data/Rakefile +78 -0
  9. data/graticule.gemspec +26 -0
  10. data/lib/graticule/cli.rb +3 -1
  11. data/lib/graticule/geocoder/google.rb +33 -4
  12. data/lib/graticule/geocoder/mapquest.rb +52 -59
  13. data/lib/graticule/precision.rb +3 -1
  14. data/lib/graticule/version.rb +1 -1
  15. data/site/index.html +114 -0
  16. data/site/plugin.html +82 -0
  17. data/site/stylesheets/style.css +69 -0
  18. data/test/config.yml.default +33 -0
  19. data/test/fixtures/responses/freethepostcode/not_found.txt +3 -0
  20. data/test/fixtures/responses/freethepostcode/success.txt +2 -0
  21. data/test/fixtures/responses/geocoder_ca/success.xml +12 -0
  22. data/test/fixtures/responses/geocoder_us/success.xml +8 -0
  23. data/test/fixtures/responses/geocoder_us/unknown.xml +1 -0
  24. data/test/fixtures/responses/geonames/missing.xml +4 -0
  25. data/test/fixtures/responses/geonames/success.xml +14 -0
  26. data/test/fixtures/responses/geonames/unknown.xml +4 -0
  27. data/test/fixtures/responses/google/address.json +64 -0
  28. data/test/fixtures/responses/google/badkey.json +4 -0
  29. data/test/fixtures/responses/google/country.json +43 -0
  30. data/test/fixtures/responses/google/limit.json +4 -0
  31. data/test/fixtures/responses/google/locality.json +58 -0
  32. data/test/fixtures/responses/google/region.json +48 -0
  33. data/test/fixtures/responses/google/server_error.json +4 -0
  34. data/test/fixtures/responses/google/street.json +58 -0
  35. data/test/fixtures/responses/google/success.json +64 -0
  36. data/test/fixtures/responses/google/success_multiple_results.json +68 -0
  37. data/test/fixtures/responses/google/zero_results.json +4 -0
  38. data/test/fixtures/responses/host_ip/private.txt +4 -0
  39. data/test/fixtures/responses/host_ip/success.txt +4 -0
  40. data/test/fixtures/responses/host_ip/unknown.txt +4 -0
  41. data/test/fixtures/responses/local_search_maps/empty.txt +1 -0
  42. data/test/fixtures/responses/local_search_maps/not_found.txt +1 -0
  43. data/test/fixtures/responses/local_search_maps/success.txt +1 -0
  44. data/test/fixtures/responses/mapquest/multi_result.xml +317 -0
  45. data/test/fixtures/responses/mapquest/success.xml +54 -0
  46. data/test/fixtures/responses/multimap/missing_params.xml +4 -0
  47. data/test/fixtures/responses/multimap/no_matches.xml +4 -0
  48. data/test/fixtures/responses/multimap/success.xml +19 -0
  49. data/test/fixtures/responses/simple_geo/error.json +4 -0
  50. data/test/fixtures/responses/simple_geo/success.json +255 -0
  51. data/test/fixtures/responses/yahoo/success.xml +3 -0
  52. data/test/fixtures/responses/yahoo/unknown_address.xml +6 -0
  53. data/test/fixtures/responses/yandex/badkey.xml +5 -0
  54. data/test/fixtures/responses/yandex/success.xml +204 -0
  55. data/test/graticule/distance_test.rb +59 -0
  56. data/test/graticule/geocoder/freethepostcode_test.rb +37 -0
  57. data/test/graticule/geocoder/geocoder_ca_test.rb +42 -0
  58. data/test/graticule/geocoder/geocoder_us_test.rb +44 -0
  59. data/test/graticule/geocoder/geocoders.rb +56 -0
  60. data/test/graticule/geocoder/geonames_test.rb +56 -0
  61. data/test/graticule/geocoder/google_signed_test.rb +19 -0
  62. data/test/graticule/geocoder/google_test.rb +138 -0
  63. data/test/graticule/geocoder/host_ip_test.rb +41 -0
  64. data/test/graticule/geocoder/local_search_maps_test.rb +31 -0
  65. data/test/graticule/geocoder/mapquest_test.rb +56 -0
  66. data/test/graticule/geocoder/multi_test.rb +52 -0
  67. data/test/graticule/geocoder/multimap_test.rb +53 -0
  68. data/test/graticule/geocoder/simple_geo_test.rb +45 -0
  69. data/test/graticule/geocoder/yahoo_test.rb +50 -0
  70. data/test/graticule/geocoder/yandex_test.rb +42 -0
  71. data/test/graticule/geocoder_test.rb +24 -0
  72. data/test/graticule/location_test.rb +79 -0
  73. data/test/graticule/precision_test.rb +38 -0
  74. data/test/mocks/uri.rb +53 -0
  75. data/test/test_helper.rb +32 -0
  76. metadata +147 -75
  77. data/README.textile +0 -64
@@ -0,0 +1,13 @@
1
+ Autotest.add_hook :initialize do |at|
2
+ at.clear_mappings
3
+
4
+ at.add_mapping %r%^lib/(.*)\.rb$% do |_, m|
5
+ at.files_matching %r%^test/#{m[1]}_test.rb$%
6
+ end
7
+
8
+ at.add_mapping(%r%^test/.*\.rb$%) {|filename, _| filename }
9
+
10
+ at.add_mapping %r%^test/fixtures/(.*)s.yml% do |_, _|
11
+ at.files_matching %r%^test/.*\.rb$%
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ Gemfile.lock
2
+ test/config.yml
3
+ *.gem
4
+ pkg
5
+ coverage
6
+ rdoc
7
+ .DS_Store
8
+ .*.swp
9
+ .*.swo
10
+ .rvmrc
@@ -0,0 +1,14 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ before_script:
6
+ - cp test/config.yml.default test/config.yml
7
+ only:
8
+ - master
9
+ notifications:
10
+ email: false
11
+ webhooks:
12
+ urls:
13
+ - http://buildlight.collectiveidea.com/
14
+ on_start: true
@@ -1,3 +1,6 @@
1
+ 2.4.0 (--)
2
+ * Update MapQuest handler to use the current API
3
+
1
4
  2.3.0 (2013-04-01)
2
5
  * Google v3 API [Adamlb]
3
6
  * Escape XML entities in GeoCoder::MapQuest queries [Simon Coffey]
@@ -72,4 +75,4 @@
72
75
  * added Haversine, Spherical and Vincenty distance calculations
73
76
 
74
77
  0.1 (2006-10-31)
75
- * Initial release
78
+ * Initial release
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem 'mocha'
6
+ gem 'rake'
7
+ gem 'rdoc'
@@ -1,4 +1,4 @@
1
- Copyright 2006 Brandon Keepers, Collective Idea. All rights reserved.
1
+ Copyright 2013 Brandon Keepers, Collective Idea. All rights reserved.
2
2
 
3
3
  Original geocoding code:
4
4
  Copyright 2006 Eric Hodel, The Robot Co-op. All rights reserved.
@@ -0,0 +1,122 @@
1
+ Graticule
2
+ =========
3
+
4
+ ```
5
+ grat·i·cule |ˈgratəˌkyoōl|
6
+ Navigation. a network of parallels and meridians on a map or chart.
7
+ ```
8
+
9
+ Graticule is a geocoding API for looking up address coordinates and performing distance calculations. It supports many popular APIs:
10
+
11
+ * Yahoo
12
+ * Google
13
+ * MapQuest
14
+ * Geocoder.ca
15
+ * Geocoder.us
16
+ * Geonames
17
+ * SimpleGeo
18
+ * Postcode Anywhere
19
+ * MetaCarta
20
+ * FreeThePostcode
21
+ * LocalSearchMaps
22
+ * Yandex
23
+
24
+ ### Installation
25
+
26
+ ```
27
+ gem install graticule
28
+ ```
29
+
30
+ ### Usage
31
+
32
+ There is a companion Rails plugin called [acts_as_geocodable](https://github.com/collectiveidea/acts_as_geocodable) that makes geocoding seem like magic.
33
+
34
+ Graticule exposes to main APIs: location search and distance calculations. Graticule also
35
+ provides a command line utility.
36
+
37
+ #### Location Search / Geocoding
38
+
39
+ ```
40
+ require 'rubygems'
41
+ require 'graticule'
42
+
43
+ geocoder = Graticule.service(:google).new "api_key"
44
+ location = geocoder.locate("61 East 9th Street, Holland, MI")
45
+ ```
46
+
47
+ For specific service documentation, please visit the [RDOCS -- rdoc.info link].
48
+
49
+ #### Distance Calculation
50
+
51
+ Graticule includes 3 different distance formulas, Spherical (simplest but least accurate), Vincenty (most accurate and most complicated), and Haversine (somewhere inbetween). The default is Haversine. There are two ways to calculate the distance between two points.
52
+
53
+ First is `Location#distance_to`:
54
+
55
+ ```
56
+ holland = geocoder.locate("Holland, MI")
57
+ chicago = geocoder.locate("Chicago, IL")
58
+ holland.distance_to(chicago, :formula => :haversine) # or :spherical or :vincenty
59
+ # => 101.997458788177
60
+ ```
61
+
62
+ You can also use the formula classes directly:
63
+
64
+ ```
65
+ Graticule::Distance::Haversine.distance(holland, chicago)
66
+ # => 101.997458788177
67
+ ```
68
+
69
+ All units are miles by default, but you can switch to kilometers with the `units` option
70
+
71
+ ```
72
+ holland.distance_to(chicago, :units => :kilometers)
73
+ #
74
+ Graticule::Distance::Haversine.distance(holland, chicago, :kilometers)
75
+ ```
76
+
77
+
78
+ #### Command Line
79
+
80
+ Graticule includes a command line interface (CLI). The CLI does not currently support all of the implemented services.
81
+
82
+ ```
83
+ $ geocode -s google -a [api_key] Washington, DC
84
+ Washington, DC US
85
+ latitude: 38.895222, longitude: -77.036758
86
+ ```
87
+
88
+ ### Contributing
89
+
90
+ In the spirit of [free software](http://www.fsf.org/licensing/essays/free-sw.html), **everyone** is encouraged to help improve this project.
91
+
92
+ Here are some ways you can contribute:
93
+
94
+ * Reporting bugs
95
+ * Suggesting new features
96
+ * Writing or editing documentation
97
+ * Writing specifications
98
+ * Writing code (**no patch is too small**: fix typos, add comments, clean up inconsistent whitespace)
99
+ * Refactoring code
100
+ * Reviewing patches
101
+
102
+ ### Submitting an Issue
103
+
104
+ We use the [GitHub issue tracker](https://github.com/collectiveidea/graticule/issues) to track bugs and features. Before submitting a bug report or feature request, check to make sure it hasn't already been submitted. When submitting a bug report, please include a [Gist](https://gist.github.com/) that includes a stack trace and any details that may be necessary to reproduce the bug, including your gem version, Ruby version, and operating system.
105
+
106
+ ### Submitting a Pull Request
107
+
108
+ 1. Fork the project.
109
+ 2. Create a topic branch.
110
+ 3. Implement your feature or bug fix.
111
+ 4. Add specs for your feature or bug fix.
112
+ 5. Run `rake`. If your changes are not 100% covered and passing, go back to step 4.
113
+ 6. Commit and push your changes.
114
+ 7. Submit a pull request. Please do not include changes to the gemspec, version, or history file. (If you want to create your own version for some reason, please do so in a separate commit.)
115
+
116
+ ### Other Links
117
+
118
+ [Blog posts about Graticule](http://opensoul.org/tags/geocoding)
119
+
120
+ [Geocoder: Alternative Geocoding library](https://github.com/alex.../geocoder)
121
+
122
+
@@ -0,0 +1,78 @@
1
+ $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
2
+
3
+ require 'bundler/setup'
4
+ require 'graticule/version'
5
+ require 'active_support'
6
+ require 'rake/testtask'
7
+ require 'yaml'
8
+
9
+ desc 'Default: run unit tests.'
10
+ task :default => :test
11
+
12
+ task :build do
13
+ system "gem build graticule.gemspec"
14
+ end
15
+
16
+ task :release => :build do
17
+ system "gem push graticule-#{Graticule::VERSION}.gem"
18
+ end
19
+
20
+ task :install => :build do
21
+ system "gem install graticule-#{Graticule::VERSION}.gem"
22
+ end
23
+
24
+ desc 'Run the unit tests'
25
+ Rake::TestTask.new(:test) do |t|
26
+ t.libs << 'lib' << 'test'
27
+ t.pattern = 'test/**/*_test.rb'
28
+ t.verbose = true
29
+ end
30
+
31
+ require 'active_support'
32
+ require 'net/http'
33
+ require 'uri'
34
+ RESPONSES_PATH = File.dirname(__FILE__) + '/test/fixtures/responses'
35
+
36
+ def cache_responses(service)
37
+ test_config[service.to_s]['responses'].each do |file,url|
38
+ File.open("#{RESPONSES_PATH}/#{service}/#{file}", 'w') do |f|
39
+ f.puts Net::HTTP.get(URI.parse(url))
40
+ end
41
+ end
42
+ end
43
+
44
+ def test_config
45
+ file = File.dirname(__FILE__) + '/test/config.yml'
46
+ unless File.exists?(file)
47
+ raise "API keys not found. Add them by:
48
+ cp #{file}.default #{file}
49
+ vi #{file}
50
+ "
51
+ end
52
+ @test_config ||= YAML.load(File.read(file)).tap do |config|
53
+ config.each do |service,values|
54
+ values['responses'].each {|f,url| update_placeholders!(values, url) }
55
+ end
56
+ end
57
+ end
58
+
59
+ def update_placeholders!(config, thing)
60
+ config.each do |option, value|
61
+ thing.gsub!(":#{option}", value) if value.is_a?(String)
62
+ end
63
+ end
64
+
65
+ namespace :test do
66
+ namespace :cache do
67
+ desc 'Cache test responses from all the geocoders'
68
+ task :all => test_config.keys
69
+
70
+ test_config.keys.each do |service|
71
+ desc "Cache test responses for #{service}"
72
+ task service do
73
+ cache_responses(service)
74
+ end
75
+ end
76
+ end
77
+ end
78
+
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+
3
+ require File.expand_path("../lib/graticule/version.rb", __FILE__)
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "graticule"
7
+ spec.version = Graticule::VERSION
8
+
9
+ spec.authors = ["Brandon Keepers", "Daniel Morrison", "Collective Idea"]
10
+ spec.email = ["brandon@opensoul.org", "daniel@collectiveidea.com", "code@collectiveidea.com"]
11
+ spec.description = "Graticule is a geocoding API that provides a common interface to all the popular services, including Google, Yahoo, Geocoder.us, and MetaCarta."
12
+ spec.summary = "API for using all the popular geocoding services"
13
+ spec.homepage = "https://github.com/collectiveidea/graticule"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($\)
17
+ spec.test_files = spec.files.grep(/^test/)
18
+ spec.executables = ["geocode"]
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "activesupport"
22
+ spec.add_dependency "i18n"
23
+ spec.add_dependency "happymapper", ">= 0.3.0"
24
+ spec.add_dependency "json"
25
+ end
26
+
@@ -23,13 +23,15 @@ module Graticule
23
23
 
24
24
  def self.start(args, out = STDOUT)
25
25
  options = { :service => :yahoo, :api_key => 'YahooDemo' }
26
+ supported_services = %w(yahoo google yandex geocoder_us metacarta)
26
27
 
27
28
  OptionParser.new do |opts|
28
29
  opts.banner = "Usage: geocode [options] location"
29
30
  opts.separator ""
30
31
  opts.separator "Options: "
31
32
 
32
- opts.on("-s service", %w(yahoo google yandex geocoder_us metacarta), "--service service", "Geocoding service") do |service|
33
+ opts.on("-s service", supported_services, "--service service",
34
+ "Geocoding service.", "Currently supported services: #{supported_services.join(", ")}") do |service|
33
35
  options[:service] = service
34
36
  end
35
37
 
@@ -2,16 +2,21 @@
2
2
  require 'json'
3
3
  module Graticule #:nodoc:
4
4
  module Geocoder #:nodoc:
5
- # gg = Graticule.service(:google).new(MAPS_API_KEY) API Key is not required
5
+ # gg = Graticule.service(:google).new
6
6
  # location = gg.locate '1600 Amphitheater Pkwy, Mountain View, CA'
7
7
  # p location.coordinates
8
8
  # #=> [37.423111, -122.081783
9
9
  #
10
+ # If you have a Google business account, initialize with:
11
+ #
12
+ # gg = Graticule.service(:google).new(MAPS_API_KEY, MAPS_CLIENT_ID)
13
+ #
10
14
  class Google < Base
11
15
  # https://developers.google.com/maps/documentation/geocoding/
12
16
 
13
- def initialize(key)
17
+ def initialize(key=nil, client_id=nil)
14
18
  @key = key
19
+ @client_id = client_id
15
20
  @url = URI.parse 'http://maps.googleapis.com/maps/api/geocode/json'
16
21
  end
17
22
 
@@ -129,9 +134,33 @@ module Graticule #:nodoc:
129
134
  end
130
135
 
131
136
  # Creates a URL from the Hash +params+..
137
+ #
138
+ # If initialized with a key and client id for a Business account, signs
139
+ # the url as required by v3 of the library:
140
+ #
141
+ # https://developers.google.com/maps/documentation/business/webservices#digital_signatures
142
+ #
132
143
  def make_url(params) #:nodoc:
133
- super params.merge(:key => @key, :sensor => false)
144
+ if @key && @client_id
145
+ url = super params.merge(:sensor => false, :client => @client_id)
146
+ make_signed_url(url)
147
+ else
148
+ super params.merge(:sensor => false)
149
+ end
134
150
  end
151
+
152
+ def make_signed_url(original_url) #:nodoc:
153
+ require "base64"
154
+ require 'openssl'
155
+ url_to_sign = "#{original_url.path}?#{original_url.query}"
156
+ decoded_key = Base64.decode64(@key.tr("-_", "+/"))
157
+ signature = OpenSSL::HMAC.digest('sha1', decoded_key, url_to_sign)
158
+ encoded_signature = Base64.encode64(signature).tr("+/", "-_")
159
+ signed_url = original_url.to_s + "&signature=#{encoded_signature}"
160
+ #puts signed_url
161
+ URI.parse signed_url
162
+ end
163
+
135
164
  end
136
165
  end
137
- end
166
+ end
@@ -1,40 +1,19 @@
1
- require 'htmlentities'
2
-
3
1
  # encoding: UTF-8
4
2
  module Graticule #:nodoc:
5
3
  module Geocoder #:nodoc:
6
4
 
7
- # Mapquest requires both a client id and a password, which you can
8
- # get by registering at:
9
- # http://developer.mapquest.com/Home/Register?_devAPISignup_WAR_devAPISignup_action=signup&_devAPISignup_WAR_devAPISignup_clientType=Developer
5
+ # Mapquest uses the Licenced Community API which requires an api key. You can sign up an account
6
+ # and get an api key by registering at: http://developer.mapquest.com/
10
7
  #
11
- # mq = Graticule.service(:mapquest).new(CLIENT_ID, PASSWORD)
8
+ # mq = Graticule.service(:mapquest).new(API_KEY)
12
9
  # location = gg.locate('44 Allen Rd., Lovell, ME 04051')
13
10
  # [42.78942, -86.104424]
14
11
  #
15
12
  class Mapquest < Base
16
- # I would link to the documentation here, but there is none that will do anything but confuse you.
17
-
18
- PRECISION = {
19
- 'L1' => Precision::Address,
20
- 'I1' => Precision::Street,
21
- 'B1' => Precision::Street,
22
- 'B2' => Precision::Street,
23
- 'B3' => Precision::Street,
24
- 'Z3' => Precision::PostalCode,
25
- 'Z4' => Precision::PostalCode,
26
- 'Z2' => Precision::PostalCode,
27
- 'Z1' => Precision::PostalCode,
28
- 'A5' => Precision::Locality,
29
- 'A4' => Precision::Region,
30
- 'A3' => Precision::Region,
31
- 'A1' => Precision::Country
32
- }
33
13
 
34
- def initialize(client_id, password)
35
- @password = password
36
- @client_id = client_id
37
- @url = URI.parse('http://geocode.dev.mapquest.com/mq/mqserver.dll')
14
+ def initialize(api_key)
15
+ @api_key = api_key
16
+ @url = URI.parse('http://www.mapquestapi.com/geocoding/v1/address')
38
17
  end
39
18
 
40
19
  # Locates +address+ returning a Location
@@ -45,66 +24,80 @@ module Graticule #:nodoc:
45
24
  protected
46
25
 
47
26
  def make_url(params) #:nodoc
48
- request = Mapquest::Request.new(params[:q], @client_id, @password)
27
+ request = Mapquest::Request.new(params[:q], @api_key)
49
28
  url = @url.dup
50
- url.query = escape(request.query)
29
+ url.query = request.query
51
30
  url
52
31
  end
53
32
 
54
33
  class Request
55
- def initialize(address, client_id, password)
34
+ def initialize(address, api_key)
56
35
  @address = address
57
- @client_id = client_id
58
- @password = password
36
+ @api_key = api_key
59
37
  end
60
38
 
61
39
  def query
62
- "e=5&<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?><Geocode Version=\"1\">#{address_string}#{authentication_string}</Geocode>"
63
- end
64
-
65
- def address_string
66
- "<Address><Street>#{escaped_address}</Street></Address><GeocodeOptionsCollection Count=\"0\"/>"
67
- end
68
-
69
- def authentication_string
70
- "<Authentication Version=\"2\"><Password>#{@password}</Password><ClientId>#{@client_id}</ClientId></Authentication>"
71
- end
72
-
73
- def escaped_address
74
- HTMLEntities.new.encode(@address, :basic)
40
+ "key=#{URI.escape(@api_key)}&outFormat=xml&inFormat=kvp&location=#{URI.escape(@address)}"
75
41
  end
76
42
  end
77
43
 
44
+ # See http://www.mapquestapi.com/geocoding/geocodequality.html#granularity
45
+ PRECISION = {
46
+ 'P1' => Precision::Point,
47
+ 'L1' => Precision::Address,
48
+ 'I1' => Precision::Street,
49
+ 'B1' => Precision::Street,
50
+ 'B2' => Precision::Street,
51
+ 'B3' => Precision::Street,
52
+ 'Z3' => Precision::PostalCode,
53
+ 'Z4' => Precision::PostalCode,
54
+ 'Z2' => Precision::PostalCode,
55
+ 'Z1' => Precision::PostalCode,
56
+ 'A5' => Precision::Locality,
57
+ 'A4' => Precision::Region,
58
+ 'A3' => Precision::Region,
59
+ 'A1' => Precision::Country
60
+ }
61
+
78
62
  class Address
79
63
  include HappyMapper
80
- tag 'GeoAddress'
81
- element :latitude, Float, :tag => 'Lat', :deep => true
82
- element :longitude, Float, :tag => 'Lng', :deep => true
83
- element :street, String, :tag => 'Street'
84
- element :locality, String, :tag => 'AdminArea5'
85
- element :region, String, :tag => 'AdminArea3'
86
- element :postal_code, String, :tag => 'PostalCode'
87
- element :country, String, :tag => 'AdminArea1'
88
- element :result_code, String, :tag => 'ResultCode'
64
+ tag 'location'
65
+ element :latitude, Float, :tag => 'lat', :deep => true
66
+ element :longitude, Float, :tag => 'lng', :deep => true
67
+ element :street, String, :tag => 'street'
68
+ element :locality, String, :tag => 'adminArea5'
69
+ element :region, String, :tag => 'adminArea3'
70
+ element :postal_code, String, :tag => 'postalCode'
71
+ element :country, String, :tag => 'adminArea1'
72
+ element :result_code, String, :tag => 'geocodeQualityCode'
89
73
 
90
74
  def precision
91
75
  PRECISION[result_code.to_s[0,2]] || :unknown
92
76
  end
93
77
  end
94
78
 
79
+ class Locations
80
+ include HappyMapper
81
+ has_many :addresses, Address, :tag => "location"
82
+ end
83
+
95
84
  class Result
96
85
  include HappyMapper
97
- tag 'GeocodeResponse'
98
- has_many :addresses, Address, :deep => true
86
+ has_one :locations, Locations, :tag => "locations"
87
+ end
88
+
89
+ class Response
90
+ include HappyMapper
91
+ has_one :result, Result, :deep => true
99
92
  end
100
93
 
101
94
  def prepare_response(xml)
102
- Result.parse(xml, :single => true)
95
+ Response.parse(xml, :single => true)
103
96
  end
104
97
 
105
98
  # Extracts a location from +xml+.
106
- def parse_response(result) #:nodoc:
107
- addr = result.addresses.first
99
+ def parse_response(response) #:nodoc:
100
+ addr = response.result.locations.addresses.first
108
101
  Location.new(
109
102
  :latitude => addr.latitude,
110
103
  :longitude => addr.longitude,