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 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: