pepe-graticule 0.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/CHANGELOG.txt +61 -0
- data/LICENSE.txt +30 -0
- data/Manifest.txt +84 -0
- data/README.txt +41 -0
- data/Rakefile +143 -0
- data/VERSION +1 -0
- data/bin/geocode +5 -0
- data/graticule.gemspec +146 -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 +100 -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 +82 -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 +36 -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/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 +112 -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 +79 -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
|