CodeMonkeySteve-graticule 0.2.11
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/CHANGELOG.txt +61 -0
- data/LICENSE.txt +30 -0
- data/Manifest.txt +85 -0
- data/README.txt +41 -0
- data/Rakefile +143 -0
- data/VERSION +1 -0
- data/bin/geocode +5 -0
- data/graticule.gemspec +144 -0
- data/init.rb +2 -0
- data/lib/graticule.rb +26 -0
- data/lib/graticule/cli.rb +64 -0
- data/lib/graticule/core_ext.rb +15 -0
- data/lib/graticule/distance.rb +18 -0
- data/lib/graticule/distance/haversine.rb +40 -0
- data/lib/graticule/distance/spherical.rb +52 -0
- data/lib/graticule/distance/vincenty.rb +76 -0
- data/lib/graticule/geocoder.rb +21 -0
- data/lib/graticule/geocoder/base.rb +112 -0
- data/lib/graticule/geocoder/bogus.rb +15 -0
- data/lib/graticule/geocoder/geocoder_ca.rb +54 -0
- data/lib/graticule/geocoder/geocoder_us.rb +51 -0
- data/lib/graticule/geocoder/google.rb +104 -0
- data/lib/graticule/geocoder/host_ip.rb +41 -0
- data/lib/graticule/geocoder/local_search_maps.rb +45 -0
- data/lib/graticule/geocoder/mapquest.rb +87 -0
- data/lib/graticule/geocoder/meta_carta.rb +33 -0
- data/lib/graticule/geocoder/multi.rb +46 -0
- data/lib/graticule/geocoder/multimap.rb +73 -0
- data/lib/graticule/geocoder/postcode_anywhere.rb +63 -0
- data/lib/graticule/geocoder/rest.rb +18 -0
- data/lib/graticule/geocoder/yahoo.rb +84 -0
- data/lib/graticule/location.rb +61 -0
- data/lib/graticule/version.rb +9 -0
- data/site/index.html +114 -0
- data/site/plugin.html +82 -0
- data/site/stylesheets/style.css +69 -0
- data/test/config.yml.default +37 -0
- data/test/fixtures/responses/geocoder_us/success.xml +18 -0
- data/test/fixtures/responses/geocoder_us/unknown.xml +1 -0
- data/test/fixtures/responses/google/badkey.xml +1 -0
- data/test/fixtures/responses/google/limit.xml +10 -0
- data/test/fixtures/responses/google/missing_address.xml +1 -0
- data/test/fixtures/responses/google/only_coordinates.xml +1 -0
- data/test/fixtures/responses/google/partial.xml +1 -0
- data/test/fixtures/responses/google/server_error.xml +10 -0
- data/test/fixtures/responses/google/success.xml +1 -0
- data/test/fixtures/responses/google/success_multiple_results.xml +88 -0
- data/test/fixtures/responses/google/success_on_options.xml +16 -0
- data/test/fixtures/responses/google/unavailable.xml +1 -0
- data/test/fixtures/responses/google/unknown_address.xml +1 -0
- data/test/fixtures/responses/host_ip/private.txt +4 -0
- data/test/fixtures/responses/host_ip/success.txt +4 -0
- data/test/fixtures/responses/host_ip/unknown.txt +4 -0
- data/test/fixtures/responses/local_search_maps/empty.txt +1 -0
- data/test/fixtures/responses/local_search_maps/not_found.txt +1 -0
- data/test/fixtures/responses/local_search_maps/success.txt +1 -0
- data/test/fixtures/responses/mapquest/multi_result.xml +1 -0
- data/test/fixtures/responses/mapquest/success.xml +1 -0
- data/test/fixtures/responses/meta_carta/bad_address.xml +17 -0
- data/test/fixtures/responses/meta_carta/multiple.xml +33 -0
- data/test/fixtures/responses/meta_carta/success.xml +31 -0
- data/test/fixtures/responses/multimap/missing_params.xml +4 -0
- data/test/fixtures/responses/multimap/no_matches.xml +4 -0
- data/test/fixtures/responses/multimap/success.xml +19 -0
- data/test/fixtures/responses/postcode_anywhere/badkey.xml +9 -0
- data/test/fixtures/responses/postcode_anywhere/canada.xml +16 -0
- data/test/fixtures/responses/postcode_anywhere/empty.xml +16 -0
- data/test/fixtures/responses/postcode_anywhere/success.xml +16 -0
- data/test/fixtures/responses/postcode_anywhere/uk.xml +18 -0
- data/test/fixtures/responses/yahoo/success.xml +3 -0
- data/test/fixtures/responses/yahoo/unknown_address.xml +6 -0
- data/test/mocks/uri.rb +52 -0
- data/test/test_helper.rb +31 -0
- data/test/unit/graticule/distance_test.rb +58 -0
- data/test/unit/graticule/geocoder/geocoder_us_test.rb +43 -0
- data/test/unit/graticule/geocoder/geocoders.rb +56 -0
- data/test/unit/graticule/geocoder/google_test.rb +127 -0
- data/test/unit/graticule/geocoder/host_ip_test.rb +40 -0
- data/test/unit/graticule/geocoder/local_search_maps_test.rb +30 -0
- data/test/unit/graticule/geocoder/mapquest_test.rb +47 -0
- data/test/unit/graticule/geocoder/meta_carta_test.rb +44 -0
- data/test/unit/graticule/geocoder/multi_test.rb +43 -0
- data/test/unit/graticule/geocoder/multimap_test.rb +52 -0
- data/test/unit/graticule/geocoder/postcode_anywhere_test.rb +50 -0
- data/test/unit/graticule/geocoder/yahoo_test.rb +48 -0
- data/test/unit/graticule/geocoder_test.rb +27 -0
- data/test/unit/graticule/location_test.rb +73 -0
- metadata +167 -0
data/init.rb
ADDED
data/lib/graticule.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
|
5
|
+
require 'graticule/version'
|
6
|
+
require 'graticule/core_ext'
|
7
|
+
require 'graticule/location'
|
8
|
+
require 'graticule/geocoder'
|
9
|
+
require 'graticule/geocoder/base'
|
10
|
+
require 'graticule/geocoder/bogus'
|
11
|
+
require 'graticule/geocoder/rest'
|
12
|
+
require 'graticule/geocoder/google'
|
13
|
+
require 'graticule/geocoder/host_ip'
|
14
|
+
require 'graticule/geocoder/multi'
|
15
|
+
require 'graticule/geocoder/yahoo'
|
16
|
+
require 'graticule/geocoder/geocoder_ca'
|
17
|
+
require 'graticule/geocoder/geocoder_us'
|
18
|
+
require 'graticule/geocoder/local_search_maps'
|
19
|
+
require 'graticule/geocoder/meta_carta'
|
20
|
+
require 'graticule/geocoder/postcode_anywhere'
|
21
|
+
require 'graticule/geocoder/multimap'
|
22
|
+
require 'graticule/geocoder/mapquest'
|
23
|
+
require 'graticule/distance'
|
24
|
+
require 'graticule/distance/haversine'
|
25
|
+
require 'graticule/distance/spherical'
|
26
|
+
require 'graticule/distance/vincenty'
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'graticule'
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
module Graticule
|
5
|
+
|
6
|
+
# A command line interface for geocoding. From the command line, run:
|
7
|
+
#
|
8
|
+
# geocode 49423
|
9
|
+
#
|
10
|
+
# Outputs:
|
11
|
+
#
|
12
|
+
# # Holland, MI 49423 US
|
13
|
+
# # latitude: 42.7654, longitude: -86.1085
|
14
|
+
#
|
15
|
+
# == Usage: geocode [options] location
|
16
|
+
#
|
17
|
+
# Options:
|
18
|
+
# -s, --service service Geocoding service
|
19
|
+
# -a, --apikey apikey API key for the selected service
|
20
|
+
# -h, --help Help
|
21
|
+
class Cli
|
22
|
+
|
23
|
+
def self.start(args, out = STDOUT)
|
24
|
+
options = { :service => :yahoo, :api_key => 'YahooDemo' }
|
25
|
+
|
26
|
+
OptionParser.new do |opts|
|
27
|
+
opts.banner = "Usage: geocode [options] location"
|
28
|
+
opts.separator ""
|
29
|
+
opts.separator "Options: "
|
30
|
+
|
31
|
+
opts.on("-s service", %w(yahoo google geocoder_us metacarta), "--service service", "Geocoding service") do |service|
|
32
|
+
options[:service] = service
|
33
|
+
end
|
34
|
+
|
35
|
+
opts.on("-a apikey", "--apikey apikey", "API key for the selected service")
|
36
|
+
|
37
|
+
opts.on_tail("-h", "--help", "Help") do
|
38
|
+
puts opts
|
39
|
+
exit
|
40
|
+
end
|
41
|
+
end.parse! args
|
42
|
+
|
43
|
+
options[:location] = args.join(" ")
|
44
|
+
|
45
|
+
result = Graticule.service(options[:service]).new(*options[:api_key].split(',')).locate(options[:location])
|
46
|
+
location = (result.is_a?(Array) ? result.first : result)
|
47
|
+
if location
|
48
|
+
out << location.to_s(:coordinates => true)
|
49
|
+
exit 0
|
50
|
+
else
|
51
|
+
out << "Location not found"
|
52
|
+
exit 1
|
53
|
+
end
|
54
|
+
rescue Graticule::CredentialsError
|
55
|
+
$stderr.puts "Invalid API key. Pass your #{options[:service]} API key using the -a option. "
|
56
|
+
rescue OptionParser::InvalidArgument, OptionParser::InvalidOption,
|
57
|
+
Graticule::Error => error
|
58
|
+
$stderr.puts error.message
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Graticule
|
2
|
+
module RadiansAndDegrees
|
3
|
+
# Convert from degrees to radians
|
4
|
+
def to_radians
|
5
|
+
( self / 360.0 ) * Math::PI * 2
|
6
|
+
end
|
7
|
+
|
8
|
+
# Convert from radians to degrees
|
9
|
+
def to_degrees
|
10
|
+
( self * 360.0 ) / Math::PI / 2
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
Numeric.send :include, Graticule::RadiansAndDegrees
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Graticule
|
2
|
+
module Distance
|
3
|
+
|
4
|
+
EARTH_RADIUS = { :kilometers => 6378.135, :miles => 3963.1676 }
|
5
|
+
# WGS-84 numbers
|
6
|
+
EARTH_MAJOR_AXIS_RADIUS = { :kilometers => 6378.137, :miles => 3963.19059 }
|
7
|
+
EARTH_MINOR_AXIS_RADIUS = { :kilometers => 6356.7523142, :miles => 3949.90276 }
|
8
|
+
|
9
|
+
class DistanceFormula
|
10
|
+
include Math
|
11
|
+
extend Math
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
raise NotImplementedError
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Graticule
|
2
|
+
module Distance
|
3
|
+
#
|
4
|
+
# The Haversine Formula works better at small distances than the Spherical Law of Cosines
|
5
|
+
#
|
6
|
+
# Thanks to Chris Veness (http://www.movable-type.co.uk/scripts/LatLong.html)
|
7
|
+
# for distance formulas.
|
8
|
+
#
|
9
|
+
class Haversine < DistanceFormula
|
10
|
+
|
11
|
+
# Calculate the distance between two Locations using the Haversine formula
|
12
|
+
#
|
13
|
+
# Graticule::Distance::Haversine.distance(
|
14
|
+
# Graticule::Location.new(:latitude => 42.7654, :longitude => -86.1085),
|
15
|
+
# Graticule::Location.new(:latitude => 41.849838, :longitude => -87.648193)
|
16
|
+
# )
|
17
|
+
# #=> 101.061720831836
|
18
|
+
#
|
19
|
+
def self.distance(from, to, units = :miles)
|
20
|
+
from_longitude = from.longitude.to_radians
|
21
|
+
from_latitude = from.latitude.to_radians
|
22
|
+
to_longitude = to.longitude.to_radians
|
23
|
+
to_latitude = to.latitude.to_radians
|
24
|
+
|
25
|
+
latitude_delta = to_latitude - from_latitude
|
26
|
+
longitude_delta = to_longitude - from_longitude
|
27
|
+
|
28
|
+
a = sin(latitude_delta/2)**2 +
|
29
|
+
cos(from_latitude) *
|
30
|
+
cos(to_latitude) *
|
31
|
+
sin(longitude_delta/2)**2
|
32
|
+
|
33
|
+
c = 2 * atan2(sqrt(a), sqrt(1-a))
|
34
|
+
|
35
|
+
d = EARTH_RADIUS[units.to_sym] * c
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Graticule
|
2
|
+
module Distance
|
3
|
+
|
4
|
+
#
|
5
|
+
# The Spherical Law of Cosines is the simplist though least accurate distance
|
6
|
+
# formula (earth isn't a perfect sphere).
|
7
|
+
#
|
8
|
+
class Spherical < DistanceFormula
|
9
|
+
|
10
|
+
# Calculate the distance between two Locations using the Spherical formula
|
11
|
+
#
|
12
|
+
# Graticule::Distance::Spherical.distance(
|
13
|
+
# Graticule::Location.new(:latitude => 42.7654, :longitude => -86.1085),
|
14
|
+
# Graticule::Location.new(:latitude => 41.849838, :longitude => -87.648193)
|
15
|
+
# )
|
16
|
+
# #=> 101.061720831853
|
17
|
+
#
|
18
|
+
def self.distance(from, to, units = :miles)
|
19
|
+
from_longitude = from.longitude.to_radians
|
20
|
+
from_latitude = from.latitude.to_radians
|
21
|
+
to_longitude = to.longitude.to_radians
|
22
|
+
to_latitude = to.latitude.to_radians
|
23
|
+
|
24
|
+
Math.acos(
|
25
|
+
Math.sin(from_latitude) *
|
26
|
+
Math.sin(to_latitude) +
|
27
|
+
|
28
|
+
Math.cos(from_latitude) *
|
29
|
+
Math.cos(to_latitude) *
|
30
|
+
Math.cos(to_longitude - from_longitude)
|
31
|
+
) * EARTH_RADIUS[units.to_sym]
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.to_sql(options)
|
35
|
+
options = {
|
36
|
+
:units => :miles,
|
37
|
+
:latitude_column => 'latitude',
|
38
|
+
:longitude_column => 'longitude'
|
39
|
+
}.merge(options)
|
40
|
+
%{(ACOS(
|
41
|
+
SIN(RADIANS(#{options[:latitude]})) *
|
42
|
+
SIN(RADIANS(#{options[:latitude_column]})) +
|
43
|
+
COS(RADIANS(#{options[:latitude]})) *
|
44
|
+
COS(RADIANS(#{options[:latitude_column]})) *
|
45
|
+
COS(RADIANS(#{options[:longitude_column]}) - RADIANS(#{options[:longitude]}))
|
46
|
+
) * #{Graticule::Distance::EARTH_RADIUS[options[:units].to_sym]})
|
47
|
+
}.gsub("\n", '').squeeze(" ")
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Graticule
|
2
|
+
module Distance
|
3
|
+
|
4
|
+
#
|
5
|
+
# The Vincenty Formula uses an ellipsoidal model of the earth, which is very accurate.
|
6
|
+
#
|
7
|
+
# Thanks to Chris Veness (http://www.movable-type.co.uk/scripts/LatLongVincenty.html)
|
8
|
+
# for distance formulas.
|
9
|
+
#
|
10
|
+
class Vincenty < DistanceFormula
|
11
|
+
|
12
|
+
# Calculate the distance between two Locations using the Vincenty formula
|
13
|
+
#
|
14
|
+
# Graticule::Distance::Vincenty.distance(
|
15
|
+
# Graticule::Location.new(:latitude => 42.7654, :longitude => -86.1085),
|
16
|
+
# Graticule::Location.new(:latitude => 41.849838, :longitude => -87.648193)
|
17
|
+
# )
|
18
|
+
# #=> 101.070118000159
|
19
|
+
#
|
20
|
+
def self.distance(from, to, units = :miles)
|
21
|
+
from_longitude = from.longitude.to_radians
|
22
|
+
from_latitude = from.latitude.to_radians
|
23
|
+
to_longitude = to.longitude.to_radians
|
24
|
+
to_latitude = to.latitude.to_radians
|
25
|
+
|
26
|
+
earth_major_axis_radius = EARTH_MAJOR_AXIS_RADIUS[units.to_sym]
|
27
|
+
earth_minor_axis_radius = EARTH_MINOR_AXIS_RADIUS[units.to_sym]
|
28
|
+
|
29
|
+
f = (earth_major_axis_radius - earth_minor_axis_radius) / earth_major_axis_radius
|
30
|
+
|
31
|
+
l = to_longitude - from_longitude
|
32
|
+
u1 = atan((1-f) * tan(from_latitude))
|
33
|
+
u2 = atan((1-f) * tan(to_latitude))
|
34
|
+
sin_u1 = sin(u1)
|
35
|
+
cos_u1 = cos(u1)
|
36
|
+
sin_u2 = sin(u2)
|
37
|
+
cos_u2 = cos(u2)
|
38
|
+
|
39
|
+
lambda = l
|
40
|
+
lambda_p = 2 * PI
|
41
|
+
iteration_limit = 20
|
42
|
+
while (lambda-lambda_p).abs > 1e-12 && (iteration_limit -= 1) > 0
|
43
|
+
sin_lambda = sin(lambda)
|
44
|
+
cos_lambda = cos(lambda)
|
45
|
+
sin_sigma = sqrt((cos_u2*sin_lambda) * (cos_u2*sin_lambda) +
|
46
|
+
(cos_u1*sin_u2-sin_u1*cos_u2*cos_lambda) * (cos_u1*sin_u2-sin_u1*cos_u2*cos_lambda))
|
47
|
+
return 0 if sin_sigma == 0 # co-incident points
|
48
|
+
cos_sigma = sin_u1*sin_u2 + cos_u1*cos_u2*cos_lambda
|
49
|
+
sigma = atan2(sin_sigma, cos_sigma)
|
50
|
+
sin_alpha = cos_u1 * cos_u2 * sin_lambda / sin_sigma
|
51
|
+
cosSqAlpha = 1 - sin_alpha*sin_alpha
|
52
|
+
cos2SigmaM = cos_sigma - 2*sin_u1*sin_u2/cosSqAlpha
|
53
|
+
|
54
|
+
cos2SigmaM = 0 if cos2SigmaM.nan? # equatorial line: cosSqAlpha=0 (§6)
|
55
|
+
|
56
|
+
c = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha))
|
57
|
+
lambda_p = lambda
|
58
|
+
lambda = l + (1-c) * f * sin_alpha *
|
59
|
+
(sigma + c*sin_sigma*(cos2SigmaM+c*cos_sigma*(-1+2*cos2SigmaM*cos2SigmaM)))
|
60
|
+
end
|
61
|
+
# formula failed to converge (happens on antipodal points)
|
62
|
+
# We'll call Haversine formula instead.
|
63
|
+
return Haversine.distance(from, to, units) if iteration_limit == 0
|
64
|
+
|
65
|
+
uSq = cosSqAlpha * (earth_major_axis_radius**2 - earth_minor_axis_radius**2) / (earth_minor_axis_radius**2)
|
66
|
+
a = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)))
|
67
|
+
b = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)))
|
68
|
+
delta_sigma = b*sin_sigma*(cos2SigmaM+b/4*(cos_sigma*(-1+2*cos2SigmaM*cos2SigmaM)-
|
69
|
+
b/6*cos2SigmaM*(-3+4*sin_sigma*sin_sigma)*(-3+4*cos2SigmaM*cos2SigmaM)))
|
70
|
+
|
71
|
+
earth_minor_axis_radius * a * (sigma-delta_sigma)
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
module Graticule
|
3
|
+
|
4
|
+
# Get a geocoder for the given service
|
5
|
+
#
|
6
|
+
# geocoder = Graticule.service(:google).new "api_key"
|
7
|
+
#
|
8
|
+
# See the documentation for your specific geocoder for more information
|
9
|
+
#
|
10
|
+
def self.service(name)
|
11
|
+
Geocoder.const_get name.to_s.camelize
|
12
|
+
end
|
13
|
+
|
14
|
+
# Base error class
|
15
|
+
class Error < RuntimeError; end
|
16
|
+
class CredentialsError < Error; end
|
17
|
+
|
18
|
+
# Raised when you try to locate an invalid address.
|
19
|
+
class AddressError < Error; end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
|
3
|
+
module Graticule #:nodoc:
|
4
|
+
module Geocoder
|
5
|
+
|
6
|
+
# Abstract class for implementing geocoders.
|
7
|
+
#
|
8
|
+
# === Example
|
9
|
+
#
|
10
|
+
# The following methods must be implemented in sublcasses:
|
11
|
+
#
|
12
|
+
# * +initialize+:: Sets @url to the service enpoint.
|
13
|
+
# * +check_error+:: Checks for errors in the server response.
|
14
|
+
# * +parse_response+:: Extracts information from the server response.
|
15
|
+
#
|
16
|
+
# Optionally, you can also override
|
17
|
+
#
|
18
|
+
# * +prepare_response+:: Convert the string response into a different format
|
19
|
+
# that gets passed on to +check_error+ and +parse_response+.
|
20
|
+
#
|
21
|
+
# If you have extra URL paramaters (application id, output type) or need to
|
22
|
+
# perform URL customization, override +make_url+.
|
23
|
+
#
|
24
|
+
# class FakeGeocoder < Base
|
25
|
+
#
|
26
|
+
# def initialize(appid)
|
27
|
+
# @appid = appid
|
28
|
+
# @url = URI.parse 'http://example.com/test'
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# def locate(query)
|
32
|
+
# get :q => query
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# private
|
36
|
+
#
|
37
|
+
# def check_error(xml)
|
38
|
+
# raise Error, xml.elements['error'].text if xml.elements['error']
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# def make_url(params)
|
42
|
+
# params[:appid] = @appid
|
43
|
+
# super params
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# def parse_response(response)
|
47
|
+
# # return Location
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
class Base
|
53
|
+
USER_AGENT = "Mozilla/5.0 (compatible; Graticule/#{Graticule::Version::STRING}; http://graticule.rubyforge.org)"
|
54
|
+
|
55
|
+
def initialize
|
56
|
+
raise NotImplementedError
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def location_from_params(params)
|
62
|
+
case params
|
63
|
+
when Location then params
|
64
|
+
when Hash then Location.new params
|
65
|
+
else
|
66
|
+
raise ArgumentError, "Expected a Graticule::Location or a hash with :street, :locality, :region, :postal_code, and :country attributes"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Check for errors in +response+ and raise appropriate error, if any.
|
71
|
+
# Must return if no error could be found.
|
72
|
+
def check_error(response)
|
73
|
+
raise NotImplementedError
|
74
|
+
end
|
75
|
+
|
76
|
+
# Performs a GET request with +params+. Calls +check_error+ and returns
|
77
|
+
# the result of +parse_response+.
|
78
|
+
def get(params = {})
|
79
|
+
response = prepare_response(make_url(params).open('User-Agent' => USER_AGENT).read)
|
80
|
+
check_error(response)
|
81
|
+
return parse_response(response)
|
82
|
+
rescue OpenURI::HTTPError => e
|
83
|
+
check_error(prepare_response(e.io.read))
|
84
|
+
raise
|
85
|
+
end
|
86
|
+
|
87
|
+
# Creates a URI from the Hash +params+. Override this then call super if
|
88
|
+
# you need to add extra params like an application id or output type.
|
89
|
+
def make_url(params)
|
90
|
+
escaped_params = params.sort_by { |k,v| k.to_s }.map do |k,v|
|
91
|
+
"#{URI.escape k.to_s}=#{URI.escape v.to_s}"
|
92
|
+
end
|
93
|
+
|
94
|
+
url = @url.dup
|
95
|
+
url.query = escaped_params.join '&'
|
96
|
+
return url
|
97
|
+
end
|
98
|
+
|
99
|
+
# Override to convert the response to something besides a String, which
|
100
|
+
# will get passed to +check_error+ and +parse_response+.
|
101
|
+
def prepare_response(response)
|
102
|
+
response
|
103
|
+
end
|
104
|
+
|
105
|
+
# Must parse results from +response+ into a Location.
|
106
|
+
def parse_response(response)
|
107
|
+
raise NotImplementedError
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Graticule #:nodoc:
|
2
|
+
module Geocoder #:nodoc:
|
3
|
+
|
4
|
+
# Bogus geocoder that can be used for test purposes
|
5
|
+
class Bogus
|
6
|
+
|
7
|
+
# returns a new location with the address set to the original query string
|
8
|
+
def locate(address)
|
9
|
+
Location.new :street => address
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|