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 CHANGED
@@ -1,5 +1,10 @@
1
1
 
2
- 0.1.1
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
@@ -3,8 +3,10 @@ LICENSE.txt
3
3
  Manifest.txt
4
4
  README.txt
5
5
  Rakefile
6
+ bin/geocode
6
7
  init.rb
7
8
  lib/graticule.rb
9
+ lib/graticule/cli.rb
8
10
  lib/graticule/distance.rb
9
11
  lib/graticule/distance/haversine.rb
10
12
  lib/graticule/distance/spherical.rb
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
+
@@ -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 * Math::PI / 180)
19
+ (deg * PI / 180)
16
20
  end
17
21
 
22
+ # Convert from radians to degrees
18
23
  def self.rad2deg(rad)
19
- (rad * 180 / Math::PI)
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
- # Thanks to Chris Veness for distance formulas.
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
- # Distance Measured usign the Haversine Formula
10
- # Works better at small distances than the Spherical Law of Cosines
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 = Math.sin(latitude_delta/2)**2 +
30
- Math.cos(from_latitude) *
31
- Math.cos(to_latitude) *
32
- Math.sin(longitude_delta/2)**2
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 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a))
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
- # Distance Measured usign the Spherical Law of Cosines
6
- # Simplist though least accurate (earth isn't a perfect sphere)
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
- # Thanks to Chris Veness for distance formulas.
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
- # Distance Measured usign the Vincenty Formula
9
- # Very accurate, using an accurate ellipsoidal model of the earth
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
- l = to_longitude - from_longitude
55
- u1 = Math.atan((1-f) * Math.tan(from_latitude))
56
- u2 = Math.atan((1-f) * Math.tan(to_latitude))
57
- sinU1 = Math.sin(u1)
58
- cosU1 = Math.cos(u1)
59
- sinU2 = Math.sin(u2)
60
- cosU2 = Math.cos(u2)
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
- lambda = l
63
- lambdaP = 2*Math::PI
64
- iterLimit = 20;
65
- while (lambda-lambdaP).abs > 1e-12 && --iterLimit>0
66
- sinLambda = Math.sin(lambda)
67
- cosLambda = Math.cos(lambda)
68
- sinSigma = Math.sqrt((cosU2*sinLambda) * (cosU2*sinLambda) +
69
- (cosU1*sinU2-sinU1*cosU2*cosLambda) * (cosU1*sinU2-sinU1*cosU2*cosLambda))
70
- return 0 if sinSigma==0 # co-incident points
71
- cosSigma = sinU1*sinU2 + cosU1*cosU2*cosLambda
72
- sigma = Math.atan2(sinSigma, cosSigma)
73
- sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma
74
- cosSqAlpha = 1 - sinAlpha*sinAlpha
75
- cos2SigmaM = cosSigma - 2*sinU1*sinU2/cosSqAlpha
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
- cos2SigmaM = 0 if cos2SigmaM.nan? # equatorial line: cosSqAlpha=0 (§6)
54
+ cos2SigmaM = 0 if cos2SigmaM.nan? # equatorial line: cosSqAlpha=0 (§6)
78
55
 
79
- c = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha))
80
- lambdaP = lambda
81
- lambda = l + (1-c) * f * sinAlpha *
82
- (sigma + c*sinSigma*(cos2SigmaM+c*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)))
83
- end
84
- return NaN if (iterLimit==0) # formula failed to converge
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
- uSq = cosSqAlpha * (earth_major_axis_radius**2 - earth_minor_axis_radius**2) / (earth_minor_axis_radius**2);
87
- bigA = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
88
- bigB = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));
89
- deltaSigma = bigB*sinSigma*(cos2SigmaM+bigB/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
90
- bigB/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
91
- s = earth_minor_axis_radius*bigA*(sigma-deltaSigma);
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
@@ -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
@@ -1,12 +1,11 @@
1
-
2
1
  module Graticule #:nodoc:
3
2
 
4
3
  # Bogus geocoder that can be used for test purposes
5
4
  class BogusGeocoder < Geocoder
6
5
 
7
6
  def locate(address)
8
- Location.new
7
+ Location.new :street => address
9
8
  end
10
9
 
11
10
  end
12
- end
11
+ 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
 
@@ -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
@@ -1,3 +1,9 @@
1
- module Graticule
2
- Version = '0.1.1'
1
+ module Graticule #:nodoc:
2
+ module Version #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ TINY = 2
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
3
9
  end
@@ -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.1
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.1
7
- date: 2006-12-16 00:00:00 -05:00
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.6
112
+ version: 1.1.7
102
113
  version: