norman-graticule 0.2.7

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.
Files changed (71) hide show
  1. data/CHANGELOG.txt +46 -0
  2. data/LICENSE.txt +30 -0
  3. data/Manifest.txt +73 -0
  4. data/README.txt +29 -0
  5. data/Rakefile +86 -0
  6. data/bin/geocode +5 -0
  7. data/init.rb +2 -0
  8. data/lib/graticule/cli.rb +64 -0
  9. data/lib/graticule/distance/haversine.rb +40 -0
  10. data/lib/graticule/distance/spherical.rb +52 -0
  11. data/lib/graticule/distance/vincenty.rb +76 -0
  12. data/lib/graticule/distance.rb +29 -0
  13. data/lib/graticule/geocoder/base.rb +113 -0
  14. data/lib/graticule/geocoder/bogus.rb +15 -0
  15. data/lib/graticule/geocoder/geocoder_ca.rb +54 -0
  16. data/lib/graticule/geocoder/geocoder_us.rb +49 -0
  17. data/lib/graticule/geocoder/google.rb +120 -0
  18. data/lib/graticule/geocoder/host_ip.rb +41 -0
  19. data/lib/graticule/geocoder/local_search_maps.rb +45 -0
  20. data/lib/graticule/geocoder/map_quest.rb +109 -0
  21. data/lib/graticule/geocoder/meta_carta.rb +33 -0
  22. data/lib/graticule/geocoder/multi.rb +46 -0
  23. data/lib/graticule/geocoder/postcode_anywhere.rb +63 -0
  24. data/lib/graticule/geocoder/rest.rb +18 -0
  25. data/lib/graticule/geocoder/yahoo.rb +102 -0
  26. data/lib/graticule/geocoder.rb +21 -0
  27. data/lib/graticule/location.rb +57 -0
  28. data/lib/graticule/version.rb +9 -0
  29. data/lib/graticule.rb +24 -0
  30. data/test/config.yml.default +36 -0
  31. data/test/fixtures/responses/geocoder_us/success.xml +18 -0
  32. data/test/fixtures/responses/geocoder_us/unknown.xml +1 -0
  33. data/test/fixtures/responses/google/badkey.xml +1 -0
  34. data/test/fixtures/responses/google/limit.xml +10 -0
  35. data/test/fixtures/responses/google/missing_address.xml +1 -0
  36. data/test/fixtures/responses/google/only_coordinates.xml +1 -0
  37. data/test/fixtures/responses/google/partial.xml +1 -0
  38. data/test/fixtures/responses/google/server_error.xml +10 -0
  39. data/test/fixtures/responses/google/success.xml +1 -0
  40. data/test/fixtures/responses/google/unavailable.xml +1 -0
  41. data/test/fixtures/responses/google/unknown_address.xml +1 -0
  42. data/test/fixtures/responses/host_ip/private.txt +4 -0
  43. data/test/fixtures/responses/host_ip/success.txt +4 -0
  44. data/test/fixtures/responses/host_ip/unknown.txt +4 -0
  45. data/test/fixtures/responses/local_search_maps/empty.txt +1 -0
  46. data/test/fixtures/responses/local_search_maps/not_found.txt +1 -0
  47. data/test/fixtures/responses/local_search_maps/success.txt +1 -0
  48. data/test/fixtures/responses/meta_carta/bad_address.xml +17 -0
  49. data/test/fixtures/responses/meta_carta/multiple.xml +33 -0
  50. data/test/fixtures/responses/meta_carta/success.xml +31 -0
  51. data/test/fixtures/responses/postcode_anywhere/badkey.xml +9 -0
  52. data/test/fixtures/responses/postcode_anywhere/canada.xml +16 -0
  53. data/test/fixtures/responses/postcode_anywhere/empty.xml +16 -0
  54. data/test/fixtures/responses/postcode_anywhere/success.xml +16 -0
  55. data/test/fixtures/responses/postcode_anywhere/uk.xml +18 -0
  56. data/test/fixtures/responses/yahoo/success.xml +3 -0
  57. data/test/fixtures/responses/yahoo/unknown_address.xml +6 -0
  58. data/test/mocks/uri.rb +52 -0
  59. data/test/test_helper.rb +32 -0
  60. data/test/unit/graticule/distance_test.rb +58 -0
  61. data/test/unit/graticule/geocoder/geocoder_us_test.rb +43 -0
  62. data/test/unit/graticule/geocoder/google_test.rb +107 -0
  63. data/test/unit/graticule/geocoder/host_ip_test.rb +40 -0
  64. data/test/unit/graticule/geocoder/local_search_maps_test.rb +30 -0
  65. data/test/unit/graticule/geocoder/meta_carta_test.rb +44 -0
  66. data/test/unit/graticule/geocoder/multi_test.rb +43 -0
  67. data/test/unit/graticule/geocoder/postcode_anywhere_test.rb +50 -0
  68. data/test/unit/graticule/geocoder/yahoo_test.rb +58 -0
  69. data/test/unit/graticule/geocoder_test.rb +27 -0
  70. data/test/unit/graticule/location_test.rb +66 -0
  71. metadata +123 -0
data/CHANGELOG.txt ADDED
@@ -0,0 +1,46 @@
1
+ 0.2.7
2
+ * Added MapQuest geocoder [Andrew Selder]
3
+ * Fix google geocoder for responses that only return coordinates [Andrew Selder]
4
+
5
+ 0.2.5
6
+ * fixed address mapping for local search maps (again)
7
+
8
+ 0.2.4 (2007-05-15)
9
+ * fixed address mapping for local search maps (Tom Taylor)
10
+
11
+ 0.2.3 (2007-04-27)
12
+ * fixed Google for less precise queries
13
+ * added User-Agent to coerce Google into returning UTF-8 (Jonathan Tron)
14
+
15
+ 0.2.2 (2007-03-27)
16
+ * fixed LocalSearchMaps
17
+
18
+ 0.2.1 (2007-03-19)
19
+ * fixed error in command line interface
20
+
21
+ 0.2.0 (2007-03-17)
22
+ * changed city to locality, state to region, and zip to postal_code
23
+ * added support for PostcodeAnywhere
24
+ * added support for Local Search Maps (James Stewart)
25
+ * added IP-based geocoder
26
+ * moved geocoders to Graticule::Geocoder namespace
27
+ * fixed Google geocoder (again)
28
+ * made Yahoo geocoder consistent with others by returning 1 result
29
+ * geocoders can how take a Hash (:street, :locality, :region, :postal_code, :country)
30
+ or a Graticule::Location for the #locate call
31
+
32
+ 0.1.3 (2007-02-14)
33
+ * fixed Google geocoder
34
+ * fixed CLI
35
+
36
+ 0.1.2 (2007-02-12)
37
+ * added "geocode" executable. See "geocode --help" for more information
38
+ * declared dependency on ActiveSupport
39
+
40
+ 0.1.1 (2006-12-16)
41
+ * fixed bug in Yahoo that raised error when street address not returned
42
+ * migrated to Hoe (http://seattlerb.rubyforge.org/hoe/)
43
+ * added Haversine, Spherical and Vincenty distance calculations
44
+
45
+ 0.1 (2006-10-31)
46
+ * Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,30 @@
1
+ Copyright 2006 Brandon Keepers, Collective Idea. All rights reserved.
2
+
3
+ Original geocoding code:
4
+ Copyright 2006 Eric Hodel, The Robot Co-op. All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions
8
+ are met:
9
+
10
+ 1. Redistributions of source code must retain the above copyright
11
+ notice, this list of conditions and the following disclaimer.
12
+ 2. Redistributions in binary form must reproduce the above copyright
13
+ notice, this list of conditions and the following disclaimer in the
14
+ documentation and/or other materials provided with the distribution.
15
+ 3. Neither the names of the authors nor the names of their contributors
16
+ may be used to endorse or promote products derived from this software
17
+ without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
20
+ OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
23
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
24
+ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
25
+ OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
26
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
27
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
28
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
29
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+
data/Manifest.txt ADDED
@@ -0,0 +1,73 @@
1
+ CHANGELOG.txt
2
+ LICENSE.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ bin/geocode
7
+ init.rb
8
+ lib/graticule.rb
9
+ lib/graticule/cli.rb
10
+ lib/graticule/distance.rb
11
+ lib/graticule/distance/haversine.rb
12
+ lib/graticule/distance/spherical.rb
13
+ lib/graticule/distance/vincenty.rb
14
+ lib/graticule/geocoder.rb
15
+ lib/graticule/geocoder/base.rb
16
+ lib/graticule/geocoder/bogus.rb
17
+ lib/graticule/geocoder/geocoder_ca.rb
18
+ lib/graticule/geocoder/geocoder_us.rb
19
+ lib/graticule/geocoder/google.rb
20
+ lib/graticule/geocoder/host_ip.rb
21
+ lib/graticule/geocoder/local_search_maps.rb
22
+ lib/graticule/geocoder/map_quest.rb
23
+ lib/graticule/geocoder/meta_carta.rb
24
+ lib/graticule/geocoder/multi.rb
25
+ lib/graticule/geocoder/postcode_anywhere.rb
26
+ lib/graticule/geocoder/rest.rb
27
+ lib/graticule/geocoder/yahoo.rb
28
+ lib/graticule/location.rb
29
+ lib/graticule/version.rb
30
+ site/index.html
31
+ site/plugin.html
32
+ site/stylesheets/style.css
33
+ test/config.yml.default
34
+ test/fixtures/responses/geocoder_us/success.xml
35
+ test/fixtures/responses/geocoder_us/unknown.xml
36
+ test/fixtures/responses/google/badkey.xml
37
+ test/fixtures/responses/google/limit.xml
38
+ test/fixtures/responses/google/missing_address.xml
39
+ test/fixtures/responses/google/only_coordinates.xml
40
+ test/fixtures/responses/google/partial.xml
41
+ test/fixtures/responses/google/server_error.xml
42
+ test/fixtures/responses/google/success.xml
43
+ test/fixtures/responses/google/unavailable.xml
44
+ test/fixtures/responses/google/unknown_address.xml
45
+ test/fixtures/responses/host_ip/private.txt
46
+ test/fixtures/responses/host_ip/success.txt
47
+ test/fixtures/responses/host_ip/unknown.txt
48
+ test/fixtures/responses/local_search_maps/empty.txt
49
+ test/fixtures/responses/local_search_maps/not_found.txt
50
+ test/fixtures/responses/local_search_maps/success.txt
51
+ test/fixtures/responses/meta_carta/bad_address.xml
52
+ test/fixtures/responses/meta_carta/multiple.xml
53
+ test/fixtures/responses/meta_carta/success.xml
54
+ test/fixtures/responses/postcode_anywhere/badkey.xml
55
+ test/fixtures/responses/postcode_anywhere/canada.xml
56
+ test/fixtures/responses/postcode_anywhere/empty.xml
57
+ test/fixtures/responses/postcode_anywhere/success.xml
58
+ test/fixtures/responses/postcode_anywhere/uk.xml
59
+ test/fixtures/responses/yahoo/success.xml
60
+ test/fixtures/responses/yahoo/unknown_address.xml
61
+ test/mocks/uri.rb
62
+ test/test_helper.rb
63
+ test/unit/graticule/distance_test.rb
64
+ test/unit/graticule/geocoder/geocoder_us_test.rb
65
+ test/unit/graticule/geocoder/google_test.rb
66
+ test/unit/graticule/geocoder/host_ip_test.rb
67
+ test/unit/graticule/geocoder/local_search_maps_test.rb
68
+ test/unit/graticule/geocoder/meta_carta_test.rb
69
+ test/unit/graticule/geocoder/multi_test.rb
70
+ test/unit/graticule/geocoder/postcode_anywhere_test.rb
71
+ test/unit/graticule/geocoder/yahoo_test.rb
72
+ test/unit/graticule/geocoder_test.rb
73
+ test/unit/graticule/location_test.rb
data/README.txt ADDED
@@ -0,0 +1,29 @@
1
+ = Graticule
2
+
3
+ Graticule is a geocoding API for looking up address coordinates. It supports many popular APIs, including Yahoo, Google, Geocoder.ca, Geocoder.us, PostcodeAnywhere and MetaCarta.
4
+
5
+ = Usage
6
+
7
+ require 'rubygems'
8
+ require 'graticule'
9
+ geocoder = Graticule.service(:google).new "api_key"
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
26
+
27
+ = Source
28
+
29
+ The source code for Graticule is available at http://source.collectiveidea.com/public/geocode/trunk
data/Rakefile ADDED
@@ -0,0 +1,86 @@
1
+ require 'rubygems'
2
+ require 'active_support'
3
+ require 'hoe'
4
+ require File.join(File.dirname(__FILE__), 'lib', 'graticule', 'version.rb')
5
+
6
+ Hoe.new("graticule", Graticule::Version::STRING) do |p|
7
+ p.rubyforge_name = "graticule"
8
+ p.author = 'Brandon Keepers'
9
+ p.email = 'brandon@opensoul.org'
10
+ p.summary = "API for using all the popular geocoding services."
11
+ p.description = 'Graticule is a geocoding API that provides a common interface to all the popular services, including Google, Yahoo, Geocoder.us, and MetaCarta.'
12
+ p.url = 'http://graticule.rubyforge.org'
13
+ p.need_tar = true
14
+ p.need_zip = true
15
+ p.test_globs = ['test/**/*_test.rb']
16
+ p.changes = p.paragraphs_of('CHANGELOG.txt', 0..1).join("\n\n")
17
+ p.extra_deps << ['activesupport']
18
+ end
19
+
20
+
21
+
22
+ namespace :test do
23
+ namespace :cache do
24
+ desc 'Cache test responses from all the free geocoders'
25
+ task :free => [:google, :geocoder_us, :host_ip, :local_search_maps, :meta_carta, :yahoo]
26
+
27
+ desc 'Cache test responses from Google'
28
+ task :google do
29
+ cache_responses('google')
30
+ end
31
+
32
+ desc 'Cache test responses from Geocoder.us'
33
+ task :geocoder_us do
34
+ cache_responses('geocoder_us')
35
+ end
36
+
37
+ desc 'Cache test responses from HostIP'
38
+ task :host_ip do
39
+ cache_responses('host_ip')
40
+ end
41
+
42
+ desc 'Cache test responses from Local Search Maps'
43
+ task :local_search_maps do
44
+ cache_responses('local_search_maps')
45
+ end
46
+
47
+ desc 'Cache test responses from Meta Carta'
48
+ task :meta_carta do
49
+ cache_responses('meta_carta')
50
+ end
51
+
52
+ desc 'Cache test responses from Yahoo'
53
+ task :yahoo do
54
+ cache_responses('yahoo')
55
+ end
56
+
57
+ end
58
+ end
59
+
60
+ require 'net/http'
61
+ require 'uri'
62
+ RESPONSES_PATH = File.dirname(__FILE__) + '/test/fixtures/responses'
63
+
64
+ def cache_responses(service)
65
+ test_config[service.to_s]['responses'].each do |file,url|
66
+ File.open("#{RESPONSES_PATH}/#{service}/#{file}", 'w') do |f|
67
+ f.puts Net::HTTP.get(URI.parse(url))
68
+ end
69
+ end
70
+ end
71
+
72
+ def test_config
73
+ file = File.dirname(__FILE__) + '/test/config.yml'
74
+ raise "Copy config.yml.default to config.yml and set the API keys" unless File.exists?(file)
75
+ @test_config ||= returning(YAML.load(File.read(file))) do |config|
76
+ config.each do |service,values|
77
+ values['responses'].each {|file,url| update_placeholders!(values, url) }
78
+ end
79
+ end
80
+ end
81
+
82
+ def update_placeholders!(config, thing)
83
+ config.each do |option, value|
84
+ thing.gsub!(":#{option}", value) if value.is_a?(String)
85
+ end
86
+ end
data/bin/geocode ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'graticule/cli'
4
+
5
+ Graticule::Cli.start ARGV
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+
2
+ require 'graticule'
@@ -0,0 +1,64 @@
1
+ require 'graticule'
2
+ require 'optparse'
3
+
4
+ module Graticule
5
+
6
+ # A command line interface for geocoding. From the command line, run:
7
+ #
8
+ # geocode 49423
9
+ #
10
+ # Outputs:
11
+ #
12
+ # # Holland, MI 49423 US
13
+ # # latitude: 42.7654, longitude: -86.1085
14
+ #
15
+ # == Usage: geocode [options] location
16
+ #
17
+ # Options:
18
+ # -s, --service service Geocoding service
19
+ # -a, --apikey apikey API key for the selected service
20
+ # -h, --help Help
21
+ class Cli
22
+
23
+ def self.start(args, out = STDOUT)
24
+ options = { :service => :yahoo, :api_key => 'YahooDemo' }
25
+
26
+ OptionParser.new do |opts|
27
+ opts.banner = "Usage: geocode [options] location"
28
+ opts.separator ""
29
+ opts.separator "Options: "
30
+
31
+ opts.on("-s service", %w(yahoo google geocoder_us metacarta), "--service service", "Geocoding service") do |service|
32
+ options[:service] = service
33
+ end
34
+
35
+ opts.on("-a apikey", "--apikey apikey", "API key for the selected service")
36
+
37
+ opts.on_tail("-h", "--help", "Help") do
38
+ puts opts
39
+ exit
40
+ end
41
+ end.parse! args
42
+
43
+ options[:location] = args.join(" ")
44
+
45
+ result = Graticule.service(options[:service]).new(*options[:api_key].split(',')).locate(options[:location])
46
+ location = (result.is_a?(Array) ? result.first : result)
47
+ if location
48
+ out << location.to_s(:coordinates => true)
49
+ exit 0
50
+ else
51
+ out << "Location not found"
52
+ exit 1
53
+ end
54
+ rescue Graticule::CredentialsError
55
+ $stderr.puts "Invalid API key. Pass your #{options[:service]} API key using the -a option. "
56
+ rescue OptionParser::InvalidArgument, OptionParser::InvalidOption,
57
+ Graticule::Error => error
58
+ $stderr.puts error.message
59
+ end
60
+
61
+
62
+ end
63
+ end
64
+
@@ -0,0 +1,40 @@
1
+ module Graticule
2
+ module Distance
3
+ #
4
+ # The Haversine Formula works better at small distances than the Spherical Law of Cosines
5
+ #
6
+ # Thanks to Chris Veness (http://www.movable-type.co.uk/scripts/LatLong.html)
7
+ # for distance formulas.
8
+ #
9
+ class Haversine < DistanceFormula
10
+
11
+ # Calculate the distance between two Locations using the Haversine formula
12
+ #
13
+ # Graticule::Distance::Haversine.distance(
14
+ # Graticule::Location.new(:latitude => 42.7654, :longitude => -86.1085),
15
+ # Graticule::Location.new(:latitude => 41.849838, :longitude => -87.648193)
16
+ # )
17
+ # #=> 101.061720831836
18
+ #
19
+ def self.distance(from, to, units = :miles)
20
+ from_longitude = deg2rad(from.longitude)
21
+ from_latitude = deg2rad(from.latitude)
22
+ to_longitude = deg2rad(to.longitude)
23
+ to_latitude = deg2rad(to.latitude)
24
+
25
+ latitude_delta = to_latitude - from_latitude
26
+ longitude_delta = to_longitude - from_longitude
27
+
28
+ a = sin(latitude_delta/2)**2 +
29
+ cos(from_latitude) *
30
+ cos(to_latitude) *
31
+ sin(longitude_delta/2)**2
32
+
33
+ c = 2 * atan2(sqrt(a), sqrt(1-a))
34
+
35
+ d = EARTH_RADIUS[units.to_sym] * c
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,52 @@
1
+ module Graticule
2
+ module Distance
3
+
4
+ #
5
+ # The Spherical Law of Cosines is the simplist though least accurate distance
6
+ # formula (earth isn't a perfect sphere).
7
+ #
8
+ class Spherical < DistanceFormula
9
+
10
+ # Calculate the distance between two Locations using the Spherical formula
11
+ #
12
+ # Graticule::Distance::Spherical.distance(
13
+ # Graticule::Location.new(:latitude => 42.7654, :longitude => -86.1085),
14
+ # Graticule::Location.new(:latitude => 41.849838, :longitude => -87.648193)
15
+ # )
16
+ # #=> 101.061720831853
17
+ #
18
+ def self.distance(from, to, units = :miles)
19
+ from_longitude = deg2rad(from.longitude)
20
+ from_latitude = deg2rad(from.latitude)
21
+ to_longitude = deg2rad(to.longitude)
22
+ to_latitude = deg2rad(to.latitude)
23
+
24
+ Math.acos(
25
+ Math.sin(from_latitude) *
26
+ Math.sin(to_latitude) +
27
+
28
+ Math.cos(from_latitude) *
29
+ Math.cos(to_latitude) *
30
+ Math.cos(to_longitude - from_longitude)
31
+ ) * EARTH_RADIUS[units.to_sym]
32
+ end
33
+
34
+ def self.to_sql(options)
35
+ options = {
36
+ :units => :miles,
37
+ :latitude_column => 'latitude',
38
+ :longitude_column => 'longitude'
39
+ }.merge(options)
40
+ %{(ACOS(
41
+ SIN(RADIANS(#{options[:latitude]})) *
42
+ SIN(RADIANS(#{options[:latitude_column]})) +
43
+ COS(RADIANS(#{options[:latitude]})) *
44
+ COS(RADIANS(#{options[:latitude_column]})) *
45
+ COS(RADIANS(#{options[:longitude_column]}) - RADIANS(#{options[:longitude]}))
46
+ ) * #{Graticule::Distance::EARTH_RADIUS[options[:units].to_sym]})
47
+ }.gsub("\n", '').squeeze(" ")
48
+ end
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,76 @@
1
+ module Graticule
2
+ module Distance
3
+
4
+ #
5
+ # The Vincenty Formula uses an ellipsoidal model of the earth, which is very accurate.
6
+ #
7
+ # Thanks to Chris Veness (http://www.movable-type.co.uk/scripts/LatLongVincenty.html)
8
+ # for distance formulas.
9
+ #
10
+ class Vincenty < DistanceFormula
11
+
12
+ # Calculate the distance between two Locations using the Vincenty formula
13
+ #
14
+ # Graticule::Distance::Vincenty.distance(
15
+ # Graticule::Location.new(:latitude => 42.7654, :longitude => -86.1085),
16
+ # Graticule::Location.new(:latitude => 41.849838, :longitude => -87.648193)
17
+ # )
18
+ # #=> 101.070118000159
19
+ #
20
+ def self.distance(from, to, units = :miles)
21
+ from_longitude = deg2rad(from.longitude)
22
+ from_latitude = deg2rad(from.latitude)
23
+ to_longitude = deg2rad(to.longitude)
24
+ to_latitude = deg2rad(to.latitude)
25
+
26
+ earth_major_axis_radius = EARTH_MAJOR_AXIS_RADIUS[units.to_sym]
27
+ earth_minor_axis_radius = EARTH_MINOR_AXIS_RADIUS[units.to_sym]
28
+
29
+ f = (earth_major_axis_radius - earth_minor_axis_radius) / earth_major_axis_radius
30
+
31
+ l = to_longitude - from_longitude
32
+ u1 = atan((1-f) * tan(from_latitude))
33
+ u2 = atan((1-f) * tan(to_latitude))
34
+ sin_u1 = sin(u1)
35
+ cos_u1 = cos(u1)
36
+ sin_u2 = sin(u2)
37
+ cos_u2 = cos(u2)
38
+
39
+ lambda = l
40
+ lambda_p = 2 * PI
41
+ iteration_limit = 20
42
+ while (lambda-lambda_p).abs > 1e-12 && (iteration_limit -= 1) > 0
43
+ sin_lambda = sin(lambda)
44
+ cos_lambda = cos(lambda)
45
+ sin_sigma = sqrt((cos_u2*sin_lambda) * (cos_u2*sin_lambda) +
46
+ (cos_u1*sin_u2-sin_u1*cos_u2*cos_lambda) * (cos_u1*sin_u2-sin_u1*cos_u2*cos_lambda))
47
+ return 0 if sin_sigma == 0 # co-incident points
48
+ cos_sigma = sin_u1*sin_u2 + cos_u1*cos_u2*cos_lambda
49
+ sigma = atan2(sin_sigma, cos_sigma)
50
+ sin_alpha = cos_u1 * cos_u2 * sin_lambda / sin_sigma
51
+ cosSqAlpha = 1 - sin_alpha*sin_alpha
52
+ cos2SigmaM = cos_sigma - 2*sin_u1*sin_u2/cosSqAlpha
53
+
54
+ cos2SigmaM = 0 if cos2SigmaM.nan? # equatorial line: cosSqAlpha=0 (§6)
55
+
56
+ c = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha))
57
+ lambda_p = lambda
58
+ lambda = l + (1-c) * f * sin_alpha *
59
+ (sigma + c*sin_sigma*(cos2SigmaM+c*cos_sigma*(-1+2*cos2SigmaM*cos2SigmaM)))
60
+ end
61
+ # formula failed to converge (happens on antipodal points)
62
+ # We'll call Haversine formula instead.
63
+ return Haversine.distance(from, to, units) if iteration_limit == 0
64
+
65
+ uSq = cosSqAlpha * (earth_major_axis_radius**2 - earth_minor_axis_radius**2) / (earth_minor_axis_radius**2)
66
+ a = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)))
67
+ b = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)))
68
+ delta_sigma = b*sin_sigma*(cos2SigmaM+b/4*(cos_sigma*(-1+2*cos2SigmaM*cos2SigmaM)-
69
+ b/6*cos2SigmaM*(-3+4*sin_sigma*sin_sigma)*(-3+4*cos2SigmaM*cos2SigmaM)))
70
+
71
+ earth_minor_axis_radius * a * (sigma-delta_sigma)
72
+ end
73
+
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,29 @@
1
+ module Graticule
2
+ module Distance
3
+
4
+ EARTH_RADIUS = { :kilometers => 6378.135, :miles => 3963.1676 }
5
+ # WGS-84 numbers
6
+ EARTH_MAJOR_AXIS_RADIUS = { :kilometers => 6378.137, :miles => 3963.19059 }
7
+ EARTH_MINOR_AXIS_RADIUS = { :kilometers => 6356.7523142, :miles => 3949.90276 }
8
+
9
+ class DistanceFormula
10
+ include Math
11
+ extend Math
12
+
13
+ def initialize
14
+ raise NotImplementedError
15
+ end
16
+
17
+ # Convert from degrees to radians
18
+ def self.deg2rad(deg)
19
+ (deg * PI / 180)
20
+ end
21
+
22
+ # Convert from radians to degrees
23
+ def self.rad2deg(rad)
24
+ (rad * 180 / PI)
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,113 @@
1
+ require 'open-uri'
2
+
3
+ module Graticule #:nodoc:
4
+ module Geocoder
5
+
6
+ # Abstract class for implementing geocoders.
7
+ #
8
+ # === Example
9
+ #
10
+ # The following methods must be implemented in sublcasses:
11
+ #
12
+ # * +initialize+:: Sets @url to the service enpoint.
13
+ # * +check_error+:: Checks for errors in the server response.
14
+ # * +parse_response+:: Extracts information from the server response.
15
+ #
16
+ # Optionally, you can also override
17
+ #
18
+ # * +prepare_response+:: Convert the string response into a different format
19
+ # that gets passed on to +check_error+ and +parse_response+.
20
+ #
21
+ # If you have extra URL paramaters (application id, output type) or need to
22
+ # perform URL customization, override +make_url+.
23
+ #
24
+ # class FakeGeocoder < Base
25
+ #
26
+ # def initialize(appid)
27
+ # @appid = appid
28
+ # @url = URI.parse 'http://example.com/test'
29
+ # end
30
+ #
31
+ # def locate(query)
32
+ # get :q => query
33
+ # end
34
+ #
35
+ # private
36
+ #
37
+ # def check_error(xml)
38
+ # raise Error, xml.elements['error'].text if xml.elements['error']
39
+ # end
40
+ #
41
+ # def make_url(params)
42
+ # params[:appid] = @appid
43
+ # super params
44
+ # end
45
+ #
46
+ # def parse_first(response)
47
+ # # return Location
48
+ # end
49
+ #
50
+ # end
51
+ #
52
+ class Base
53
+ USER_AGENT = "Mozilla/5.0 (compatible; Graticule/#{Graticule::Version::STRING}; http://graticule.rubyforge.org)"
54
+
55
+ def initialize
56
+ raise NotImplementedError
57
+ end
58
+
59
+ private
60
+
61
+ def location_from_params(params)
62
+ case params
63
+ when Location then params
64
+ when Hash then Location.new params
65
+ else
66
+ raise ArgumentError, "Expected a Graticule::Location or a hash with :street, :locality, :region, :postal_code, and :country attributes"
67
+ end
68
+ end
69
+
70
+ # Check for errors in +response+ and raise appropriate error, if any.
71
+ # Must return if no error could be found.
72
+ def check_error(response)
73
+ raise NotImplementedError
74
+ end
75
+
76
+ # Performs a GET request with +params+. Calls +check_error+ and returns
77
+ # the result of +parse_response+.
78
+ def get(*args)
79
+ params = args.last.is_a?(::Hash) ? args.pop : args
80
+ response = prepare_response(make_url(params).open('User-Agent' => USER_AGENT).read)
81
+ check_error(response)
82
+ parse_response(args.first, response)
83
+ rescue OpenURI::HTTPError => e
84
+ check_error(prepare_response(e.io.read))
85
+ raise
86
+ end
87
+
88
+ # Creates a URI from the Hash +params+. Override this then call super if
89
+ # you need to add extra params like an application id or output type.
90
+ def make_url(params)
91
+ escaped_params = params.sort_by { |k,v| k.to_s }.map do |k,v|
92
+ "#{URI.escape k.to_s}=#{URI.escape v.to_s}"
93
+ end
94
+
95
+ url = @url.dup
96
+ url.query = escaped_params.join '&'
97
+ return url
98
+ end
99
+
100
+ # Override to convert the response to something besides a String, which
101
+ # will get passed to +check_error+ and +parse_response+.
102
+ def prepare_response(response)
103
+ response
104
+ end
105
+
106
+ # Must parse :first or :all results from +response+ into a Location.
107
+ def parse_response(parse_type, response)
108
+ raise NotImplementedError
109
+ end
110
+
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,15 @@
1
+ module Graticule #:nodoc:
2
+ module Geocoder #:nodoc:
3
+
4
+ # Bogus geocoder that can be used for test purposes
5
+ class Bogus
6
+
7
+ # returns a new location with the address set to the original query string
8
+ def locate(address)
9
+ Location.new :street => address
10
+ end
11
+
12
+ end
13
+
14
+ end
15
+ end