geocoder 0.1.0
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 +4 -0
- data/LICENSE +20 -0
- data/README +122 -0
- data/Rakefile +42 -0
- data/TODO +10 -0
- data/bin/geocode +4 -0
- data/lib/geocoder.rb +327 -0
- data/test/mocks/csv?address=2038+damen+ave+chicago+il +2 -0
- data/test/mocks/csv?address=2125+w+north+ave+chicago+il +1 -0
- data/test/mocks/csv?address=donotleaveitisnotreal +1 -0
- data/test/mocks/geocode?appid=YahooDemo&location= +4 -0
- data/test/mocks/geocode?appid=YahooDemo&location=2038+damen+ave+chicago+il +2 -0
- data/test/mocks/geocode?appid=YahooDemo&location=2125+w+north+ave+chicago+il +2 -0
- data/test/mocks/geocode?appid=YahooDemo&location=donotleaveitisnotreal +4 -0
- data/test/mocks/http.rb +19 -0
- data/test/sample.xml +2 -0
- data/test/tc_geocoderus.rb +105 -0
- data/test/tc_yahoo.rb +110 -0
- data/test/ts_geocoder.rb +4 -0
- metadata +64 -0
data/CHANGELOG
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2006 Paul Smith <paul@cnt.org>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
= Geocoder: Geocoding library for Ruby and command-line utility
|
2
|
+
|
3
|
+
Author:: Paul Smith <paul@cnt.org>
|
4
|
+
License:: MIT
|
5
|
+
|
6
|
+
Geocoder helps in converting street addresses to coordinate pairs,
|
7
|
+
specifically latitude and longitude (in degrees decimal). It also
|
8
|
+
provides address normalization.
|
9
|
+
|
10
|
+
It comes with a library so that Ruby developers can include geocoding
|
11
|
+
in their applications. It also comes with a command-line application,
|
12
|
+
<tt>geocode</tt>, that is useful for one-offs, shell scripts, and non-Ruby
|
13
|
+
applications.
|
14
|
+
|
15
|
+
== How it works
|
16
|
+
|
17
|
+
The Geocoder is basically a wrapper for geocoding web services.
|
18
|
+
Currently, two services are utilized: {Yahoo! Maps Geocoding
|
19
|
+
API}[http://developer.yahoo.net/maps/rest/V1/geocode.html], which
|
20
|
+
is an interface to their proprietary geocoding system; and
|
21
|
+
Geocoder.us[http://www.geocoder.us], which is basically a Perl
|
22
|
+
frontend to the TIGER/Line file from the US Census Bureau.
|
23
|
+
|
24
|
+
Unless the user or developer indicates otherwise, the Yahoo! Maps
|
25
|
+
API is used by default.
|
26
|
+
|
27
|
+
== Example: library
|
28
|
+
|
29
|
+
If you are going to use this library with the Yahoo! Maps Geocoding
|
30
|
+
service, you need to get yourself an {Application
|
31
|
+
ID}[http://developer.yahoo.net/faq/index.html#appid].
|
32
|
+
|
33
|
+
The very simplest use of the library would just instantiate a
|
34
|
+
Geocoder object and call the <tt>#geocode</tt> method, grab the
|
35
|
+
result, and get the values directly:
|
36
|
+
|
37
|
+
require 'geocoder'
|
38
|
+
geocoder = Geocoder::Yahoo.new "my_app_id" # => #<Geocoder::Yahoo:0x36b77c @appid="my_app_id">
|
39
|
+
result = geocoder.geocode "1600 pennsylvania ave nw washington dc"
|
40
|
+
result.lat # => "38.8987"
|
41
|
+
result.lng # => "-77.037223"
|
42
|
+
result.address # => "1600 PENNSYLVANIA AVE NW"
|
43
|
+
result.city # => "WASHINGTON"
|
44
|
+
result.state # => "DC"
|
45
|
+
result.zip # => "20502-0001"
|
46
|
+
|
47
|
+
The result that the <tt>geocode</tt> method returns is a subclass
|
48
|
+
of Array; each element contains the "closest match" for ambiguous
|
49
|
+
addresses, as determined by the remote geocoding service.
|
50
|
+
|
51
|
+
Any sort of production-level code would want to check for success
|
52
|
+
and watch for exceptions. Geocoding is an inexact science, so you
|
53
|
+
would want to check for success, and iterate over the refinements
|
54
|
+
or closest matches that the service returns:
|
55
|
+
|
56
|
+
require 'geocoder'
|
57
|
+
geocoder = Geocoder::Yahoo.new "my_app_id" # => #<Geocoder::Yahoo:0x36ae44 @appid="my_app_id">
|
58
|
+
result = geocoder.geocode "2038 damen ave chicago il"
|
59
|
+
result.success? # => false
|
60
|
+
result.each do |r|
|
61
|
+
r.lat # => "41.854524", "41.918759"
|
62
|
+
r.lng # => "-87.676083", "-87.67763"
|
63
|
+
r.address # => "2038 S DAMEN AVE", "2038 N DAMEN AVE"
|
64
|
+
r.city # => "CHICAGO", "CHICAGO"
|
65
|
+
r.state # => "IL", "IL"
|
66
|
+
r.zip # => "60608-2625", "60647-4564"
|
67
|
+
end
|
68
|
+
|
69
|
+
<tt>#success?</tt> is defined by a single result returned from the
|
70
|
+
geocoding service (and in the case of the Y! API, with no warning)
|
71
|
+
|
72
|
+
require 'geocoder'
|
73
|
+
geocoder = Geocoder::Yahoo.new "my_app_id"
|
74
|
+
begin
|
75
|
+
result = geocoder.geocode "thisisnotreal"
|
76
|
+
rescue Geocoder::GeocodingError
|
77
|
+
# do something appropriate to your application here
|
78
|
+
end
|
79
|
+
|
80
|
+
The Geocoder module defines 2 exception classes:
|
81
|
+
|
82
|
+
1. BlankLocationString, thrown if a nil or a empty String is given to the geocode method;
|
83
|
+
2. GeocodingError, thrown if the remote service indicates an error, for instance, on an ungeocodeable location
|
84
|
+
|
85
|
+
== Example: command-line utility
|
86
|
+
|
87
|
+
% geocode 2125 W North Ave, Chicago IL
|
88
|
+
Found 1 result(s).
|
89
|
+
------------------
|
90
|
+
Latitude: 41.910263
|
91
|
+
Longitude: -87.680696
|
92
|
+
Address: 2125 W NORTH AVE
|
93
|
+
City: CHICAGO
|
94
|
+
State: IL
|
95
|
+
ZIP Code: 60647-5415
|
96
|
+
|
97
|
+
Notice that the geocoder normalizes the address, including city,
|
98
|
+
state, and ZIP Code.
|
99
|
+
|
100
|
+
You can also throw some switches on it. Try <tt>-q</tt> for quieter, CSV output:
|
101
|
+
|
102
|
+
% geocode -q 1600 pennsylvania ave nw washington dc
|
103
|
+
38.8987,-77.037223,1600 PENNSYLVANIA AVE NW,WASHINGTON,DC,20502-0001
|
104
|
+
|
105
|
+
The order of the fields in the CSV output is:
|
106
|
+
|
107
|
+
1. latitude (degrees decimal)
|
108
|
+
2. longitude (degrees decimal)
|
109
|
+
3. street address
|
110
|
+
4. city
|
111
|
+
5. state
|
112
|
+
6. ZIP Code
|
113
|
+
|
114
|
+
== Usage: command-line utility
|
115
|
+
|
116
|
+
% geocode -h
|
117
|
+
Options:
|
118
|
+
-a, --appid appid Yahoo! Application ID
|
119
|
+
-s, --service service `yahoo' or `geocoderus'
|
120
|
+
-t, --timeout secs Timeout in seconds
|
121
|
+
-q, --quiet Quiet output
|
122
|
+
-h, --help Show this message
|
data/Rakefile
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'rake/rdoctask'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require 'rubygems'
|
4
|
+
|
5
|
+
GEOCODER_VERSION = "0.1.0"
|
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"
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "Generate documentation"
|
19
|
+
task :docs do
|
20
|
+
sh "rdoc -S -N -o doc -t \"Geocoder Documentation\" README TODO lib"
|
21
|
+
end
|
22
|
+
|
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.email = "paul@cnt.org"
|
33
|
+
s.rubyforge_project = "geocoder"
|
34
|
+
s.homepage = "http://geocoder.rubyforge.org"
|
35
|
+
s.bindir = "bin"
|
36
|
+
s.executables = ["geocode"]
|
37
|
+
end
|
38
|
+
|
39
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
40
|
+
pkg.need_zip = true
|
41
|
+
pkg.need_tar = true
|
42
|
+
end
|
data/TODO
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
= Geocoder To-Do list
|
2
|
+
|
3
|
+
Send suggestions to Paul Smith <paul@cnt.org>.
|
4
|
+
|
5
|
+
* unit tests for CLI
|
6
|
+
* many more tests of known and unknown addresses
|
7
|
+
* caching
|
8
|
+
* more services (c'mon, Google!)
|
9
|
+
* Rails plugin
|
10
|
+
* option to "average" results from more than one service
|
data/bin/geocode
ADDED
data/lib/geocoder.rb
ADDED
@@ -0,0 +1,327 @@
|
|
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'
|
30
|
+
|
31
|
+
module Geocoder
|
32
|
+
|
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
|
185
|
+
end
|
186
|
+
|
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
|
327
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
41.910408,-87.680592,2125 W North Ave,Chicago,IL,60647
|
@@ -0,0 +1 @@
|
|
1
|
+
2: couldn't find this address! sorry
|
@@ -0,0 +1,2 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?><ResultSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:yahoo:maps" xsi:schemaLocation="urn:yahoo:maps http://api.local.yahoo.com/MapsService/V1/GeocodeResponse.xsd"><Result precision="address" warning="The exact location could not be found, here is the closest match: 2038 S Damen Ave, Chicago, IL 60608"><Latitude>41.854524</Latitude><Longitude>-87.676083</Longitude><Address>2038 S DAMEN AVE</Address><City>CHICAGO</City><State>IL</State><Zip>60608-2625</Zip><Country>US</Country></Result><Result precision="address" warning="The exact location could not be found, here is the closest match: 2038 N Damen Ave, Chicago, IL 60647"><Latitude>41.918759</Latitude><Longitude>-87.67763</Longitude><Address>2038 N DAMEN AVE</Address><City>CHICAGO</City><State>IL</State><Zip>60647-4564</Zip><Country>US</Country></Result></ResultSet>
|
2
|
+
<!-- ws01.search.scd.yahoo.com uncompressed Fri Feb 3 14:49:21 PST 2006 -->
|
@@ -0,0 +1,2 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?><ResultSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:yahoo:maps" xsi:schemaLocation="urn:yahoo:maps http://api.local.yahoo.com/MapsService/V1/GeocodeResponse.xsd"><Result precision="address"><Latitude>41.910263</Latitude><Longitude>-87.680696</Longitude><Address>2125 W NORTH AVE</Address><City>CHICAGO</City><State>IL</State><Zip>60647-5415</Zip><Country>US</Country></Result></ResultSet>
|
2
|
+
<!-- ws01.search.scd.yahoo.com uncompressed Fri Feb 3 14:49:43 PST 2006 -->
|
data/test/mocks/http.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module Net
|
4
|
+
class HTTP
|
5
|
+
alias_method :old_get, :get
|
6
|
+
def get(path, *args)
|
7
|
+
begin
|
8
|
+
filename = File.dirname(__FILE__) + "/" + File.basename(path)
|
9
|
+
return File.open(filename)
|
10
|
+
rescue Errno::ENOENT
|
11
|
+
old_get(path, *args)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class File
|
18
|
+
alias_method :body, :read
|
19
|
+
end
|
data/test/sample.xml
ADDED
@@ -0,0 +1,2 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?><ResultSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:yahoo:maps" xsi:schemaLocation="urn:yahoo:maps http://api.local.yahoo.com/MapsService/V1/GeocodeResponse.xsd"><Result precision="address"><Latitude>41.910263</Latitude><Longitude>-87.680696</Longitude><Address>2125 W NORTH AVE</Address><City>CHICAGO</City><State>IL</State><Zip>60647-5415</Zip><Country>US</Country></Result></ResultSet>
|
2
|
+
<!-- ws02.search.re2.yahoo.com uncompressed Wed Jan 25 14:06:31 PST 2006 -->
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require File.dirname(__FILE__) + '/../lib/geocoder'
|
3
|
+
require File.dirname(__FILE__) + '/mocks/http'
|
4
|
+
|
5
|
+
class TC_GeoCoderUsSuccess < Test::Unit::TestCase
|
6
|
+
include Geocoder
|
7
|
+
|
8
|
+
def setup
|
9
|
+
@geocoder = GeoCoderUs.new
|
10
|
+
@response = @geocoder.geocode "2125 w north ave chicago il"
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_success
|
14
|
+
assert_equal true, @response.success?
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_latitude
|
18
|
+
assert_in_delta 41.910408, @response.latitude, 0.0005
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_longitude
|
22
|
+
assert_in_delta -87.680592, @response.longitude, 0.0005
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_address
|
26
|
+
assert_equal "2125 W North Ave", @response.address
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_city
|
30
|
+
assert_equal "Chicago", @response.city
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_state
|
34
|
+
assert_equal "IL", @response.state
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_zip
|
38
|
+
assert_equal "60647", @response.zip
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_lat
|
42
|
+
assert_respond_to @response, :lat
|
43
|
+
assert_same @response.lat, @response.latitude
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_lng
|
47
|
+
assert_respond_to @response, :lng
|
48
|
+
assert_same @response.lng, @response.lng
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class TC_GeoCoderUsAmbiguous < Test::Unit::TestCase
|
53
|
+
include Geocoder
|
54
|
+
|
55
|
+
def setup
|
56
|
+
@geocoder = GeoCoderUs.new
|
57
|
+
@response = @geocoder.geocode "2038 damen ave chicago il"
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_success
|
61
|
+
assert_equal false, @response.success?
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_matches
|
65
|
+
assert_equal 2, @response.size
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_warning
|
69
|
+
@response.each do |result|
|
70
|
+
assert_nil result.warning
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_precision
|
75
|
+
@response.each do |result|
|
76
|
+
assert_nil result.precision
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class TC_GeoCoderUsExceptions < Test::Unit::TestCase
|
82
|
+
include Geocoder
|
83
|
+
|
84
|
+
def setup
|
85
|
+
@geocoder = GeoCoderUs.new
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_throws_on_nil
|
89
|
+
assert_raise(Geocoder::BlankLocationString) {
|
90
|
+
@geocoder.geocode nil
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_throws_on_empty_string
|
95
|
+
assert_raise(Geocoder::BlankLocationString) {
|
96
|
+
@geocoder.geocode ""
|
97
|
+
}
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_throws_on_ungeocodeable
|
101
|
+
assert_raise(Geocoder::GeocodingError) {
|
102
|
+
@geocoder.geocode "donotleaveitisnotreal"
|
103
|
+
}
|
104
|
+
end
|
105
|
+
end
|
data/test/tc_yahoo.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require File.dirname(__FILE__) + '/../lib/geocoder'
|
3
|
+
require File.dirname(__FILE__) + '/mocks/http'
|
4
|
+
|
5
|
+
class TC_YahooGeocoderSuccess < Test::Unit::TestCase
|
6
|
+
include Geocoder
|
7
|
+
|
8
|
+
def setup
|
9
|
+
@geocoder = Yahoo.new "YahooDemo"
|
10
|
+
@response = @geocoder.geocode "2125 w north ave chicago il"
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_success
|
14
|
+
assert_equal true, @response.success?
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_latitude
|
18
|
+
assert_in_delta 41.910263, @response.latitude, 0.0005
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_longitude
|
22
|
+
assert_in_delta -87.680696, @response.longitude, 0.0005
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_address
|
26
|
+
assert_equal "2125 W NORTH AVE", @response.address
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_city
|
30
|
+
assert_equal "CHICAGO", @response.city
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_state
|
34
|
+
assert_equal "IL", @response.state
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_zip
|
38
|
+
assert_equal "60647-5415", @response.zip
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_country
|
42
|
+
assert_equal "US", @response.country
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_lat
|
46
|
+
assert_respond_to @response, :lat
|
47
|
+
assert_same @response.lat, @response.latitude
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_lng
|
51
|
+
assert_respond_to @response, :lng
|
52
|
+
assert_same @response.lng, @response.lng
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class TC_YahooGeocoderAmbiguous < Test::Unit::TestCase
|
57
|
+
include Geocoder
|
58
|
+
|
59
|
+
def setup
|
60
|
+
@geocoder = Yahoo.new "YahooDemo"
|
61
|
+
@response = @geocoder.geocode "2038 damen ave chicago il"
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_success
|
65
|
+
assert_equal false, @response.success?
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_matches
|
69
|
+
assert_equal 2, @response.size
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_warning
|
73
|
+
@response.each do |result|
|
74
|
+
assert_match %r{^The exact location could not be found, here is the closest match:},
|
75
|
+
result.warning
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_precision
|
80
|
+
@response.each do |result|
|
81
|
+
assert_equal "address", result.precision
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class TC_YahooGeocoderExceptions < Test::Unit::TestCase
|
87
|
+
include Geocoder
|
88
|
+
|
89
|
+
def setup
|
90
|
+
@geocoder = Yahoo.new "YahooDemo"
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_throws_on_nil
|
94
|
+
assert_raise(Geocoder::BlankLocationString) {
|
95
|
+
@geocoder.geocode nil
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_throws_on_empty_string
|
100
|
+
assert_raise(Geocoder::BlankLocationString) {
|
101
|
+
@geocoder.geocode ""
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_throws_on_ungeocodeable
|
106
|
+
assert_raise(Geocoder::GeocodingError) {
|
107
|
+
@geocoder.geocode "donotleaveitisnotreal"
|
108
|
+
}
|
109
|
+
end
|
110
|
+
end
|
data/test/ts_geocoder.rb
ADDED
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.11
|
3
|
+
specification_version: 1
|
4
|
+
name: geocoder
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.1.0
|
7
|
+
date: 2006-02-08 00:00:00 -06:00
|
8
|
+
summary: Geocoding library and CLI.
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: paul@cnt.org
|
12
|
+
homepage: http://geocoder.rubyforge.org
|
13
|
+
rubyforge_project: geocoder
|
14
|
+
description:
|
15
|
+
autorequire:
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: false
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
authors:
|
29
|
+
- Paul Smith
|
30
|
+
files:
|
31
|
+
- lib/geocoder.rb
|
32
|
+
- bin/geocode
|
33
|
+
- CHANGELOG
|
34
|
+
- LICENSE
|
35
|
+
- Rakefile
|
36
|
+
- README
|
37
|
+
- TODO
|
38
|
+
- test/mocks
|
39
|
+
- test/sample.xml
|
40
|
+
- test/tc_geocoderus.rb
|
41
|
+
- test/tc_yahoo.rb
|
42
|
+
- test/ts_geocoder.rb
|
43
|
+
- test/mocks/csv?address=2038+damen+ave+chicago+il
|
44
|
+
- test/mocks/csv?address=2125+w+north+ave+chicago+il
|
45
|
+
- test/mocks/csv?address=donotleaveitisnotreal
|
46
|
+
- test/mocks/geocode?appid=YahooDemo&location=
|
47
|
+
- test/mocks/geocode?appid=YahooDemo&location=2038+damen+ave+chicago+il
|
48
|
+
- test/mocks/geocode?appid=YahooDemo&location=2125+w+north+ave+chicago+il
|
49
|
+
- test/mocks/geocode?appid=YahooDemo&location=donotleaveitisnotreal
|
50
|
+
- test/mocks/http.rb
|
51
|
+
test_files: []
|
52
|
+
|
53
|
+
rdoc_options: []
|
54
|
+
|
55
|
+
extra_rdoc_files: []
|
56
|
+
|
57
|
+
executables:
|
58
|
+
- geocode
|
59
|
+
extensions: []
|
60
|
+
|
61
|
+
requirements:
|
62
|
+
- none
|
63
|
+
dependencies: []
|
64
|
+
|