graticule 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.txt +6 -1
- data/Manifest.txt +2 -0
- data/README.txt +15 -0
- data/Rakefile +2 -1
- data/bin/geocode +19 -0
- data/lib/graticule.rb +5 -1
- data/lib/graticule/cli.rb +60 -0
- data/lib/graticule/distance.rb +8 -3
- data/lib/graticule/distance/haversine.rb +17 -42
- data/lib/graticule/distance/spherical.rb +25 -3
- data/lib/graticule/distance/vincenty.rb +48 -71
- data/lib/graticule/geocoder.rb +6 -0
- data/lib/graticule/geocoders/bogus.rb +2 -3
- data/lib/graticule/geocoders/geocoder_us.rb +2 -2
- data/lib/graticule/geocoders/google.rb +3 -3
- data/lib/graticule/geocoders/yahoo.rb +2 -2
- data/lib/graticule/location.rb +19 -1
- data/lib/graticule/version.rb +8 -2
- data/test/unit/graticule/distance_test.rb +29 -1
- data/test/unit/graticule/location_test.rb +13 -0
- metadata +16 -5
data/CHANGELOG.txt
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
|
2
|
-
|
2
|
+
|
3
|
+
0.1.2
|
4
|
+
* added "geocode" executable. See "geocode --help" for more information
|
5
|
+
* declared dependency on ActiveSupport
|
6
|
+
|
7
|
+
0.1.1 (2006-12-16)
|
3
8
|
* fixed bug in Yahoo that raised error when street address not returned
|
4
9
|
* migrated to Hoe (http://seattlerb.rubyforge.org/hoe/)
|
5
10
|
* added Haversine, Spherical and Vincenty distance calculations
|
data/Manifest.txt
CHANGED
data/README.txt
CHANGED
@@ -8,3 +8,18 @@ Graticule is a geocoding API for looking up address coordinates. It supports su
|
|
8
8
|
require 'graticule'
|
9
9
|
geocoder = Graticule.service(:google).new "api_key"
|
10
10
|
location = geocoder.locate "61 East 9th Street, Holland, MI"
|
11
|
+
|
12
|
+
= Distance Calculation
|
13
|
+
|
14
|
+
Graticule includes 3 different distance formulas, Spherical (simplest but least accurate), Vincenty (most accurate and most complicated), and Haversine (somewhere inbetween).
|
15
|
+
|
16
|
+
geocoder.locate("Holland, MI").distance_to(geocoder.locate("Chicago, IL"))
|
17
|
+
#=> 101.997458788177
|
18
|
+
|
19
|
+
= Command Line
|
20
|
+
|
21
|
+
Graticule includes a command line interface (CLI).
|
22
|
+
|
23
|
+
$ geocode -s yahoo -a yahookey Washington, DC
|
24
|
+
Washington, DC US
|
25
|
+
latitude: 38.895222, longitude: -77.036758
|
data/Rakefile
CHANGED
@@ -2,7 +2,7 @@ require 'rubygems'
|
|
2
2
|
require 'hoe'
|
3
3
|
require File.join(File.dirname(__FILE__), 'lib', 'graticule', 'version.rb')
|
4
4
|
|
5
|
-
Hoe.new("graticule", Graticule::Version) do |p|
|
5
|
+
Hoe.new("graticule", Graticule::Version::STRING) do |p|
|
6
6
|
p.rubyforge_name = "graticule"
|
7
7
|
p.author = 'Brandon Keepers'
|
8
8
|
p.email = 'brandon@opensoul.org'
|
@@ -13,4 +13,5 @@ Hoe.new("graticule", Graticule::Version) do |p|
|
|
13
13
|
p.need_zip = true
|
14
14
|
p.test_globs = ['test/**/*_test.rb']
|
15
15
|
p.changes = p.paragraphs_of('CHANGELOG.txt', 0..1).join("\n\n")
|
16
|
+
p.extra_deps << ['activesupport']
|
16
17
|
end
|
data/bin/geocode
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by RubyGems.
|
4
|
+
#
|
5
|
+
# The application 'geocode' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'rubygems'
|
10
|
+
version = "> 0"
|
11
|
+
if ARGV.size > 0 && ARGV[0][0]==95 && ARGV[0][-1]==95
|
12
|
+
if Gem::Version.correct?(ARGV[0][1..-2])
|
13
|
+
version = ARGV[0][1..-2]
|
14
|
+
ARGV.shift
|
15
|
+
end
|
16
|
+
end
|
17
|
+
require_gem 'graticule', version
|
18
|
+
|
19
|
+
Graticule::Cli.start
|
data/lib/graticule.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
$:.unshift(File.dirname(__FILE__))
|
1
|
+
$:.unshift(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
require 'active_support'
|
2
4
|
|
3
5
|
require 'graticule/location'
|
4
6
|
require 'graticule/geocoder'
|
@@ -12,3 +14,5 @@ require 'graticule/distance'
|
|
12
14
|
require 'graticule/distance/haversine'
|
13
15
|
require 'graticule/distance/spherical'
|
14
16
|
require 'graticule/distance/vincenty'
|
17
|
+
|
18
|
+
require 'graticule/cli'
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module Graticule
|
4
|
+
|
5
|
+
# A command line interface for geocoding. From the command line, run:
|
6
|
+
#
|
7
|
+
# geocode 49423
|
8
|
+
#
|
9
|
+
# Outputs:
|
10
|
+
#
|
11
|
+
# # Holland, MI 49423 US
|
12
|
+
# # latitude: 42.7654, longitude: -86.1085
|
13
|
+
#
|
14
|
+
# == Usage: geocode [options] location
|
15
|
+
#
|
16
|
+
# Options:
|
17
|
+
# -s, --service service Geocoding service
|
18
|
+
# -a, --apikey apikey API key for the selected service
|
19
|
+
# -h, --help Help
|
20
|
+
class Cli
|
21
|
+
|
22
|
+
def self.start
|
23
|
+
options = { :service => :yahoo, :api_key => 'YahooDemo' }
|
24
|
+
|
25
|
+
option_parser = OptionParser.new do |opts|
|
26
|
+
opts.banner = "Usage: geocode [options] location"
|
27
|
+
opts.separator ""
|
28
|
+
opts.separator "Options: "
|
29
|
+
|
30
|
+
opts.on("-s service", %w(yahoo google geocoder_us metacarta), "--service service", "Geocoding service") do |service|
|
31
|
+
options[:service] = service
|
32
|
+
end
|
33
|
+
|
34
|
+
opts.on("-a apikey", "--apikey apikey", "API key for the selected service")
|
35
|
+
|
36
|
+
opts.on_tail("-h", "--help", "Help") do
|
37
|
+
puts opts
|
38
|
+
exit
|
39
|
+
end
|
40
|
+
end.parse!
|
41
|
+
|
42
|
+
result = Graticule.service(options[:service]).new(options[:api_key]).locate(options[:location])
|
43
|
+
location = (result.is_a?(Array) ? result.first : result)
|
44
|
+
if location
|
45
|
+
puts location.to_s(true)
|
46
|
+
else
|
47
|
+
puts "Location not found"
|
48
|
+
end
|
49
|
+
rescue OptionParser::InvalidArgument => error
|
50
|
+
$stderr.puts error.message
|
51
|
+
rescue OptionParser::InvalidOption => error
|
52
|
+
$stderr.puts error.message
|
53
|
+
rescue Graticule::CredentialsError
|
54
|
+
$stderr.puts "Invalid API key. Pass your #{options[:service]} API key using the -a option. "
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
data/lib/graticule/distance.rb
CHANGED
@@ -1,22 +1,27 @@
|
|
1
1
|
module Graticule
|
2
2
|
module Distance
|
3
|
+
|
3
4
|
EARTH_RADIUS = { :kilometers => 6378.135, :miles => 3963.1676 }
|
4
5
|
# WGS-84 numbers
|
5
6
|
EARTH_MAJOR_AXIS_RADIUS = { :kilometers => 6378.137, :miles => 3963.19059 }
|
6
7
|
EARTH_MINOR_AXIS_RADIUS = { :kilometers => 6356.7523142, :miles => 3949.90276 }
|
7
8
|
|
8
9
|
class DistanceFormula
|
9
|
-
|
10
|
+
include Math
|
11
|
+
extend Math
|
12
|
+
|
10
13
|
def initialize
|
11
14
|
raise NotImplementedError
|
12
15
|
end
|
13
16
|
|
17
|
+
# Convert from degrees to radians
|
14
18
|
def self.deg2rad(deg)
|
15
|
-
(deg *
|
19
|
+
(deg * PI / 180)
|
16
20
|
end
|
17
21
|
|
22
|
+
# Convert from radians to degrees
|
18
23
|
def self.rad2deg(rad)
|
19
|
-
(rad * 180 /
|
24
|
+
(rad * 180 / PI)
|
20
25
|
end
|
21
26
|
|
22
27
|
end
|
@@ -1,22 +1,21 @@
|
|
1
|
-
|
2
1
|
module Graticule
|
3
2
|
module Distance
|
4
|
-
|
5
3
|
#
|
6
|
-
#
|
7
|
-
# * http://www.movable-type.co.uk/scripts/LatLong.html
|
4
|
+
# The Haversine Formula works better at small distances than the Spherical Law of Cosines
|
8
5
|
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
# R = earth’s radius (mean radius = 6,371km)
|
12
|
-
# Δlat = lat2− lat1
|
13
|
-
# Δlong = long2− long1
|
14
|
-
# a = sin²(Δlat/2) + cos(lat1).cos(lat2).sin²(Δlong/2)
|
15
|
-
# c = 2.atan2(√a, √(1−a))
|
16
|
-
# d = R.c
|
6
|
+
# Thanks to Chris Veness (http://www.movable-type.co.uk/scripts/LatLong.html)
|
7
|
+
# for distance formulas.
|
17
8
|
#
|
18
9
|
class Haversine < DistanceFormula
|
19
|
-
|
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
|
+
#
|
20
19
|
def self.distance(from, to, units = :miles)
|
21
20
|
from_longitude = deg2rad(from.longitude)
|
22
21
|
from_latitude = deg2rad(from.latitude)
|
@@ -26,40 +25,16 @@ module Graticule
|
|
26
25
|
latitude_delta = to_latitude - from_latitude
|
27
26
|
longitude_delta = to_longitude - from_longitude
|
28
27
|
|
29
|
-
a =
|
30
|
-
|
31
|
-
|
32
|
-
|
28
|
+
a = sin(latitude_delta/2)**2 +
|
29
|
+
cos(from_latitude) *
|
30
|
+
cos(to_latitude) *
|
31
|
+
sin(longitude_delta/2)**2
|
33
32
|
|
34
|
-
c = 2 *
|
33
|
+
c = 2 * atan2(sqrt(a), sqrt(1-a))
|
35
34
|
|
36
35
|
d = EARTH_RADIUS[units.to_sym] * c
|
37
36
|
end
|
38
37
|
|
39
|
-
# # What formula is this?
|
40
|
-
# def self.distance(from, to, units = :miles)
|
41
|
-
# from_longitude = deg2rad(from.longitude)
|
42
|
-
# from_latitude = deg2rad(from.latitude)
|
43
|
-
# to_longitude = deg2rad(to.longitude)
|
44
|
-
# to_latitude = deg2rad(to.latitude)
|
45
|
-
#
|
46
|
-
# Math.acos(
|
47
|
-
# Math.cos(from_longitude) *
|
48
|
-
# Math.cos(to_longitude) *
|
49
|
-
# Math.cos(from_latitude) *
|
50
|
-
# Math.cos(to_latitude) +
|
51
|
-
#
|
52
|
-
# Math.cos(from_latitude) *
|
53
|
-
# Math.sin(from_longitude) *
|
54
|
-
# Math.cos(to_latitude) *
|
55
|
-
# Math.sin(to_longitude) +
|
56
|
-
#
|
57
|
-
# Math.sin(from_latitude) *
|
58
|
-
# Math.sin(to_latitude)
|
59
|
-
# ) * EARTH_RADIUS[units.to_sym]
|
60
|
-
# end
|
61
|
-
|
62
|
-
|
63
38
|
end
|
64
39
|
end
|
65
40
|
end
|
@@ -2,12 +2,19 @@ module Graticule
|
|
2
2
|
module Distance
|
3
3
|
|
4
4
|
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
# d = acos(sin(lat1).sin(lat2)+cos(lat1).cos(lat2).cos(long2−long1)).R
|
5
|
+
# The Spherical Law of Cosines is the simplist though least accurate distance
|
6
|
+
# formula (earth isn't a perfect sphere).
|
8
7
|
#
|
9
8
|
class Spherical < DistanceFormula
|
10
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
|
+
#
|
11
18
|
def self.distance(from, to, units = :miles)
|
12
19
|
from_longitude = deg2rad(from.longitude)
|
13
20
|
from_latitude = deg2rad(from.latitude)
|
@@ -24,6 +31,21 @@ module Graticule
|
|
24
31
|
) * EARTH_RADIUS[units.to_sym]
|
25
32
|
end
|
26
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
|
27
49
|
|
28
50
|
end
|
29
51
|
end
|
@@ -2,44 +2,21 @@ module Graticule
|
|
2
2
|
module Distance
|
3
3
|
|
4
4
|
#
|
5
|
-
#
|
6
|
-
# * http://www.movable-type.co.uk/scripts/LatLongVincenty.html
|
5
|
+
# The Vincenty Formula uses an ellipsoidal model of the earth, which is very accurate.
|
7
6
|
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# a, b = major & minor semiaxes of the ellipsoid
|
11
|
-
# f = flattening (a−b)/a
|
12
|
-
# φ1, φ2 = geodetic latitude
|
13
|
-
# L = difference in longitude
|
14
|
-
# U1 = atan((1−f).tanφ1) (U is ‘reduced latitude’)
|
15
|
-
# U2 = atan((1−f).tanφ2)
|
16
|
-
# λ = L, λ′ = 2π
|
17
|
-
# while abs(λ−λ′) > 10-12 { (i.e. 0.06mm)
|
18
|
-
# sinσ = √[ (cosU2.sinλ)² + (cosU1.sinU2 − sinU1.cosU2.cosλ)² ] (14)
|
19
|
-
# cosσ = sinU1.sinU2 + cosU1.cosU2.cosλ (15)
|
20
|
-
# σ = atan2(sinσ, cosσ) (16)
|
21
|
-
# sinα = cosU1.cosU2.sinλ / sinσ (17)
|
22
|
-
# cos²α = 1 − sin²α (trig identity; §6)
|
23
|
-
# cos2σm = cosσ − 2.sinU1.sinU2/cos²α (18)
|
24
|
-
# C = f/16.cos²α.[4+f.(4−3.cos²α)] (10)
|
25
|
-
# λ′ = λ
|
26
|
-
# λ = L + (1−C).f.sinα.{σ+C.sinσ.[cos2σm+C.cosσ.(−1+2.cos²2σm)]} (11)
|
27
|
-
# }
|
28
|
-
# u² = cos²α.(a²−b²)/b²
|
29
|
-
# A = 1+u²/16384.{4096+u².[−768+u².(320−175.u²)]} (3)
|
30
|
-
# B = u²/1024.{256+u².[−128+u².(74−47.u²)]} (4)
|
31
|
-
# Δσ = B.sinσ.{cos2σm+B/4.[cosσ.(−1+2.cos²2σm) − B/6.cos2σm.(−3+4.sin²σ).(−3+4.cos²2σm)]} (6)
|
32
|
-
# s = b.A.(σ−Δσ) (19)
|
33
|
-
# α1 = atan2(cosU2.sinλ, cosU1.sinU2 − sinU1.cosU2.cosλ) (20)
|
34
|
-
# α2 = atan2(cosU1.sinλ, −sinU1.cosU2 + cosU1.sinU2.cosλ) (21)
|
35
|
-
# Where:
|
36
|
-
#
|
37
|
-
# s is the distance (in the same units as a & b)
|
38
|
-
# α1 is the initial bearing, or forward azimuth
|
39
|
-
# α2 is the final bearing (in direction p1→p2)
|
7
|
+
# Thanks to Chris Veness (http://www.movable-type.co.uk/scripts/LatLongVincenty.html)
|
8
|
+
# for distance formulas.
|
40
9
|
#
|
41
10
|
class Vincenty < DistanceFormula
|
42
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
|
+
#
|
43
20
|
def self.distance(from, to, units = :miles)
|
44
21
|
from_longitude = deg2rad(from.longitude)
|
45
22
|
from_latitude = deg2rad(from.latitude)
|
@@ -51,47 +28,47 @@ module Graticule
|
|
51
28
|
|
52
29
|
f = (earth_major_axis_radius - earth_minor_axis_radius) / earth_major_axis_radius
|
53
30
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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)
|
61
38
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
76
53
|
|
77
|
-
|
54
|
+
cos2SigmaM = 0 if cos2SigmaM.nan? # equatorial line: cosSqAlpha=0 (§6)
|
78
55
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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
|
85
64
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
#s = s.toFixed(3) # round to 1mm precision
|
94
|
-
return s
|
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)
|
95
72
|
end
|
96
73
|
|
97
74
|
end
|
data/lib/graticule/geocoder.rb
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
|
2
2
|
module Graticule
|
3
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
|
+
#
|
4
10
|
def self.service(name)
|
5
11
|
self.const_get "#{name}_geocoder".camelize
|
6
12
|
end
|
@@ -27,7 +27,7 @@ module Graticule #:nodoc:
|
|
27
27
|
get :address => address
|
28
28
|
end
|
29
29
|
|
30
|
-
def parse_response(xml)
|
30
|
+
def parse_response(xml) #:nodoc:
|
31
31
|
location = Location.new
|
32
32
|
location.street = xml.elements['rdf:RDF/geo:Point/dc:description'].text
|
33
33
|
|
@@ -37,7 +37,7 @@ module Graticule #:nodoc:
|
|
37
37
|
return location
|
38
38
|
end
|
39
39
|
|
40
|
-
def check_error(xml)
|
40
|
+
def check_error(xml) #:nodoc:
|
41
41
|
raise AddressError, xml.text if xml.text == 'couldn\'t find this address! sorry'
|
42
42
|
end
|
43
43
|
|
@@ -44,7 +44,7 @@ module Graticule
|
|
44
44
|
end
|
45
45
|
|
46
46
|
# Extracts a Location from +xml+.
|
47
|
-
def parse_response(xml)
|
47
|
+
def parse_response(xml) #:nodoc:
|
48
48
|
longitude, latitude, = xml.elements['/kml/Response/Placemark/Point/coordinates'].text.split(',').map { |v| v.to_f }
|
49
49
|
Location.new \
|
50
50
|
:street => text(xml.elements['/kml/Response/Placemark/AddressDetails/Country/AdministrativeArea/SubAdministrativeArea/Locality/Thoroughfare/ThoroughfareName']),
|
@@ -58,7 +58,7 @@ module Graticule
|
|
58
58
|
end
|
59
59
|
|
60
60
|
# Extracts and raises an error from +xml+, if any.
|
61
|
-
def check_error(xml)
|
61
|
+
def check_error(xml) #:nodoc:
|
62
62
|
status ||= xml.elements['/kml/Response/Status/code'].text.to_i
|
63
63
|
case status
|
64
64
|
when 200 then # ignore, ok
|
@@ -81,7 +81,7 @@ module Graticule
|
|
81
81
|
|
82
82
|
# Creates a URL from the Hash +params+. Automatically adds the key and
|
83
83
|
# sets the output type to 'xml'.
|
84
|
-
def make_url(params)
|
84
|
+
def make_url(params) #:nodoc:
|
85
85
|
params[:key] = @key
|
86
86
|
params[:output] = 'xml'
|
87
87
|
|
@@ -81,14 +81,14 @@ module Graticule #:nodoc:
|
|
81
81
|
end
|
82
82
|
|
83
83
|
# Extracts and raises an error from +xml+, if any.
|
84
|
-
def check_error(xml)
|
84
|
+
def check_error(xml) #:nodoc:
|
85
85
|
err = xml.elements['Error']
|
86
86
|
raise Error, err.elements['Message'].text if err
|
87
87
|
end
|
88
88
|
|
89
89
|
# Creates a URL from the Hash +params+. Automatically adds the appid and
|
90
90
|
# sets the output type to 'xml'.
|
91
|
-
def make_url(params)
|
91
|
+
def make_url(params) #:nodoc:
|
92
92
|
params[:appid] = @appid
|
93
93
|
params[:output] = 'xml'
|
94
94
|
|
data/lib/graticule/location.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
|
2
1
|
module Graticule
|
2
|
+
|
3
|
+
# A geographic location
|
3
4
|
class Location
|
4
5
|
attr_accessor :latitude, :longitude, :street, :city, :state, :zip, :country, :precision, :warning
|
5
6
|
|
@@ -20,9 +21,26 @@ module Graticule
|
|
20
21
|
end
|
21
22
|
end
|
22
23
|
|
24
|
+
# Calculate the distance to another location. See the various Distance formulas
|
25
|
+
# for more information
|
23
26
|
def distance_to(destination, units = :miles, formula = :haversine)
|
24
27
|
"Graticule::Distance::#{formula.to_s.titleize}".constantize.distance(self, destination)
|
25
28
|
end
|
26
29
|
|
30
|
+
# Where would I be if I dug through the center of the earth?
|
31
|
+
def antipode
|
32
|
+
new_longitude = longitude + (longitude >= 0 ? -180 : 180)
|
33
|
+
Location.new(:latitude => -latitude, :longitude => new_longitude)
|
34
|
+
end
|
35
|
+
alias_method :antipodal_location, :antipode
|
36
|
+
|
37
|
+
def to_s(coordinates = false)
|
38
|
+
result = ""
|
39
|
+
result << "#{street}\n" if street
|
40
|
+
result << [city, [state, zip, country].compact.join(" ")].compact.join(", ")
|
41
|
+
result << "\nlatitude: #{latitude}, longitude: #{longitude}" if coordinates && [latitude, longitude].any?
|
42
|
+
result
|
43
|
+
end
|
44
|
+
|
27
45
|
end
|
28
46
|
end
|
data/lib/graticule/version.rb
CHANGED
@@ -18,13 +18,41 @@ module Graticule
|
|
18
18
|
chicago = Location.new(:latitude => 41.85, :longitude => -87.65)
|
19
19
|
|
20
20
|
FORMULAS.each do |formula|
|
21
|
-
assert_in_delta formula.distance(washington_dc, chicago), formula.distance(chicago, washington_dc), 0.
|
21
|
+
assert_in_delta formula.distance(washington_dc, chicago), formula.distance(chicago, washington_dc), 0.00001
|
22
22
|
assert_in_delta 594.820, formula.distance(washington_dc, chicago), 1.0
|
23
23
|
assert_in_delta 594.820, formula.distance(washington_dc, chicago, :miles), 1.0
|
24
24
|
assert_in_delta 957.275, formula.distance(washington_dc, chicago, :kilometers), 1.0
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
+
def test_distance_between_antipodal_points
|
29
|
+
# The Vincenty formula will be indeterminant with antipodal points.
|
30
|
+
# See http://mathworld.wolfram.com/AntipodalPoints.html
|
31
|
+
washington_dc = Location.new(:latitude => 38.898748, :longitude => -77.037684)
|
32
|
+
chicago = Location.new(:latitude => 41.85, :longitude => -87.65)
|
33
|
+
|
34
|
+
# First, test the deltas.
|
35
|
+
FORMULAS.each do |formula|
|
36
|
+
assert_in_delta 12450.6582171051,
|
37
|
+
formula.distance(chicago, chicago.antipodal_location), 1.0
|
38
|
+
assert_in_delta 12450.6582171051,
|
39
|
+
formula.distance(washington_dc, washington_dc.antipodal_location), 1.0
|
40
|
+
assert_in_delta 12450.6582171051,
|
41
|
+
formula.distance(chicago, chicago.antipodal_location, :miles), 1.0
|
42
|
+
assert_in_delta 12450.6582171051,
|
43
|
+
formula.distance(washington_dc, washington_dc.antipodal_location, :miles), 1.0
|
44
|
+
assert_in_delta 20037.50205960391,
|
45
|
+
formula.distance(chicago, chicago.antipodal_location, :kilometers), 1.0
|
46
|
+
assert_in_delta 20037.5020596039,
|
47
|
+
formula.distance(washington_dc, washington_dc.antipodal_location, :kilometers), 1.0
|
48
|
+
end
|
49
|
+
|
50
|
+
# Next, test Vincenty. Vincenty will use haversine instead of returning NaN on antipodal points
|
51
|
+
assert_equal Haversine.distance(washington_dc, washington_dc.antipodal_location),
|
52
|
+
Vincenty.distance(washington_dc, washington_dc.antipodal_location)
|
53
|
+
assert_equal Haversine.distance(chicago, chicago.antipodal_location),
|
54
|
+
Vincenty.distance(chicago, chicago.antipodal_location)
|
55
|
+
end
|
28
56
|
end
|
29
57
|
end
|
30
58
|
end
|
@@ -34,5 +34,18 @@ module Graticule
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
+
def test_antipode
|
38
|
+
washington_dc = Location.new(:latitude => 38.898748, :longitude => -77.037684)
|
39
|
+
chicago = Location.new(:latitude => 41.85, :longitude => -87.65)
|
40
|
+
|
41
|
+
assert_equal [-38.898748, 102.962316], washington_dc.antipode.coordinates
|
42
|
+
assert_equal [-41.85, 92.35], chicago.antipode.coordinates
|
43
|
+
assert_equal [-41, -180], Graticule::Location.new(:latitude => 41, :longitude => 0).antipode.coordinates
|
44
|
+
assert_equal [-41, 179], Graticule::Location.new(:latitude => 41, :longitude => -1).antipode.coordinates
|
45
|
+
assert_equal [-41, -179], Graticule::Location.new(:latitude => 41, :longitude => 1).antipode.coordinates
|
46
|
+
|
47
|
+
assert_equal washington_dc.coordinates, washington_dc.antipode.antipode.coordinates
|
48
|
+
assert_equal chicago.coordinates, chicago.antipode.antipode.coordinates
|
49
|
+
end
|
37
50
|
end
|
38
51
|
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
|
|
3
3
|
specification_version: 1
|
4
4
|
name: graticule
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.1.
|
7
|
-
date:
|
6
|
+
version: 0.1.2
|
7
|
+
date: 2007-02-12 00:00:00 -05:00
|
8
8
|
summary: API for using all the popular geocoding services.
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -33,8 +33,10 @@ files:
|
|
33
33
|
- Manifest.txt
|
34
34
|
- README.txt
|
35
35
|
- Rakefile
|
36
|
+
- bin/geocode
|
36
37
|
- init.rb
|
37
38
|
- lib/graticule.rb
|
39
|
+
- lib/graticule/cli.rb
|
38
40
|
- lib/graticule/distance.rb
|
39
41
|
- lib/graticule/distance/haversine.rb
|
40
42
|
- lib/graticule/distance/spherical.rb
|
@@ -84,13 +86,22 @@ rdoc_options: []
|
|
84
86
|
|
85
87
|
extra_rdoc_files: []
|
86
88
|
|
87
|
-
executables:
|
88
|
-
|
89
|
+
executables:
|
90
|
+
- geocode
|
89
91
|
extensions: []
|
90
92
|
|
91
93
|
requirements: []
|
92
94
|
|
93
95
|
dependencies:
|
96
|
+
- !ruby/object:Gem::Dependency
|
97
|
+
name: activesupport
|
98
|
+
version_requirement:
|
99
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 0.0.0
|
104
|
+
version:
|
94
105
|
- !ruby/object:Gem::Dependency
|
95
106
|
name: hoe
|
96
107
|
version_requirement:
|
@@ -98,5 +109,5 @@ dependencies:
|
|
98
109
|
requirements:
|
99
110
|
- - ">="
|
100
111
|
- !ruby/object:Gem::Version
|
101
|
-
version: 1.1.
|
112
|
+
version: 1.1.7
|
102
113
|
version:
|