graticule 0.1.1 → 0.1.2
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/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:
|