geocoder 0.1.1 → 0.9.8

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of geocoder might be problematic. Click here for more details.

data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,96 @@
1
+ = Changelog
2
+
3
+ Per-release changes to Geocoder.
4
+
5
+ == 0.9.8 (2011 Feb 8)
6
+
7
+ * Include geocode:all Rake task in gem (was missing!).
8
+ * Add Geocoder.search for access to Google's full response.
9
+ * Add ability to configure Google connection timeout.
10
+ * Emit warnings on Google connection problems and errors.
11
+ * Refactor: insert Geocoder into ActiveRecord via Railtie.
12
+
13
+ == 0.9.7 (2011 Feb 1)
14
+
15
+ * Add reverse geocoding (+reverse_geocoded_by+).
16
+ * Prevent exception (uninitialized constant Geocoder::Net) when net/http not already required (sleepycat).
17
+ * Refactor: split monolithic Geocoder module into several smaller ones.
18
+
19
+ == 0.9.6 (2011 Jan 19)
20
+
21
+ * Fix incompatibility with will_paginate gem.
22
+ * Include table names in GROUP BY clause of nearby scope to avoid ambiguity in joins (matchu).
23
+
24
+ == 0.9.5 (2010 Oct 15)
25
+
26
+ * Fix broken PostgreSQL compatibility (now 100% compatible).
27
+ * Switch from Google's XML to JSON geocoding API.
28
+ * Separate Rails 2 and Rails 3-compatible branches.
29
+ * Don't allow :conditions hash in 'options' argument to 'nearbys' method (was deprecated in 0.9.3).
30
+
31
+ == 0.9.4 (2010 Aug 2)
32
+
33
+ * Google Maps API key no longer required (uses geocoder v3).
34
+
35
+ == 0.9.3 (2010 Aug 2)
36
+
37
+ * Fix incompatibility with Rails 3 RC 1.
38
+ * Deprecate 'options' argument to 'nearbys' method.
39
+ * Allow inclusion of 'nearbys' in Arel method chains.
40
+
41
+ == 0.9.2 (2010 Jun 3)
42
+
43
+ * Fix LIMIT clause bug in PostgreSQL (reported by kenzie).
44
+
45
+ == 0.9.1 (2010 May 4)
46
+
47
+ * Use scope instead of named_scope in Rails 3.
48
+
49
+ == 0.9.0 (2010 Apr 2)
50
+
51
+ * Fix bug in PostgreSQL support (caused "PGError: ERROR: column "distance" does not exist"), reported by developish.
52
+
53
+ == 0.8.9 (2010 Feb 11)
54
+
55
+ * Add Rails 3 compatibility.
56
+ * Avoid querying Google when query would be an empty string.
57
+
58
+ == 0.8.8 (2009 Dec 7)
59
+
60
+ * Automatically select a less accurate but compatible distance algorithm when SQLite database detected (fixes SQLite incompatibility).
61
+
62
+ == 0.8.7 (2009 Nov 4)
63
+
64
+ * Added Geocoder.geographic_center method.
65
+ * Replaced _get_coordinates class method with read_coordinates instance method.
66
+
67
+ == 0.8.6 (2009 Oct 27)
68
+
69
+ * The fetch_coordinates method now assigns coordinates to attributes (behaves like fetch_coordinates! used to) and fetch_coordinates! both assigns and saves the attributes.
70
+ * Added geocode:all rake task.
71
+
72
+ == 0.8.5 (2009 Oct 26)
73
+
74
+ * Avoid calling deprecated method from within Geocoder itself.
75
+
76
+ == 0.8.4 (2009 Oct 23)
77
+
78
+ * Deprecate <tt>find_near</tt> class method in favor of +near+ named scope.
79
+
80
+ == 0.8.3 (2009 Oct 23)
81
+
82
+ * Update Google URL query string parameter to reflect recent changes in Google's API.
83
+
84
+ == 0.8.2 (2009 Oct 12)
85
+
86
+ * Allow a model's geocoder search string method to be something other than an ActiveRecord attribute.
87
+ * Clean up documentation.
88
+
89
+ == 0.8.1 (2009 Oct 8)
90
+
91
+ * Extract XML-fetching code from Geocoder.search and place in Geocoder._fetch_xml (for ease of mocking).
92
+ * Add tests for coordinate-fetching instance methods.
93
+
94
+ == 0.8.0 (2009 Oct 1)
95
+
96
+ First release.
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2006 Paul Smith <paul@cnt.org>
1
+ Copyright (c) 2009-10 Alex Reisner
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.rdoc ADDED
@@ -0,0 +1,215 @@
1
+ = Geocoder
2
+
3
+ Geocoder adds object geocoding and database-agnostic distance calculations to Ruby on Rails. It's as simple as calling <tt>fetch_coordinates!</tt> on your objects, and then using a scope like <tt>Venue.near("Billings, MT")</tt>. Since it does not rely on proprietary database functions finding geocoded objects in a given area works with out-of-the-box MySQL or even SQLite.
4
+
5
+ Geocoder is compatible with Rails 2.x and 3.x. <b>This is the README for the 3.x branch.</b> Please see the 2.x branch for installation instructions, documentation, and issues.
6
+
7
+
8
+ == 1. Install
9
+
10
+ === As a Gem
11
+
12
+ Add this to your Gemfile:
13
+
14
+ gem "rails-geocoder", :require => "geocoder"
15
+
16
+ and run this at the command prompt:
17
+
18
+ bundle install
19
+
20
+ === Or As a Plugin
21
+
22
+ At the command prompt:
23
+
24
+ rails plugin install git://github.com/alexreisner/geocoder.git
25
+
26
+
27
+ == 2. Configure
28
+
29
+ A) Add +latitude+ and +longitude+ columns to your model:
30
+
31
+ rails generate migration AddLatitudeAndLongitudeToYourModel latitude:float longitude:float
32
+ rake db:migrate
33
+
34
+ B) Tell geocoder where your model stores its address:
35
+
36
+ geocoded_by :address
37
+
38
+ C) Optionally, auto-fetch coordinates every time your model is saved:
39
+
40
+ after_validation :fetch_coordinates
41
+
42
+ <i>Note that you are not stuck with the +latitude+ and +longitude+ column names, or the +address+ method. See "More On Configuration" below for details.</i>
43
+
44
+
45
+ == 3. Use
46
+
47
+ Assuming +obj+ is an instance of a geocoded class, you can get its coordinates:
48
+
49
+ obj.fetch_coordinates # fetches and assigns coordinates
50
+ obj.fetch_coordinates! # also saves lat, lon attributes
51
+
52
+ If you have a lot of objects you can use this Rake task to geocode them all:
53
+
54
+ rake geocode:all CLASS=YourModel
55
+
56
+ Once +obj+ is geocoded you can do things like this:
57
+
58
+ obj.nearbys(30) # other objects within 30 miles
59
+ obj.distance_to(40.714, -100.234) # distance to arbitrary point
60
+
61
+ To find objects by location, use the following scopes:
62
+
63
+ Venue.near('Omaha, NE, US', 20) # venues within 20 miles of Omaha
64
+ Venue.near([40.71, 100.23], 20) # venues within 20 miles of a point
65
+ Venue.geocoded # venues with coordinates
66
+ Venue.not_geocoded # venues without coordinates
67
+
68
+ Some utility methods are also available:
69
+
70
+ # distance (in miles) between Eiffel Tower and Empire State Building
71
+ Geocoder::Calculations.distance_between( 48.858205,2.294359, 40.748433,-73.985655 )
72
+
73
+ # look up coordinates of some location (like searching Google Maps)
74
+ Geocoder.fetch_coordinates("25 Main St, Cooperstown, NY")
75
+
76
+ # find the geographic center (aka center of gravity) of objects or points
77
+ Geocoder::Calculations.geographic_center([ city1, city2, city3, [40.22,-73.99], city4 ])
78
+
79
+
80
+ == More On Configuration
81
+
82
+ You are not stuck with using the +latitude+ and +longitude+ database column names for storing coordinates. For example, to use +lat+ and +lon+:
83
+
84
+ geocoded_by :address, :latitude => :lat, :longitude => :lon
85
+
86
+ The string to use for geocoding can be anything you'd use to search Google Maps. For example, any of the following are acceptable:
87
+
88
+ 714 Green St, Big Town, MO
89
+ Eiffel Tower, Paris, FR
90
+ Paris, TX, US
91
+
92
+ If your model has +address+, +city+, +state+, and +country+ attributes you might do something like this:
93
+
94
+ geocoded_by :location
95
+
96
+ def location
97
+ [address, city, state, country].compact.join(', ')
98
+ end
99
+
100
+ Please see the code (<tt>lib/geocoder/active_record.rb</tt>) for more methods and detailed information about arguments (eg, working with kilometers).
101
+
102
+ You can also set the timeout used for connections to Google's geocoding service. The default is 3 seconds, but if you want to set it to 5 you could put the following in an initializer:
103
+
104
+ Geocoder::Configuration.timeout = 5
105
+
106
+
107
+ == Reverse Geocoding
108
+
109
+ If you need reverse geocoding (lat/long coordinates to address), do something like the following in your model:
110
+
111
+ reverse_geocoded_by :latitude, :longitude
112
+ after_validation :fetch_address
113
+
114
+ and make sure it has +latitude+ and +longitude+ attributes, as well as an +address+ attribute. As with regular geocoding, you can specify alternate names for all of these attributes, for example:
115
+
116
+ reverse_geocoded_by :lat, :lon, :address => :location
117
+
118
+
119
+ == Forward and Reverse Geocoding in the Same Model
120
+
121
+ If you apply both forward and reverse geocoding functionality to the same model, you can provide different methods for storing the fetched address (reverse geocoding) and providing an address to use when fetching coordinates (forward geocoding), for example:
122
+
123
+ class Venue
124
+
125
+ # build an address from street, city, and state attributes
126
+ geocoded_by :address_from_components
127
+
128
+ # store the Google-provided address in the full_address attribute
129
+ reverse_geocoded_by :latitude, :longitude, :address => :full_address
130
+ end
131
+
132
+ However, there can be only one set of latitude/longitude attributes, and whichever you specify last will be used. For example:
133
+
134
+ class Venue
135
+
136
+ geocoded_by :address,
137
+ :latitude => :fetched_latitude, # this will be overridden by the below
138
+ :longitude => :fetched_longitude # same here
139
+
140
+ reverse_geocoded_by :latitude, :longitude
141
+ end
142
+
143
+ The reason for this is that we don't want ambiguity when doing distance calculations. We need a single, authoritative source for coordinates!
144
+
145
+
146
+ == Getting More Information
147
+
148
+ Those familiar with Google's Geocoding API know that it returns much more information than just an address or set of coordinates. If you want access to the entire response you can use the <tt>Geocoder.search</tt> method:
149
+
150
+ results = Geocoder.search("McCarren Park, Brooklyn, NY")
151
+ r = results.first
152
+
153
+ +r+ is now a Geocoder::Result object which has methods like the following:
154
+
155
+ r.geometry
156
+ => {
157
+ "location"=>{"lng"=>-79.3801601, "lat"=>43.6619568},
158
+ "location_type"=>"ROOFTOP",
159
+ "viewport"=>{
160
+ "northeast"=>{"lng"=>-79.3770125, "lat"=>43.6651044},
161
+ "southwest"=>{"lng"=>-79.3833077, "lat"=>43.6588092}
162
+ }
163
+ }
164
+
165
+ r.address_components_of_type(:neighborhood)
166
+ => [{
167
+ "long_name"=>"Greenpoint",
168
+ "short_name"=>"Greenpoint",
169
+ "types"=>["neighborhood", "political"]
170
+ }]
171
+
172
+ Please see the Geocoder::Result class for more information, as well as Google's API documentation (http://code.google.com/apis/maps/documentation/geocoding/#JSON).
173
+
174
+
175
+ == SQLite
176
+
177
+ SQLite's lack of trigonometric functions requires an alternate implementation of the +near+ method (scope). When using SQLite, Geocoder will automatically use a less accurate algorithm for finding objects near a given point. Results of this algorithm should not be trusted too much as it will return objects that are outside the given radius.
178
+
179
+ It is also not possible to calculate distances between points without the trig functions so you cannot sort results by "nearness."
180
+
181
+
182
+ === Discussion
183
+
184
+ There are few options for finding objects near a given point in SQLite without installing extensions:
185
+
186
+ 1. Use a square instead of a circle for finding nearby points. For example, if you want to find points near 40.71, 100.23, search for objects with latitude between 39.71 and 41.71 and longitude between 99.23 and 101.23. One degree of latitude or longitude is at most 69 miles so divide your radius (in miles) by 69.0 to get the amount to add and subtract from your center coordinates to get the upper and lower bounds. The results will not be very accurate (you'll get points outside the desired radius--at worst 29% farther away), but you will get all the points within the required radius.
187
+
188
+ 2. Load all objects into memory and compute distances between them using the <tt>Geocoder::Calculations.distance_between</tt> method. This will produce accurate results but will be very slow (and use a lot of memory) if you have a lot of objects in your database.
189
+
190
+ 3. If you have a large number of objects (so you can't use approach #2) and you need accurate results (better than approach #1 will give), you can use a combination of the two. Get all the objects within a square around your center point, and then eliminate the ones that are too far away using <tt>Geocoder::Calculations.distance_between</tt>.
191
+
192
+ Because Geocoder needs to provide this functionality as a scope, we must go with option #1, but feel free to implement #2 or #3 if you need more accuracy.
193
+
194
+
195
+ == Known Issue
196
+
197
+ You cannot use the +near+ scope with another scope that provides an +includes+ option because the +SELECT+ clause generated by +near+ will overwrite it (or vice versa). Instead, try using +joins+ and pass a <tt>:select</tt> option to the +near+ scope to get the columns you want. For example, in Rails 2 syntax:
198
+
199
+ # instead of :includes => :venues:
200
+ City.near("Omaha, NE", 20, :select => "venues.*").all(:joins => :venues)
201
+
202
+ If anyone has a more elegant solution to this problem I am very interested in seeing it.
203
+
204
+
205
+ == To-do List
206
+
207
+ * support different ORMs (DataMapper, Mongoid, etc)
208
+ * use completely separate "drivers" for different AR adapters?
209
+ * seems reasonable since we're using very DB-specific features
210
+ * also need to make sure 'mysql2' is supported
211
+ * make 'near' scope work with AR associations
212
+ * http://stackoverflow.com/questions/3266358/geocoder-rails-plugin-near-search-problem-with-activerecord
213
+
214
+
215
+ Copyright (c) 2009-11 Alex Reisner, released under the MIT license
data/Rakefile CHANGED
@@ -1,53 +1,56 @@
1
- require 'rake/rdoctask'
2
- require 'rake/gempackagetask'
3
1
  require 'rubygems'
4
-
5
- GEOCODER_VERSION = "0.1.1"
6
-
7
- PKG_FILES = FileList["lib/**/*", "bin/**/*", "[A-Z]*",
8
- "test/**/*"].exclude(/\b\.svn\b/)
9
-
10
- desc "Run all tests"
11
- task :default => [ :test ]
12
-
13
- desc "Run all tests"
14
- task :test do
15
- ruby "test/ts_geocoder.rb"
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "rails-geocoder"
8
+ gem.summary = %Q{Add geocoding functionality to Rails models.}
9
+ gem.description = %Q{Geocoder adds object geocoding and database-agnostic distance calculations to Ruby on Rails. It does not rely on proprietary database functions so finding geocoded objects in a given area is easily done using out-of-the-box MySQL or even SQLite.}
10
+ gem.email = "alex@alexreisner.com"
11
+ gem.homepage = "http://github.com/alexreisner/geocoder"
12
+ gem.authors = ["Alex Reisner"]
13
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
14
+ end
15
+ Jeweler::GemcutterTasks.new
16
+ rescue LoadError
17
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
16
18
  end
17
19
 
18
- desc "Generate documentation"
19
- task :docs do
20
- sh "rdoc -S -N -o doc -t \"Geocoder Documentation\" README TODO lib"
20
+ require 'rake/testtask'
21
+ Rake::TestTask.new(:test) do |test|
22
+ test.libs << 'lib' << 'test'
23
+ test.pattern = 'test/**/*_test.rb'
24
+ test.verbose = true
21
25
  end
22
26
 
23
- spec = Gem::Specification.new do |s|
24
- s.platform = Gem::Platform::RUBY
25
- s.summary = "Geocoding library and CLI."
26
- s.name = "geocoder"
27
- s.version = GEOCODER_VERSION
28
- s.requirements << "none"
29
- s.require_path = "lib"
30
- s.author = "Paul Smith"
31
- s.files = PKG_FILES
32
- s.autorequire = "geocoder"
33
- s.email = "paul@cnt.org"
34
- s.rubyforge_project = "geocoder"
35
- s.homepage = "http://geocoder.rubyforge.org"
36
- s.bindir = "bin"
37
- s.executables = ["geocode"]
27
+ begin
28
+ require 'rcov/rcovtask'
29
+ Rcov::RcovTask.new do |test|
30
+ test.libs << 'test'
31
+ test.pattern = 'test/**/*_test.rb'
32
+ test.verbose = true
33
+ end
34
+ rescue LoadError
35
+ task :rcov do
36
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
37
+ end
38
38
  end
39
39
 
40
- Rake::GemPackageTask.new(spec) do |pkg|
41
- pkg.need_zip = true
42
- pkg.need_tar = true
43
- end
40
+ task :test => :check_dependencies
44
41
 
45
- desc "Install local gem"
46
- task :install => [ :repackage ] do
47
- sh "sudo gem install -l pkg/geocoder-#{GEOCODER_VERSION}.gem"
48
- end
42
+ task :default => :test
49
43
 
50
- desc "Uninstall gem"
51
- task :uninstall do
52
- sh "sudo gem uninstall geocoder"
44
+ require 'rake/rdoctask'
45
+ Rake::RDocTask.new do |rdoc|
46
+ if File.exist?('VERSION')
47
+ version = File.read('VERSION')
48
+ else
49
+ version = ""
50
+ end
51
+
52
+ rdoc.rdoc_dir = 'rdoc'
53
+ rdoc.title = "geocoder #{version}"
54
+ rdoc.rdoc_files.include('README*')
55
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
56
  end
data/lib/geocoder.rb CHANGED
@@ -1,327 +1,23 @@
1
- #--
2
- # Copyright (c) 2006 Paul Smith <paul@cnt.org>
3
- #
4
- # Permission is hereby granted, free of charge, to any person obtaining
5
- # a copy of this software and associated documentation files (the
6
- # "Software"), to deal in the Software without restriction, including
7
- # without limitation the rights to use, copy, modify, merge, publish,
8
- # distribute, sublicense, and/or sell copies of the Software, and to
9
- # permit persons to whom the Software is furnished to do so, subject to
10
- # the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be
13
- # included in all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
- # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
- # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
- # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
- #++
23
- #
24
- # = Geocoder -- Geocoding library for Ruby
25
- #
26
- require 'cgi'
27
- require 'net/http'
28
- require 'rexml/document'
29
- require 'timeout'
1
+ require "geocoder/configuration"
2
+ require "geocoder/calculations"
3
+ require "geocoder/lookup"
4
+ require "geocoder/result"
5
+ require "geocoder/active_record"
6
+ require "geocoder/railtie"
30
7
 
31
8
  module Geocoder
9
+ extend self
32
10
 
33
- class BlankLocationString < Exception; end
34
- class GeocodingError < Exception; end
35
-
36
- FIELDS = [ ["latitude", "Latitude"],
37
- ["longitude", "Longitude"],
38
- ["address", "Address"],
39
- ["city", "City"],
40
- ["state", "State"],
41
- ["zip", "ZIP Code"] ].freeze
42
-
43
- class Base
44
- # +location+ is a string, any of the following:
45
- # * city, state
46
- # * city, state, zip
47
- # * zip
48
- # * street, city, state
49
- # * street, city, state, zip
50
- # * street, zip
51
- def geocode location, *args
52
- options = { :timeout => nil }
53
- options.update(args.pop) if args.last.is_a?(Hash)
54
- @options = options
55
- if location.nil? or location.empty?
56
- raise BlankLocationString
57
- end
58
- location = String location
59
- results = parse request(location)
60
- create_response results
61
- end
62
-
63
- def create_response results
64
- Response.new results
65
- end
66
-
67
- # Makes an HTTP GET request on URL and returns the body
68
- # of the response
69
- def get url, timeout=5
70
- url = URI.parse url
71
- http = Net::HTTP.new url.host, url.port
72
- res = Timeout::timeout(timeout) {
73
- http.get url.request_uri
74
- }
75
- res.body
76
- end
77
-
78
- def request location
79
- get url(location), @options[:timeout]
80
- end
81
- end
82
-
83
- class GeoCoderUs < Base
84
- def initialize *args
85
- #
86
- end
87
-
88
- private
89
-
90
- def parse csv_text
91
- if csv_text =~ /^2: /
92
- raise GeocodingError, csv_text.split(": ")[1]
93
- end
94
- results = []
95
- csv_text.split("\n").each do |line|
96
- latitude, longitude, address, city, state, zip = line.split ","
97
- result = Result.new
98
- result.latitude = latitude
99
- result.longitude = longitude
100
- result.address = address
101
- result.city = city
102
- result.state = state
103
- result.zip = zip
104
- results << result
105
- end
106
- results
107
- end
108
-
109
- # Returns URL of geocoder.us web service
110
- def url address
111
- "http://rpc.geocoder.us/service/csv?address=#{CGI.escape address}"
112
- end
113
- end
114
-
115
- class Yahoo < Base
116
- include REXML
117
- # Requires a Y! Application ID
118
- # http://developer.yahoo.net/faq/index.html#appid
119
- def initialize appid
120
- @appid = appid
121
- end
122
-
123
- private
124
-
125
- # return array of results
126
- def parse xml
127
- # Create a new REXML::Document object from the raw XML text
128
- xml = Document.new xml
129
- #
130
- # Normally, Y! will return an XML document with the root node
131
- # <ResultSet>; if the request bombs, they return one with the
132
- # root node <Error>
133
- if is_error? xml
134
- msgs = []
135
- # Bubble up an exception using the error messages from Y!
136
- xml.root.elements.each("Message") { |e| msgs << e.get_text.value }
137
- raise GeocodingError, msgs.join(", ")
138
- else
139
- results = []
140
- xml.root.elements.each "Result" do |e|
141
- result = Result.new
142
- # add fields
143
- fields.each do |field|
144
- text = e.elements[field.capitalize].get_text
145
- if text.respond_to? :value
146
- result.send "#{field}=", text.value
147
- end
148
- end
149
- # add attributes
150
- attributes.each do |attribute|
151
- result.send "#{attribute}=", e.attributes[attribute]
152
- end
153
- results << result
154
- end
155
- results
156
- end
157
- end
158
-
159
- def fields
160
- %w| latitude longitude address city state zip country |
161
- end
162
-
163
- def attributes
164
- %w| precision warning |
165
- end
166
-
167
- def is_error? document
168
- document.root.name == "Error"
169
- end
170
-
171
- # Returns URL of Y! Geocoding web service
172
- def url location
173
- "http://api.local.yahoo.com/MapsService/V1/geocode?appid=#{@appid}&location=#{CGI.escape location}"
174
- end
175
- end
176
-
177
- SERVICES = { :yahoo => Yahoo,
178
- :geocoderus => GeoCoderUs }.freeze
179
-
180
- class Result < Struct.new :latitude, :longitude, :address, :city,
181
- :state, :zip, :country, :precision,
182
- :warning
183
- alias :lat :latitude
184
- alias :lng :longitude
11
+ ##
12
+ # Alias for Geocoder::Lookup.search.
13
+ #
14
+ def search(*args)
15
+ Lookup.search(*args)
185
16
  end
186
17
 
187
- # A Response is a representation of the entire response from the
188
- # Y! Geocoding web service, which may include multiple results,
189
- # as well as warnings and errors
190
- class Response < Array
191
- def initialize results
192
- results.each do |result|
193
- self << result
194
- end
195
- end
196
-
197
- # Geocoding was an unqualified success if one result in the result
198
- # set is retured and there is no warning attribute in that result
199
- def success?
200
- size == 1 and self[0].warning.nil?
201
- end
202
-
203
- def bullseye?
204
- success?
205
- end
206
-
207
- # Returns latitude in degrees decimal
208
- def latitude
209
- self[0].latitude if bullseye?
210
- end
211
-
212
- # Returns longitude in degrees decimal
213
- def longitude
214
- self[0].longitude if bullseye?
215
- end
216
-
217
- # Returns normalized street address, capitalized
218
- def address
219
- self[0].address if bullseye?
220
- end
221
-
222
- # Returns normalized city name, capitalized
223
- def city
224
- self[0].city if bullseye?
225
- end
226
-
227
- # Returns normalized two-letter USPS state abbreviation
228
- def state
229
- self[0].state if bullseye?
230
- end
231
-
232
- alias_method :array_zip, :zip
233
-
234
- # Returns normalized ZIP Code, or postal code
235
- def zip
236
- self[0].zip if bullseye?
237
- end
238
-
239
- # Returns two-letter country code abbreviation
240
- def country
241
- self[0].country if bullseye?
242
- end
243
-
244
- alias :lat :latitude
245
- alias :lng :longitude
246
- end
247
-
248
- class Cli
249
- require 'optparse'
250
- require 'ostruct'
251
-
252
- def self.parse args
253
- options = OpenStruct.new
254
- # default values
255
- options.appid = "YahooDemo"
256
- options.service = Yahoo
257
- options.timeout = 5
258
- opts = OptionParser.new do |opts|
259
- opts.banner = "Usage: geocode [options] location"
260
- opts.separator ""
261
- opts.separator "Options:"
262
- opts.on "-a appid", "--appid appid", "Yahoo! Application ID" do |a|
263
- options.appid = a
264
- end
265
- opts.on "-s service", "--service service", "`yahoo' or `geocoderus'" do |s|
266
- options.service = SERVICES[s]
267
- end
268
- opts.on "-t secs", "--timeout secs", Integer, "Timeout in seconds" do |t|
269
- options.timeout = t
270
- end
271
- opts.on "-q", "--quiet", "Quiet output" do |q|
272
- options.quiet = q
273
- end
274
- opts.on_tail "-h", "--help", "Show this message" do
275
- puts opts
276
- exit
277
- end
278
- opts.parse! args
279
- end
280
- [options, opts]
281
- end
282
-
283
- def initialize cli_args
284
- @options, @opt_parser = Cli::parse cli_args
285
- @location = cli_args.join " "
286
- end
287
-
288
- def report result
289
- buffer = []
290
- if @options.quiet
291
- result.each do |r|
292
- buffer << FIELDS.collect do |k,v|
293
- r.send k
294
- end.join(",")
295
- end
296
- else
297
- buffer << "Found #{result.size} result(s)."
298
- buffer << buffer.last.gsub(/./, "-")
299
- buffer << result.collect do |r|
300
- FIELDS.collect do |k,v|
301
- "#{v}: #{r.send k}"
302
- end.join("\n")
303
- end.join("\n- - - -\n")
304
- end
305
- puts buffer.join("\n")
306
- end
307
-
308
- def go!
309
- g = @options.service.new @options.appid
310
- begin
311
- result = g.geocode @location, :timeout => @options.timeout
312
- report result
313
- rescue BlankLocationString
314
- STDERR.puts "You have to give an address to geocode!"
315
- puts
316
- puts @opt_parser
317
- exit
318
- rescue Timeout::Error
319
- STDERR.puts "The remote geocoding service timed-out. Try increasing the timeout value (-t)."
320
- exit
321
- rescue Geocoder::GeocodingError => e
322
- STDERR.puts "Geocoder: #{e}"
323
- exit
324
- end
325
- end
326
- end
18
+ # exception classes
19
+ class Error < StandardError; end
20
+ class ConfigurationError < Error; end
327
21
  end
22
+
23
+ Geocoder::Railtie.insert